如何在小部件失去焦点时进行拦截



我有一个QPlainTextEdit,希望在内容失去焦点时进行处理。我已经看到,我可以使用focusChanged事件或focusOutEvent虚拟函数来执行此操作。

我不知道如何用新语法传递参数(即my_app.focusChanged.connect(my_handler),其中my_handler是本地定义的函数)。所以我试着使用虚拟函数。

由于接口是用继承QPlainTextEdit的QT Designer创建的,所以我试图通过简单地使用my_text_edit.focusOutEvent = my_handler来覆盖虚拟函数。这确实按照我的意愿截取了消息,但它显然覆盖了QPlainTextEdit中的一些内置功能,我得到了伪影——即文本编辑中的光标在失去焦点时不会消失。我想我应该以某种方式称之为最初的事件,对我有效的是:

在我的__init__方法中,我有:

    self.original_handler = self.my_text_edit.focusOutEvent
    self.my_text_edit.focusOutEvent = self.my_handler

my_handler的定义是:

    def my_handler(self, event):
        self.original_handler(event)
        # my own handling follows...

我基本上是在复制我希望图书馆为我做的事情。我觉得这太笨拙了,我可以看到它在维护过程中会在很多方面适得其反。有人能提出一个更整洁的方法吗?谢谢

更新

下面的原始答案主要与PySide/PyQt4有关,并且部分已经过时,所以我现在添加了一个PySide2/PyQt5演示,它实现了事件处理程序、事件过滤器和全局信号。不过,关于小部件升级的最后一节仍然有效,如果您使用的是Qt Designer,则非常值得考虑。

PySide2/PyQt5

from PySide2 import QtCore, QtWidgets
# from PyQt5 import QtCore, QtWidgets
class PlainTextEdit(QtWidgets.QPlainTextEdit):
    def focusInEvent(self, event):
        print('event-focus-in:', self.objectName())
        super().focusInEvent(event)
    def focusOutEvent(self, event):
        print('event-focus-out:', self.objectName())
        super().focusOutEvent(event)
class Window(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.textEdit = PlainTextEdit(objectName='textEdit')
        self.dateEdit = QtWidgets.QDateEdit(objectName='dateEdit')
        self.button = QtWidgets.QPushButton('Test', objectName='button')
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.textEdit)
        layout.addWidget(self.dateEdit)
        layout.addWidget(self.button)
        self.dateEdit.installEventFilter(self)
        QtWidgets.QApplication.instance().focusObjectChanged.connect(
            self.handleFocusChange)
    def handleFocusChange(self, source):
        print(f'signal-focus-in:', source.objectName())
    def eventFilter(self, source, event):
        if event.type() == QtCore.QEvent.FocusIn:
            print('filter-focus-in:', source.objectName())
        elif event.type() == QtCore.QEvent.FocusOut:
            print('filter-focus-out:', source.objectName())
        return super().eventFilter(source, event)
if __name__ == '__main__':
    app = QtWidgets.QApplication(['Test'])
    window = Window()
    window.setGeometry(600, 100, 300, 200)
    window.show()
    app.exec_()

PySide/PyQt4

就我个人而言,我从不使用覆盖虚拟方法的猴子补丁风格,但保留原始行为的正确方法是直接调用基类方法,如下所示:

def my_handler(self, event):
    QtGui.QPlainTextEdit.focusOutEvent(self.my_text_edit, event)
    # my own handling follows...

我不明白你为什么不能使用focusChanged信号。它的处理程序很简单:

def my_handler(self, old, new):
    if old is self.my_text_edit:
        print('focus out')
    elif new is self.my_text_edit:
        print('focus in')

然而,我自己的偏好是使用事件过滤器:

class Window(QtGui.QMainWindow)
    def __init__(self):
        ...
        self.my_text_edit.installEventFilter(self)
    def eventFilter(self, source, event):
        if (event.type() == QtCore.QEvent.FocusOut and
            source is self.my_text_edit):
            print('eventFilter: focus out')
            # return true here to bypass default behaviour
        return super(Window, self).eventFilter(source, event)

这是一个更灵活的解决方案,它提供了一种通用的方法来处理通过Qt Designer导入的任何小部件的任何事件类型(或者,实际上,任何您不想子类化的小部件)。


小工具推广

还有小部件升级的可能性,它可以用来用自己的子类直接替换生成的ui模块中的小部件。这将允许您通过继承覆盖任何虚拟方法,而不是猴子补丁单个实例。如果您正在从Qt Designer导入几个相同类型的小部件,它们共享许多自定义功能,这可以提供一个非常干净的解决方案。

关于如何在PyQt中进行小部件升级的简单解释可以在我的回答中找到:在运行时替换QWidget对象。

相关内容

  • 没有找到相关文章

最新更新