1

I'm trying to install a keyPressEvent on a QPlainTextEdit widget such that when type, i type normally and when I hit enter, it will add text to the QPlainTextEdit. I have to files, QtDes.py created by Qt Designer and another file, QtTextEvent.py. These are my files:

QtDes.py:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'QtDes.ui'
#
# Created by: PyQt5 UI code generator 5.13.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtGui, QtWidgets, QtCore

class Ui_MainWindow(object):
    def __init__(self, *args, **kwargs):
        super(Ui_MainWindow, self).__init__(*args, **kwargs)

        self.exactAns = ""
        self.approxAns = 0

    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 569)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.plainTextEdit = QtWidgets.QPlainTextEdit(self.centralwidget)
        font = QtGui.QFont()
        font.setFamily("Courier New")
        self.plainTextEdit.setFont(font)
        self.plainTextEdit.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.plainTextEdit.setTabChangesFocus(False)
        self.plainTextEdit.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap)
        self.plainTextEdit.setOverwriteMode(False)
        self.plainTextEdit.setTabStopWidth(40)
        self.plainTextEdit.setTabStopDistance(40.0)
        self.plainTextEdit.setObjectName("plainTextEdit")
        self.plainTextEdit.appendPlainText("First Line: ")

        self.plainTextEdit.keyPressEvent = self.keyPressEvent

        self.gridLayout.addWidget(self.plainTextEdit, 0, 0, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        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", "MainWindow"))


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_())

and QtTextEvent.py:

from PyQt5 import QtCore, QtGui, QtWidgets

from QtDes import Ui_MainWindow

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, *args, **kwargs):
        QtWidgets.QMainWindow.__init__(self, *args, **kwargs)
        self.setupUi(self)

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Return or event.key() == QtCore.Qt.Key_Enter:
            print("Enter pressed")
            self.plainTextEdit.appendPlainText("New Line: ")
        else:
            super(MainWindow, self).keyPressEvent(event)

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

Pressing enter does what it should do, but pressing other buttons doesn't work. I took the answer from this question and this question. Is there something wrong with my implementation and how could I fix it?

Secozzi
  • 252
  • 3
  • 10

2 Answers2

3

Explanation:

The keyPressEvent method implements the task of adding text to the QPlaintTextEdit when you press a key but you are assigning it another keyPressEvent (from the QMainWindow) that does not implement this logic, that is, it does not add the text. So it is not correct to assign one method to another since you delete the behavior that the widgets have by default.

Solution:

In your case it is only necessary to listen to the keyboard and if you press enter or return then add a text, then to listen to an event you only need an event filter.

To do this you must delete self.plainTextEdit.keyPressEvent = self.keyPressEvent in the QtDes.py file. Also implement the event filter in QtTextEvent.py file:

from PyQt5 import QtCore, QtGui, QtWidgets

from QtDes import Ui_MainWindow


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setupUi(self)
        self.plainTextEdit.installEventFilter(self)

    def eventFilter(self, obj, event):
        if obj is self.plainTextEdit and event.type() == QtCore.QEvent.KeyPress:
            if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
                print("Enter pressed")
                self.plainTextEdit.appendPlainText("New Line: ")
                return True
        return super(MainWindow, self).eventFilter(obj, event)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

Another possible solution is to inherit from QPlainTextEdit and override the keyPressEvent method.

plaintextedit.py

class PlainTextEdit(QtWidgets.QPlainTextEdit):
    def keyPressEvent(self, event):
        if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
            print("Enter pressed")
            self.appendPlainText("New Line: ")
            return
        super(PlainTextEdit, self).keyPressEvent(event)

Then you change to:

QtDes.py

from plaintextedit import PlainTextEdit
# ...
class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        # ...
        self.plainTextEdit = PlainTextEdit(self.centralwidget)
        # ...

(you could also promote it as I indicated in this answer)

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
0

You cannot "install a keyPressEvent", and even if you could, it wouldn't work with your approach.

By doing this:

self.plainTextEdit.keyPressEvent = self.keyPressEvent

you are practically doing something like this:

mainWindowInstance.plainTextEdit.keyPress = mainWindowInstance.keyPressEvent

The result is that the event will not be received by the plainTextEdit, but the main window, and since events are always sent back to the parent if not processed, nothing else will happen.

A theoretical solution would be to call the base class implementation against the QPlainTextEdit widget instead:

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Return or event.key() == QtCore.Qt.Key_Enter:
            print("Enter pressed")
            self.plainTextEdit.appendPlainText("New Line: ")
        else:
            QtWidgets.QPlainTextEdit.keyPressEvent(self.plainTextEdit, event)

Note that I didn't call self.plainTextEdit.keyPressEvent(event), as it would cause a recursion.

This idea is not good anyway, because in this way you're overwriting the QMainWindow keyPressEvent too (which could be a problem if you need it, but that's not the point).

There are two possible (and more "elegant") solutions:

1. Subclass QPlainTextEdit and promote it in Designer

This method allows you to create your UI in designer and set basic parameters for a custom widget that you can extend with your own code; see this answer for an explanation about the procedure.

With this you can just do something like this:

class MyTextEdit(QtWidget.QPlainTextEdit):
    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Return or event.key() == QtCore.Qt.Key_Enter:
            print("Enter pressed")
            self.appendPlainText("New Line: ")
        else:
            super().keyPressEvent(event)

The extra advantage is that, in this way, the code is also more cleaner and easier to implement.

2. Install an event filter on the widget

An event filter is able to "intercept" any event a widget receives, and possibly react to it. In your case it could be something like this:

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, *args, **kwargs):
        QtWidgets.QMainWindow.__init__(self, *args, **kwargs)
        self.setupUi(self)
        self.plainTextEdit.installEventFilter(self)

    def eventFilter(self, source, event):
        if (event.type() == QtCore.QEvent.KeyPress and 
            event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return)):
                # ensure that the widget receives the key event
                super().eventFilter(source, event)
                print("Enter pressed")
                self.plainTextEdit.appendPlainText("New Line: ")
                # the event is accepted and not sent to its ancestors
                return True
        return super().eventFilter(source, event)
musicamante
  • 41,230
  • 6
  • 33
  • 58