0

【Qt6】工具提示以及调色板设置 - 东邪独孤

 9 months ago
source link: https://www.cnblogs.com/tcjiaan/p/17644373.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【Qt6】工具提示以及调色板设置

工具提示即 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();
}

这是最基本的工具提示,没什么可折腾的,设置字符串就行。看看效果。

367389-20230820182950973-1774536334.png

下面说说另一种复杂些的方法。QToolTip 类公开一组静态成员,可以用于手动控制工具提示。例如,你可以:

showText:显示工具提示。这个用得较多;
hideText:隐藏工具提示。这个一般不需要调用,毕竟你鼠标移出目标组件的区域后就会自动隐藏。
下面咱们还是用例子说事。例子程序的窗口中水平躺了两个按钮,然后连接按钮的 clicked 信号,通过调用 showText 方法手动显示工具提示。
#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 调用显示提示。

注意,咱们这里是单击按钮后才会显示工具提示的,鼠标移上去是不会出现提示的。毕竟咱们手动触发了。

367389-20230821232220330-1189184708.png

要单击按钮之后才显示工具提示,明显这做法是不合理的,所以,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()

运行的效果如下面的超清动图所示。

367389-20230826101950499-1271745670.gif

最后部分咱们讨论一下工具提示的调色板问题。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();
}

代码是没有问题的,但很多同学可能遇到运行之后没有效果的问题。就像这样:

367389-20230826105230356-1169573596.gif

很多大伙伴们可能还找不到解决方案。其实这事是主题造成的,Windows 上默认使用 WindowsVista 主题,我们换一个主题就行了,比如,换成 Windows 主题。如果你不知道当前环境下支持哪些主题,可以调试输出一下 QStyleFactory::keys 方法的返回结果。

qDebug() << QStyleFactory::keys();

得到的输出如下:

QList("windowsvista", "Windows", "Fusion")

除了 Windowsvista,另外两个随便选,都可以。

咱们修改一下应用程序的主题,在 app 变量上调用。

QApplication app(argc, argv);
// 改变主题
app.setStyle("Fusion");

再运行一下看看,效果就有了。

367389-20230826110040631-282282779.gif

好了,今天的话题就聊到这儿了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK