Pregunta

I got a problem which is driving me insane. I want to make the QMenuBar only visible when moused-over, else it should be hidden.

So far i got this to "work":

class Hidden_Menubar(QtGui.QMenuBar):           
    def __init__(self, parent=None):
        super(Hidden_Menubar, self).__init__(parent)        
        self.setMouseTracking(True)                     

    def enterEvent(self,event):       
        self.show()

    def leaveEvent(self,event):
        self.hide()

And

 class Ui_Template_FullScreen(object):
        def setupUi(self, Template_FullScreen):
            Template_FullScreen.setObjectName(_fromUtf8("Template_FullScreen"))
            Template_FullScreen.showFullScreen()        
            self.centralwidget = QtGui.QWidget(Template_FullScreen)
            self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
            Template_FullScreen.setCentralWidget(self.centralwidget)

            self.menubar = Hidden_Menubar(Template_FullScreen)
            ......

The Problem is, that as soon as the mouse stops hovering the QMenuBar, it disappears (so far so good) but then it won't be visible if i hover the QMenuBar area again! I guess the mouseMoveEvent does not trigger on hidden objects, or is something else the matter? I tried lots of solutions, for instance installing an event filter, however i was not able to implement it correctly. I'm completely new to python and QT, so I can't figure it out myself. I appreciate every help.

Thanks in advance =)

test.py: http://pastebin.com/hmRvYVup (full code)

EDIT: Thank you all for the very helpful answers! Unfortunately I can't upvote your posts, because I'm missing reputation :/

¿Fue útil?

Solución

This is trickier than it looks. The main problem is tracking mouse movements over the whole window (including all child widgets) and ensuring that the menu is only hidden when appropriate (i.e. not when a menu is shown).

One way top do this is to install an event-filter on the QApplication (so that it receives mouse-move events for all child widgets), and make use of the activePopupWidget method to check if there are any active menus.

Here's a demo script that shows a basic implementation:

from PyQt4 import QtCore, QtGui

class Window(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        # add a few widgets for testing
        widget = QtGui.QWidget(self)
        edit = QtGui.QTextEdit(widget)
        button = QtGui.QPushButton('Button', widget)
        layout = QtGui.QVBoxLayout(widget)
        layout.addWidget(edit)
        layout.addWidget(button)
        self.setCentralWidget(widget)
        menu = self.menuBar().addMenu('&File')
        menu.addAction('&Quit', self.close)
        menu = self.menuBar().addMenu('&Edit')
        menu.addAction('&Clear', edit.clear)
        QtGui.qApp.installEventFilter(self)
        # make sure initial window size includes menubar
        QtCore.QTimer.singleShot(0, self.menuBar().hide)

    def eventFilter(self, source, event):
        # do not hide menubar when menu shown
        if QtGui.qApp.activePopupWidget() is None:
            if event.type() == QtCore.QEvent.MouseMove:
                if self.menuBar().isHidden():
                    rect = self.geometry()
                    # set mouse-sensitive zone
                    rect.setHeight(25)
                    if rect.contains(event.globalPos()):
                        self.menuBar().show()
                else:
                    rect = QtCore.QRect(
                        self.menuBar().mapToGlobal(QtCore.QPoint(0, 0)),
                        self.menuBar().size())
                    if not rect.contains(event.globalPos()):
                        self.menuBar().hide()
            elif event.type() == QtCore.QEvent.Leave and source is self:
                self.menuBar().hide()
        return QtGui.QMainWindow.eventFilter(self, source, event)

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 300, 100)
    window.show()
    sys.exit(app.exec_())

Otros consejos

It's an interesting task.

The main problem with your approach is that hidden widgets doesn't receive events (or at least mouse events). But still you can implement your behavior overriding mouseMoveEvent on the central widget, try this:

class Hidden_Menubar(QtGui.QMenuBar):

    def __init__(self, parent=None, centralWidget=None):
        super(Hidden_Menubar, self).__init__(parent)  
        if centralWidget:
            centralWidget.setMouseTracking(True)   
            centralWidget.mouseMoveEvent = self.onMove

    def onMove(self, evt): 
        if self.isVisible():
            self.hide()
        elif evt.pos().y()<20:
            self.show()

And of course your Hidden_Menubar should be instantiated this way:

...
self.menubar = Hidden_Menubar(Template_FullScreen,self.centralwidget)
...

Hope it helps.

The requirement is not really complete: you can't have something that you can't see act as though you could see it. The raison-d'etre for hide-when-not-in-use strategy is that the menu bar occupies significant screen space and is only used sometimes, so when not in use you want to hide it. I can think of two strategies other than the ones mentioned by ekhumoro and xndrme: show-when-near, and collapse-to-small-but-not-zero.

  1. With the show-when-near, you want the menu to appear when your mouse is near the edge where menu will "slide out of". This requires that whatever widgets are in that "near" area capture mouse and notify the window. This is easy to do in WPF where events trickle all the up the widget tree, so base panel (which is the parent of hideable menu) can capture and hide/show as required. This works nicely, but I don't know enough about Qt yet to know if Qt has similar bubbling up of mouse events. Also, in effect the "near" area cannot be used by other widgets except to trigger showing of menu, so in effect that "near" band is only useful for viewing, you won't be able to click on buttons or text fields etc in that area. You might as well use technique #2.
  2. In the collapse-to-small-but-not-zero approach, your menu area is not completely hidden, you keep a narrow "blank" band widget which, as soon as focussed, causes menu to appear; when loose focus, cause menu to disappear. So if menu is 100 (bar) or 300 (ribbon) pixels high, the band might be only 20 pixels wide, enough to easily hover over, but not enough to waste screen real-estate. Also, this technique gives a clear cue to the user that "there is something there", not the case with the hidden menu (you have to know it is there, or discover it by coincidence like the toolbar that appears over in-browser-pdf), not as friendly.

You should consider using a hover delay such that menu becomes visible only if delay long enough (like half a second). This ensures that you don't have fast in-out widgets appearing/disappearing which can be annoying for the user because sudden changes jolt and grab our attention.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top