工具提示即 Tool Tip,当用户把鼠标移动到某个UI对象上并悬停片刻,就会出现一个“短小精悍”的窗口,显示一些说明性文本。一般就是功能描述,让用户知道这个XX是干啥用的。
在 Qt 中使用工具提示最方便的做法是直接用 QWidget 类的成员方法:setToolTip。从 QWidget 类派生的组件都可以用,直接设置文本内容即可,用起来很 666。
下面举个例子:示例窗口上有三个单选按钮(QRadioButton),分别为它们设置工具提示。
#include <qapplication.h> #include <qwidget.h> #include <qboxlayout.h> #include <qradiobutton.h> #include <qlabel.h> int main(int argc, char* argv[]) { QApplication app(argc, argv); // 窗口 QWidget *window = new QWidget; // 大小和标题 window->setWindowTitle("2023劲爆游戏盲盒"); window->resize(300, 280); // 布局 QVBoxLayout* layout = new QVBoxLayout; window->setLayout(layout); // 在布局内放些组件 // 1、标签 QLabel *lbTxt = new QLabel(window); lbTxt->setText("请选择一个分组开箱,祝您好运。"); layout->addWidget(lbTxt); // 2、三个单选按钮 QRadioButton* opt1=new QRadioButton("霸王组", window); QRadioButton *opt2 = new QRadioButton("受虐组", window); QRadioButton *opt3 = new QRadioButton("狗粮组", window); layout->addWidget(opt1); layout->addWidget(opt2); layout->addWidget(opt3); layout->addStretch(1); // 给三个单选按钮设置工具提示 opt1->setToolTip("霸王组的游戏玩得特别有自信,玩家的战斗力最大化,对手的战斗力弱得像小鸡一样,一打一个爽,一直打一直爽。"); opt2->setToolTip("受虐组的游戏玩起来比较惨,玩家的战斗力被严重削弱,对手的力量无比强大,基本被按在水泥地板上摩擦。该组游戏难度大到吓死人。"); opt3->setToolTip("狗粮组游戏比较温馨,玩家可以自由组CP,然后拼命发狗粮。哪一组CP被别人发的糖给甜死了就算出局。"); // 显示窗口 window->showNormal(); return QApplication::exec(); }
这是最基本的工具提示,没什么可折腾的,设置字符串就行。看看效果。
下面说说另一种复杂些的方法。QToolTip 类公开一组静态成员,可以用于手动控制工具提示。例如,你可以:
#include <QApplication> #include <QWidget> #include <QToolTip> #include <QPushButton> #include <QHBoxLayout> class MyWindow : public QWidget { public: MyWindow(); private: QHBoxLayout* layout; QPushButton* btnA; QPushButton* btnB; // 两个slots void onAClicked(); void onBClicked(); }; /***************************************************/ MyWindow::MyWindow() :QWidget::QWidget(nullptr) { // 初始化布局 layout = new QHBoxLayout; setLayout(layout); // 弄两个按钮 btnA = new QPushButton("按钮 1", this); btnB = new QPushButton("按钮 2", this); layout->addWidget(btnA, 0, Qt::AlignTop); layout->addWidget(btnB, 0, Qt::AlignTop); // 连接一下clicked信号 connect(btnA, &QPushButton::clicked, this, &MyWindow::onAClicked); connect(btnB, &QPushButton::clicked, this, &MyWindow::onBClicked); }
由于我这个类没有明确声明信号等东东,所以不加 Q_OBJECT 宏也没问题。如果加 Q_OBJECT 宏,这个类的定义最好放头文件里,这样就不必手动执行 MOC。
接下来咱们重点实现响应 clicked 信号的那两个方法。
void MyWindow::onAClicked() { // 获取按钮的矩形区域坐标 QPushButton* _b = static_cast<QPushButton*>(sender()); QRect rect = _b->rect(); // 左下角坐标 QPoint lbpoint = rect.bottomLeft(); // 转换为全局坐标 QPoint gpos = _b->mapToGlobal(lbpoint); // 手动显示提示信息 QToolTip::showText(gpos, "单击它,实现梦想"); } void MyWindow::onBClicked() { auto b = static_cast<QPushButton*>(sender()); // 获取按钮的矩形区域 QRect rect = b->rect(); // 左下角 QPoint p1 = rect.bottomLeft(); // 映射全局坐标 p1 = b->mapToGlobal(p1); // 显示工具提示 QToolTip::showText(p1, "单击它,放弃梦想"); }
两个方法的逻辑是一样的。首先通过 sender() 方法可以知道正在发送信号的对象,这里就是按钮。然后得到按钮的矩形区域的左下角坐标。对的,我就是想让工具提示显示在按钮左下角。由于工具提示实际上是一个无边框的顶层窗口(内部用 QLabel 实现的),所以要用相对于屏幕的坐标,调用 mapToGlobal 方法可以将相对坐标(按钮相对于程序窗口)转换为屏幕坐标。
最后,一句 showText 调用显示提示。
注意,咱们这里是单击按钮后才会显示工具提示的,鼠标移上去是不会出现提示的。毕竟咱们手动触发了。
要单击按钮之后才显示工具提示,明显这做法是不合理的,所以,QToolTip 类更合理的用法是 ToolTip 事件。对于自定义的组件类,可以重写 event 方法,然后分析事件类型是否为 ToolTip,如果是就用 QToolTip::showText 显示工具提示。不过这种做法还是不怎么好用,总不能只为了一个工具提示就把常用的组件都派生一遍吧。所以,比较好的方法是使用【事件过滤器】,被过滤者(窗口中用到的各组件)安装过滤器;而当前窗口类重写 eventFilter 方法,使自己变成事件过滤器。这样,在窗口类中就可以拦截各个组件的事件并做出处理了。
下面举例老周用 Python 来写,只是换种语言换个口味而已,知识点是不变的哟。
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 方法。
eventFilter 方法的声明如下:
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()
运行的效果如下面的超清动图所示。
最后部分咱们讨论一下工具提示的调色板问题。QToolTip 允许以调色板的方式设置提示的背景色和前景色(文本颜色)。直接看例子:
int main(int argc, char* argv[]) { QApplication app(argc, argv);// 获取工具提示的现有调色板 QPalette pal = QToolTip::palette(); // 修改背景色 pal.setColor( QPalette::Inactive, QPalette::ToolTipBase, QColor("darkblue") ); // 修改文本颜色 pal.setColor( QPalette::Inactive, QPalette::ToolTipText, QColor("lightyellow") ); // 重新设置调色板 QToolTip::setPalette(pal); // 窗口 QWidget* window = new QWidget; window->setWindowTitle("自定义ToolTip颜色"); window->resize(250, 100); // 布局 QHBoxLayout *layout = new QHBoxLayout; window->setLayout(layout); // 两个按钮 QPushButton *btn1, *btn2; btn1 = new QPushButton("回到过去", window); btn2 = new QPushButton("看看未来", window); layout->addWidget(btn1); layout->addWidget(btn2); // 为按钮设置提示 btn1->setToolTip("一键触发穿越之旅"); btn2->setToolTip("一键改变人生"); // 显示窗口 window->show(); // 进入主循环 return QApplication::exec(); }
代码是没有问题的,但很多同学可能遇到运行之后没有效果的问题。就像这样:
很多大伙伴们可能还找不到解决方案。其实这事是主题造成的,Windows 上默认使用 WindowsVista 主题,我们换一个主题就行了,比如,换成 Windows 主题。如果你不知道当前环境下支持哪些主题,可以调试输出一下 QStyleFactory::keys 方法的返回结果。
qDebug() << QStyleFactory::keys();
得到的输出如下:
QList("windowsvista", "Windows", "Fusion")
除了 Windowsvista,另外两个随便选,都可以。
咱们修改一下应用程序的主题,在 app 变量上调用。
QApplication app(argc, argv); // 改变主题 app.setStyle("Fusion");
再运行一下看看,效果就有了。
好了,今天的话题就聊到这儿了。