1

I have a PyQt5 GUI which has a main window with buttons and a QLineEdit in it.

I made the keyPressEvent function and set certain keys on the keyboard to do different things. All of the keys that I set work other than the Enter button. When you press the Enter key it either triggers the number 7 onscreen pushbutton (which is the first button made in the GUI) if no onscreen pushbutton was clicked. Once a pushbutton is clicked then the Enter key will always trigger the last onscreen pushbutton that was clicked. All the other events work fine. Anyone know why this is happening?

MRE:

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QDialog, QPushButton
from PyQt5.QtCore import*
from PyQt5.QtWidgets import*

if hasattr(QtCore.Qt, 'AA_EnableHighDpiScaling'):
    QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'):
    QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
    
# I know global variables is bad programming. Just doing this for the example
outputText = ""

class Ui_MainWindow(object):

    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        
        MainWindow.setFixedSize(331, 411)
       
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.button_7 = QtWidgets.QPushButton(self.centralwidget)
        self.button_7.setGeometry(QtCore.QRect(20, 190, 71, 41))
        self.button_7.setStyleSheet("QPushButton\n"
"{\n"
"border: none;\n"
"background-color: rgb(255, 255, 255);\n"
"font: 20pt \"Arial\";\n"
"}\n"
"QPushButton:hover{\n"
"background-color: rgb(220, 220, 220);\n"
"}\n"
"QPushButton:pressed\n"
"{\n"
"background-color: rgb(212, 212, 212);\n"
"}\n"
"\n"
"")
        self.button_7.setAutoDefault(True)
        self.button_7.setDefault(False)
        self.button_7.setFlat(True)
        self.button_7.setObjectName("button_7")
        self.button_7.clicked.connect(self.click_and_update)

        self.screenOutput = QtWidgets.QLineEdit(self.centralwidget)
        self.screenOutput.setGeometry(QtCore.QRect(20, 30, 291, 20))
        self.screenOutput.setStyleSheet("border: none; background: transparent;"
        "font: 12pt \"MS Shell Dlg 2\";\n""color: rgb(190, 190, 190);")
        self.screenOutput.setAlignment(QtCore.Qt.AlignCenter)
        self.screenOutput.setObjectName("eqInput")
        

        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 331, 22))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", " MRE"))
        self.button_7.setText(_translate("MainWindow", "7"))
        self.screenOutput.setText(_translate("MainWindow", "Do Something"))

        # set keyPressEvent to current widgets that we'd like it to be overridden
        self.centralwidget.keyPressEvent = self.keyPressEvent
        self.screenOutput.keyPressEvent = self.keyPressEvent
   
    def keyPressEvent(self,e):
        if e.key() == Qt.Key_Enter:
                self.equal_click()

        if e.key() == Qt.Key_Equal:
                self.equal_click()
   

    def update_screen(self):
        self.screenOutput.setText(outputText)
        return


    def equal_click(self):
        global outputText
        outputText = "Pressed Key"
        self.update_screen()
        return

    def click_and_update(self):
        global outputText
        outputText+=" 7"
        self.update_screen()
        return

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

The Equal key works fine, the Enter key does not.

yem
  • 529
  • 1
  • 6
  • 20
  • please provide a [mre] – eyllanesc Apr 28 '21 at 16:17
  • Exactly, that's the SO logic: the OP provides an MRE, the community tests that MRE and proposes a solution. Please read the link – eyllanesc Apr 28 '21 at 16:23
  • That code is fine, maybe it has to do with the rest of you code? – red_panda Apr 28 '21 at 16:34
  • @red_panda that it seems that this good does not imply that it is good, how do you know that it was placed in the right class? So when the OP provides pieces of code it is better to ask him to provide an MRE so we can affirm something so definitive by having evidence – eyllanesc Apr 28 '21 at 16:36
  • ok guys. I made a MRE. The GUI for both the MRE and my actual program were generated by Qt Designer and then slightly modified by me. – yem Apr 28 '21 at 16:58
  • I made the MRE in a way that reflects the way it was programmed (while retaining the issue) but taking away any non relevant code. – yem Apr 28 '21 at 17:03

1 Answers1

3

The problem is that the Enter key is associated with Qt.Key_Return, and only in keypads is it Qt.Key_Enter. So a generic solution is to check both keys:

if e.key() in (Qt.Key_Return, Qt.Key_Enter):
    self.equal_click()

However, it does not correct the real error since the problem is that the button has focus, so the keypress event is not propagated to its parent.

Also it is not advisable to do foo.keyPressEvent = bar since in many cases it can fail and if you have "n" widgets you will have to implement that logic for all of them. A more elegant solution is to use an event filter on the window. So you must restore the .py file since it is not recommended to modify it (see this post: QtDesigner changes will be lost after redesign User Interface) and I will assume that it is called main_ui.py

from PyQt5 import QtCore, QtWidgets

from main_ui import Ui_MainWindow


class KeyHelper(QtCore.QObject):
    keyPressed = QtCore.pyqtSignal(QtCore.Qt.Key)

    def __init__(self, window):
        super().__init__(window)
        self._window = window

        self.window.installEventFilter(self)

    @property
    def window(self):
        return self._window

    def eventFilter(self, obj, event):
        if obj is self.window and event.type() == QtCore.QEvent.KeyPress:
            self.keyPressed.emit(event.key())
        return super().eventFilter(obj, event)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

    def handle_key_pressed(self, key):
        if key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
            self.update_text()

    def update_text(self):
        text = self.ui.screenOutput.text() + "7"
        self.ui.screenOutput.setText(text)


if hasattr(QtCore.Qt, "AA_EnableHighDpiScaling"):
    QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
if hasattr(QtCore.Qt, "AA_UseHighDpiPixmaps"):
    QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    w = MainWindow()
    w.show()

    helper = KeyHelper(w.windowHandle())
    helper.keyPressed.connect(w.handle_key_pressed)

    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Tried both suggestions. Neither seems to solve the problem. The Enter key still prints a 7 to the screen. – yem Apr 28 '21 at 17:28
  • I tested it with the MRE that I provided. – yem Apr 28 '21 at 17:30
  • @yem I forgot to mention that the first part is not the solution, but the second method does work, have you tried my second code? – eyllanesc Apr 28 '21 at 17:37
  • I tried the second code as well. Here's what i did: I copied your code into a new .py file which I named MinimalExampleFilter.py and i substituted ```from main_ui import Ui_MainWindow``` with ```from MinimalExample import Ui_MainWindow``` because the code that I posted as the MRE was named MinimalExample.py – yem Apr 28 '21 at 19:10
  • should I try something else? – yem Apr 28 '21 at 19:11
  • @yem Have you restored the MinimalExample.py code as I pointed out in my post? that is, run pyuic5 again so that the MinimalExample.py does not have your modifications and run `python MinimalExampleFilter.py` – eyllanesc Apr 28 '21 at 19:14
  • my GUI is already too different to do that. Is there no way to get around that? I like the modifications that I coded and can't get all of them the way I like in QtDesigner. I really just used QtDesigner as a base back at the beginning of the project... – yem Apr 28 '21 at 19:24
  • @yem 1) have you read the link I publish in my post? You should not and should not modify the code generated by pyuic as indicated by the warning at the top of the file so my solution follows that good practice, 2) My answer is based on the post you provide and not on your real code so You will have to work on your real code to adapt it to my solution. – eyllanesc Apr 28 '21 at 19:28
  • 1) i read that post. The issue seemed to be if you want to continue editing the GUI in QtDesigner, no? seemingly, if you have no interest in using QtDesigner for that project at any point in the future, why should there be an issue editing the code? All its seems to do is generate code that we can technically write on our own, no? 2) I'm still talking about your code. Haven't gone back to trying it on my own program. I haven't gotten the MRE which I shared with you to work with the code you sent. – yem Apr 28 '21 at 22:02
  • @yem What you indicate is the basic reason but it is not the determining one. The class generated by Qt Designer is not a widget so for example you cannot override a method such as keyPressEvent, eventFilter, etc. and you have to apply bad practices like `foo.keyPressEvent = obj.keyPressEvent`. They are also the main cause of beginner errors: There are countless posts each week about bugs caused by it. – eyllanesc Apr 28 '21 at 22:09
  • @yem In some cases doing `foo.keyPressEvent = obj.keyPressEvent` will work but in others not since PyQt uses a cache for those properties, for example if you set the above after the user already pressed a key then it will fail since the cache will use the predefined method, on the other hand your method removes the default behavior: Do the same with QLineEdit or QTextEdit and you will see that the basic functionality is lost. – eyllanesc Apr 28 '21 at 22:12
  • @yem With the above I point out that the user loses many things so IMO is a bad practice that I do not want to promote since then N times more questions of that type will come that are boring to answer. I bet if you continue with that logic at some point you will come back for questions about a bug and the cause of the error will be that. So doing that saved me work and helped to have a more orderly code. – eyllanesc Apr 28 '21 at 22:15
  • ok. so is there a way to take the code the I already have and restructure it as a widget? – yem Apr 28 '21 at 22:19
  • @yem 1) Copy the logic to another file, 2) regenerate the file using pyuic, 3) Create the base class as MainWindow in a new file and 4) copy functionality by functionality to the MainWindow and test each functionality. There is no automated way to do this if that is your question. – eyllanesc Apr 28 '21 at 22:24
  • When you say "copy functionality by functionality to the MainWindow". I should make each one as a subclass? – yem Apr 28 '21 at 22:28
  • Is there a reason I have to regenerate the file? I can't just cut and paste the relevant parts to their respective classes? – yem Apr 28 '21 at 22:29
  • @yem No, please review your OOP concepts. The discussion is already very long so I will not answer, bye. I will only answer questions about my answer, not about your project. – eyllanesc Apr 28 '21 at 22:29