我有一个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对象。