Search code examples
pythonpyqt5qcomboboxqdialogeventfilter

QComboBox click triggers a leaveEvent on the main QDialog


I am trying to build a hover Dialog but i am stuck at the interaction between QComboBox selection and QDialog's leaveEvent. it looks like when i try to click on combobox to select something, it triggers a leaveEvent which then hides my QDialog. Why is this happening? What can i try to ensure that the Dialog is only hidden when I move my mouse outside of the Dialog?

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys

class hoverDialog(QDialog):

    def __init__(self, parent=None):
        super().__init__()
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.v = QVBoxLayout()
        self.combobox = QComboBox()
        self.combobox.addItems(['Work-around','Permanent'])
        self.textedit = QPlainTextEdit()
        self.v.addWidget(self.combobox)
        self.v.addWidget(self.textedit)
        self.setLayout(self.v)
        #self.setMouseTracking(True)

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

class Table(QWidget):

    def __init__(self, parent=None):
        super().__init__()

        self.label4 = QLabel()
        self.label4.setText("hover popup")
        self.label4.installEventFilter(self)
        self.checkbox1 = QCheckBox()

        self.pushButton3 = QPushButton()
        self.pushButton3.setText('Generate')
        self.pushButton3.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
        self.pushButton3.clicked.connect(self.buttonPressed)

        self.hbox5 = QHBoxLayout()
        self.hbox5.addWidget(self.checkbox1)
        self.hbox5.addWidget(self.label4)
        self.hbox5.addWidget(self.pushButton3)

        self.vbox1 = QVBoxLayout()
        self.vbox1.addLayout(self.hbox5)

        self.setLayout(self.vbox1)

        self.autoResolve = hoverDialog(self)

    def eventFilter(self, obj, event):
        if obj == self.label4 and event.type() == QtCore.QEvent.Enter and self.autoResolve.isHidden():
            self.onHovered()
        return super().eventFilter(obj, event)

    def onHovered(self):
        pos = QtGui.QCursor.pos()
        self.autoResolve.move(pos)
        self.autoResolve.show()

    def buttonPressed(self):
        if self.checkbox.isChecked():
            print('do something..')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    form = Table()
    form.show()
    app.exec_()


Solution

  • Looking at the sources, I believe that the origin of the problem is in Qt's behavior whenever a new popup widget is shown.

    I cannot guarantee this at 100%, but in any case the simplest solution is to hide the widget only if the combo popup is not shown:

    def leaveEvent(self, event):
        if not self.combobox.view().isVisible():
            self.hide()
    

    Note that your approach is not really perfect: since the dialog could be shown with a geometry that is outside the current mouse position, it will not be able to hide itself until the mouse actually enters it. You should probably filter FocusOut and WindowDeactivate events also.