两个方法的逻辑是一样的。首先通过 sender() 方法可以知道正在发送信号的对象,这里就是按钮。然后得到按钮的矩形区域的左下角坐标。对的,我就是想让工具提示显示在按钮左下角。由于工具提示实际上是一个无边框的顶层窗口(内部用 QLabel 实现的),所以要用相对于屏幕的坐标,调用 mapToGlobal 方法可以将相对坐标(按钮相对于程序窗口)转换为屏幕坐标。
要单击按钮之后才显示工具提示,明显这做法是不合理的,所以,QToolTip 类更合理的用法是 ToolTip 事件。对于自定义的组件类,可以重写 event 方法,然后分析事件类型是否为 ToolTip,如果是就用 QToolTip::showText 显示工具提示。不过这种做法还是不怎么好用,总不能只为了一个工具提示就把常用的组件都派生一遍吧。所以,比较好的方法是使用【事件过滤器】,被过滤者(窗口中用到的各组件)安装过滤器;而当前窗口类重写 eventFilter 方法,使自己变成事件过滤器。这样,在窗口类中就可以拦截各个组件的事件并做出处理了。
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
# 自定义窗口类
class MyWindow(QWidget):
def __init__(self):
# 调用基类的构造函数
super().__init__()
# 设置布局
layout = QFormLayout()
self.setLayout(layout)
# 表单的第一行
self.txtName = QLineEdit(self)
layout.addRow("用户名:", self.txtName)
# 表单的第二行
self.txtPass = QLineEdit(self)
layout.addRow("密码:", self.txtPass)
# 这个输入框需要显示掩码
self.txtPass.setEchoMode(QLineEdit.EchoMode.Password)
# 表单的第三行,CheckBox控件
self.ckbRemember = QCheckBox(self)
self.ckbRemember.setText("记住我")
layout.addRow(self.ckbRemember)
# 表单第四行,两个按钮
# 这两个按钮需要子布局,让它们躺平
btnLayout = QHBoxLayout()
self.btnLog = QPushButton("确定", self)
btnLayout.addWidget(self.btnLog)
self.btnExit = QPushButton("退出", self)
btnLayout.addWidget(self.btnExit)
layout.addRow(btnLayout)
# 为需要的组件安装事件过滤器
self.txtName.installEventFilter(self)
self.txtPass.installEventFilter(self)
self.btnLog.installEventFilter(self)
self.btnExit.installEventFilter(self)
self.ckbRemember.installEventFilter(self)
# 当前类重写这个方法,成为事件过滤器
def eventFilter(self, watched: QObject, event: QEvent) -> bool:
# 判断事件类型
if event.type() == QEvent.Type.ToolTip:
helpevent: QHelpEvent = event
# 给各组件设置工具提示
tip = 'what the Fxxk'
if watched is self.txtName:
tip = '请输入你的大名'
if watched is self.txtPass:
tip = '请输入密码'
if watched is self.ckbRemember:
tip = '选中这个后,下次登录不用再输密码了'
if watched is self.btnLog:
tip = '点这里,确认登录'
if watched is self.btnExit:
tip = '不登录了,直接退出'
# 显示工具提示
QToolTip.showText(
helpevent.globalPos(), # 当前鼠标的全局坐标
tip
)
# 调用基类成员
return super().eventFilter(watched, event)
窗口使用 QFormLayout 布局,表单,类似 HTML Form 的布局。两个 QLineEdit 组件,用来输入用户名和密码;两 QPushButton 组件,即普通按钮;还有一个复选框 QCheckBox。
依次调用这些组件的 installEventFilter 方法,安装过滤器,方法参数就是对过滤器实例的引用。在 MyWindow 类中,它重写了 eventFilter 方法,说明 MyWindow 类自身已经成为事件过滤器了。所以当前实例 self 可传递给 installEventFilter 方法。
bool QObject::eventFilter(QObject *watched, QEvent *event)
watched 参数引用的就是被拦截的对象。比如,如果 btnLog 的 ToolTip 事件被拦截,那么 watched 参数引用的就是 btnLog。event 参数是 QEvent 类的派生类,提供与事件有关的数据,不同事件下它的类型不同。对于 ToolTip 事件,其类型是 QHelpEvent。该类提供了显示工具提示所需的全局坐标。
对于 ToolTip 事件,相关的事件类是 QHelpEvent。可以从它的 globalPos 方法获得当前鼠标的屏幕坐标。调用 showText 方法时,第一个参数就是传递工具提示应该出现的位置。当然,这是全局坐标。第二个参数就是提示的文本,剩下的参数可以忽略。
if __name__ == "__main__":
# 初始化应用程序
app = QApplication()
# 初始化窗口
win = MyWindow()
win.setWindowTitle("试玩")
win.resize(240, 130)
win.show()
# 进入主循环
QApplication.exec()