Qt界面设计涉及很多控件,控件之间的信息联系除了“信号-槽”机制外,还有内在的事件传输,所以了解事件的传递关系是精通Qt的必经之路。
在这里,我就PyQt5的代码内容讲解一下:父子(内外层)控件之间的事件传递关系、事件过滤器的作用以及两个事件控制函数。
更加详细的说明请看其他博主的Qt版原文:https://blog.csdn.net/xiaoyink/article/details/79398953
-
事件
首先,我们得知道什么是Qt的事件。Qt中的事件主要有键盘触发事件、鼠标触发事件等关键的交互触发事件。我们可以通过重载控件中的事件触发函数,实现监控该事件的发生,并在事件发生后,立即执行必要的处理。比如下面提到的,对QLabel控件的鼠标按压事件触发函数mousePressEvent进行重载。 -
事件过滤器
事件过滤器的作用就是对一个控件的所有发生事件事先进行拦截,在执行自定义的预处理后,按需要选择是否把事件传递下去。在PyQt5中,一般是对一个QWidget父控件内装载着很多子控件的情况下使用事件过滤器,用于对事件产生的控件和事件进行判断,并执行不同的处理。事件过滤器函数是 eventFilter(self, a0: ‘QObject’, a1: ‘QEvent’),一般使用形式为eventFilter(self, obj, event),obj表示事件产生的控件,event表示事件类型。并且对需要过滤的控件,需要显式安装事件过滤器。如下代码所示:
class MyWidget(QWidget):
def __init__(self, *args, **kwargs):
super(MyWidget, self).__init__(*args, **kwargs)
self.setWindowTitle('eventTest')
self.resize(300, 300)
self.label = QLabel() # 建立一个标签
self.label.setFrameShape(QFrame.Box)
# 把标签装在外部widget上
layout = QHBoxLayout()
layout.addWidget(self.label)
self.setLayout(layout)
self.label.installEventFilter(self) # 给label控件安装事件过滤器
def eventFilter(self, obj, event): # 事件过滤器
print('here is eventFilter')
return False
运行结果:
当鼠标进入、移出label控件控件,或者在label上左键单击、右键单击、双击以及鼠标滚轮滑动时,都会触发对应事件,并通过过滤器函数。
- 事件在父子控件的传递关系
一般控件而言,事件的产生均先被父控件检测到,也就是说,如果有为子控件安装过滤器的话,事件都要经过过滤器再到达子控件的事件响应函数。并且,可以实现在过滤器中拦截事件:过滤器函数必须有返回值,返回值是bool类型。当过滤器函数返回True时,表示事件已经被处理掉,则事件不会再传递给子控件,相当于事件被拦截、被扼杀了;反之,返回False表示事件没有被处理,仍需要往下传递。下面用代码进行对比演示:
# 先对Label控件进行重载,为了展示出鼠标按压函数被执行
class MyLabel(QLabel):
def __init__(self, *args, **kwargs):
super(MyLabel, self).__init__(*args, **kwargs)
def mousePressEvent(self, event): # 重载鼠标按压函数
print('label had been pressed...')
class MyWidget(QWidget):
def __init__(self, *args, **kwargs):
super(MyWidget, self).__init__(*args, **kwargs)
self.setWindowTitle('eventTest')
self.resize(300, 300)
self.label = MyLabel('label') # 建立一个标签
self.label.setFrameShape(QFrame.Box) # 设置外框
# 把标签装在外部widget上
layout = QHBoxLayout()
layout.addWidget(self.label)
self.setLayout(layout)
self.label.installEventFilter(self) # 给label控件安装事件过滤器
def eventFilter(self, obj, event): # 事件过滤器
if obj == self.label and event.type() == QEvent.MouseButtonPress: # 判断控件和事件类型
print('\nhere is eventFilter--------MouseButtonPress')
return True # 注意这里。。。。
return False
下面是我多次鼠标单击label的结果,说明事件经过了过滤器:
然后修改一下过滤器函数的内容,把return True那语句删掉(注释掉):
def eventFilter(self, obj, event): # 事件过滤器
if obj == self.label and event.type() == QEvent.MouseButtonPress:
print('\nhere is eventFilter--------MouseButtonPress')
# return True # 注意这里。。。。
return False
多次单击鼠标,发现多了一行输出,此行输出来自上面重载的MyLabel中的鼠标按压触发函数。并且可以看出代码执行的顺序为:先经过过滤器,然后到达子控件内的事件触发函数。
对比上面两个不同的结果,可以很好的说明两点:
1) 子控件事件先被父控件检测到,然后再传递给子控件
2) 在过滤器返回True后,事件结束,不在往下传递;返回False,则继续往下传。
- 事件的两个控制函数
# event.accept() 表示事件已处理,不再继续传递事件,把事件拦截了
# event.ignore() 表示事件仍需进行下一步处理,对事件保留
event.accept()用于拦截事件,event.ignore()用于把事件保留,一般用于在重载子控件中的事件触发函数后,把事件再次抛出,让父控件也可以接受到。
# 先对Label控件进行重载,为了展示出鼠标按压函数被执行
class MyLabel(QLabel):
def __init__(self, *args, **kwargs):
super(MyLabel, self).__init__(*args, **kwargs)
def mousePressEvent(self, event): # 重载鼠标按压函数
print('label had been pressed...')
event.ignore() # 如果没有这语句,则事件在此函数执行完成后消失
class MyWidget(QWidget):
def __init__(self, *args, **kwargs):
super(MyWidget, self).__init__(*args, **kwargs)
self.setWindowTitle('eventTest')
self.resize(300, 300)
self.label = MyLabel('label') # 建立一个标签
self.label.setFrameShape(QFrame.Box) # 设置外框
# 把标签装在外部widget上
layout = QHBoxLayout()
layout.addWidget(self.label)
self.setLayout(layout)
self.label.installEventFilter(self) # 给label控件安装事件过滤器
# ------------此处增加父控件的鼠标按压重载函数----------------------
def mousePressEvent(self, event):
print('here is widget...')
def eventFilter(self, obj, event): # 事件过滤器
if obj == self.label and event.type() == QEvent.MouseButtonPress:
print('\nhere is eventFilter--------MouseButtonPress')
# return True # 注意这里。。。。
return False
然后运行结果如下,表明了事件经过子控件,又传回了父控件,触发了父控件的事件触发函数:
那可能就有的伙伴就有疑问了:又说事件一开始是被父控件检测到的,为什么一开始不会触发父控件的事件触发函数呢?
这是因为鼠标单击是单击在label(子控件)上的,单击对象是label(子控件),这个事件是被label(子控件)捕捉到,应该归label(子控件)先处理,父控件只是检测到了label(子控件)捕捉到了一个事件但不是归它处理,所以要等子控件先处理此事件。重载子控件的事件处理函数可以自定义是否把事件继续回传给父控件(不加event.ignore()语句就是不回传);没有对子控件的事件处理函数重载的话,默认会回传给父控件。