pythonpyqt

PyQt5 - Hover signal for QPushButton created via Qt Designer


I just created my first PyQt app used to store personnal data. On the New Entry Dialog there is a button that when clicked, fills in QLineEdits with default values. I would like to implement a feature so that when the mouse cursor hovers this Default button, you get a preview (probably via setPlaceholderText) of what the QLineEdits will be set to.

After looking around for a solution I came across this solution : How to Catch Hover and Mouse Leave Signal In PyQt5 to subclass the PushButton and reimplement enterEvent and leaveEvent.

However I have created my GUI with Qt Designer and am a bit confused as to how I can apply this solution since the QPushButton is created inside the Designer's .ui file where I can't really make changes...

Here's an extract of the .ui file when converted to .py with pyuic5

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")

        self.pushButton_contact_defaut = QtWidgets.QPushButton(self.groupBox_client)
        self.pushButton_contact_defaut.setGeometry(QtCore.QRect(80, 130, 165, 22))
        self.pushButton_contact_defaut.setMouseTracking(True)
        self.pushButton_contact_defaut.setAutoDefault(False)
        self.pushButton_contact_defaut.setObjectName("pushButton_contact_defaut")

As I said, I can't really make changes there as the code is reseted everytime I make changes to the ui file...

And here is also an extract of my main python file where I ''handle'' all the connections and logic. I am obviously not too familiar with Python and PyQt (or anything related to programming really!) Is there a way to ''redefine'' the PushButton from within my code and is that the best way to approach the problem, or is there something else I am missing?

class NewEntry(NE_Base, NE_Ui):
    def __init__(self):
        super().__init__()
        QDialog.__init__(self, parent=main_window)
        self.ui = NE_Ui()
        self.ui.setupUi(self)
        self.setWindowModality(0)
        self.ui.pushButton_contact_defaut.clicked.connect(self.contact_defaut)

Thanks for your help!

EDIT : Based on musicamante's answer I got it to work just fine for my app where I have 2 buttons that "fill in" different lineEdit by doing the following.

I applied .installEventFilter(self) on both pushButton and added :

 def eventFilter(self, source, event):
    if event.type() == QtCore.QEvent.Enter and source == self.ui.pushButton_contact_defaut:
        self.ui.contact_text.setPlaceholderText(self.contact_base)
        self.ui.cell_text.setPlaceholderText(self.cell)
        self.ui.email_text.setPlaceholderText(self.courriel)

    if event.type() == QtCore.QEvent.Enter and source == self.ui.pushButton_copy_adress:
        self.ui.street_text.setPlaceholderText(self.street)
        self.ui.city_text.setPlaceholderText(self.city)
        self.ui.postal_text.setPlaceholderText(self.postal)

    elif event.type() == QtCore.QEvent.Leave:
        self.ui.contact_text.setPlaceholderText('')
        self.ui.cell_text.setPlaceholderText('')
        self.ui.email_text.setPlaceholderText('')
        self.ui.street_text.setPlaceholderText('')
        self.ui.city_text.setPlaceholderText('')
        self.ui.postal_text.setPlaceholderText('')

    return super().eventFilter(source, event)

It seems a bit awkward to handle multiple pushButton this way and hopefully someone can enlighten me on that problem as well, but in the meantime, it works!


Solution

  • You can install an event filter on the button you want to track. An event filter is a system that "monitors" events received by the watched object and can eventually do something afterwards (including ignoring the event itself).

    In your case, you'll want to check for Enter and Leave events, which are fired each time the mouse enters or leaves the widget (they are usually implemented in enterEvent and leaveEvents subclasses).

    class NewEntry(QDialog, NE_Ui):
        def __init__(self, parent=None):
            super().__init__(parent)
            # Don't do the following, is unnecessary: you already called __init__
            # QDialog.__init__(self, parent=main_window)
    
            self.ui = NE_Ui()
            self.ui.setupUi(self)
            self.ui.pushButton_contact_defaut.installEventFilter(self)
    
        def eventFilter(self, source, event):
            if event.type() == QEvent.Enter:
                self.ui.lineEdit.setPlaceholderText('Default text')
            elif event.type() == QEvent.Leave:
                self.ui.lineEdit.setPlaceholderText('')
            # *always* return a bool value (meaning that the event has been acted upon
            # or not), it's common to call the base class implementation and then
            # return the result of that
            return super().eventFilter(source, event)
    

    NEVER edit the files generated by pyuic, nor start to use them as a start for your code. As you've already found out, they're cleared each time you change the ui, and it's always better to import them as modules (or use them through uic.loadUi('somefile.ui', self)).