Qt自定义一个简单的ToolTip提示框

实现过程

因为 QToolTip 自定义样式不大方便,而且半透明也没法设置,所以需要自定义。而且,Qt 中的顶层 widget 好像默认是不支持透明样式的,可以设置:

1
2
    setWindowFlags(Qt::FramelessWindowHint);
    setAttribute(Qt::WA_TranslucentBackground, true); //无边框才有效

这样顶层窗口是透明了,但是样式表又没效果了。虽然可以用 QStyleOption 获取到样式表设置的颜色等信息,然后在 paintEvent 中绘制,但是图片我不知道怎么获取 。索性就嵌套了两个 widget ,给里层的 widget 设置样式。

显示和隐藏我是过滤的 QEvent::Enter 和 QEvent::Leave 来进行操作:

1
2
3
4
5
6
7
8
9
10
11
        switch (event->type()) {
        case QEvent::Enter:
            //showTip(QCursor::pos());
            showTip(targetWidget);
            break;
        case QEvent::Leave:
            hideTip();
            break;
        default:
            break;
        }

目前的实现是相对 widget 固定位置 show 的,没有处理鼠标移动事件。

弹出的时候因为我是先计算的位置再 show ,可能大小还没计算出来,所以在 resizeEvent 中重新调用了计算位置的函数。

(目前没有条件测试多屏幕时弹出的位置,先不写了)

对于设置样式表,目前只能通过 qApp 或者直接给实例对象设置。

参考 Qt 源码:E:\Qt\qt-everywhere-src-5.15.0\qtbase\src\widgets\kernel\qtooltip.h

实现代码

实现效果

代码链接

github 链接(RToolTip 类):https://github.com/gongjianbo/RectangleComponent.git

主要代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#ifndef RTOOLTIP_H
#define RTOOLTIP_H

#include <QLabel>
#include <QBasicTimer>

/**
 * @brief 最简易的ToolTip
 * @note 这是顶层窗口不要设置parent
 * @details 顶层设置透明后,样式表失效了,所以我在里面套了一层label
 * 本来想外层也用QLabel,show时内层label把属性设置为外层的,感觉没必要
 */
class RToolTip : public QWidget
{
    Q_OBJECT
    //READ WRITE可以替换成MEMBER
    //默认显示为point的左上角,通过属性设置偏移,以右下角为起点,左减右加,上减下加
    //qss右移1px:qproperty-rightOffset:1;
    Q_PROPERTY(int rightOffset READ getRightOffset WRITE setRightOffset)
    //qss上移1px:qproperty-bottomOffset:"-1";
    Q_PROPERTY(int bottomOffset READ getBottomOffset WRITE setBottomOffset)
    Q_PROPERTY(QString text READ getText WRITE setText)
    Q_PROPERTY(Qt::Alignment alignment READ getAlignment WRITE setAlignment)
public:
    //独立的窗口不设置parent,样式表可用qApp设置
    explicit RToolTip(const QString &objectName=QString(),const QString text=QString());
    ~RToolTip();

    //右侧偏移
    int getRightOffset();
    void setRightOffset(int offset);
    //底部偏移
    int getBottomOffset();
    void setBottomOffset(int offset);

    //文本
    QString getText() const;
    void setText(const QString &text);
    //对齐方式
    Qt::Alignment getAlignment() const;
    void setAlignment(Qt::Alignment alignment);

    //设置锚定窗口,鼠标放上去时显示tooltip
    void anchorTarget(QWidget *target);
    //获取内层label对象
    const QLabel *label() const;
    //显示tip在widget的左上角
    void showTip(const QWidget *obj);
    //显示tip在点的左上角
    void showTip(const QPoint &rightBottom);
    //隐藏tip
    void hideTip();

protected:
    //过滤锚定窗口的enter和leave事件
    bool eventFilter(QObject *target, QEvent *event) override;
    void timerEvent(QTimerEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;

private:
    //外层设置背景透明后样式不生效,所以嵌套了一层
    QLabel *contentLabel;
    //默认显示为point的左上角,通过属性设置偏移
    //以右下角为起点,左减右加,上减下加
    int rightOffset = 0;
    int bottomOffset = 0;

    //锚定的窗口
    QWidget *targetWidget=nullptr;
    QPoint targetPoint;
    //show和hide延迟
    QBasicTimer showTimer;
    QBasicTimer hideTimer;
};

#endif // RTOOLTIP_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#include "RToolTip.h"

#include <QApplication>
#include <QDesktopWidget>
#include <QEvent>
#include <QTimerEvent>
#include <QResizeEvent>
//#include <QStyle>
#include <QHBoxLayout>
//#include <QDebug>

RToolTip::RToolTip(const QString &objectName, const QString text)
    : QWidget(nullptr)
    ,contentLabel(new QLabel(this))
{
    //把内层label添加到透明widget
    QHBoxLayout *layout = new QHBoxLayout(this);
    layout->setMargin(0);
    layout->setSpacing(0);
    layout->addWidget(contentLabel);

    contentLabel->setAlignment(Qt::AlignCenter);
    contentLabel->setText(text);

    setObjectName(objectName);

    //把widget设置为透明,样式表设置给label
    setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip);
    setAttribute(Qt::WA_TranslucentBackground, true);
    //默认hide
    setVisible(false);

    //qDebug()<<"new tooltip"<<objectName;
}

RToolTip::~RToolTip()
{
    //qDebug()<<"delete tooltip"<<objectName();
}

int RToolTip::getRightOffset()
{
    return rightOffset;
}

void RToolTip::setRightOffset(int offset)
{
    if(rightOffset!=offset){
        rightOffset=offset;
        //没有动态样式的处理,可自行添加
        //style()->unpolish(this);
        //style()->polish(this);
    }
}

int RToolTip::getBottomOffset()
{
    return bottomOffset;
}

void RToolTip::setBottomOffset(int offset)
{
    if(bottomOffset!=offset){
        bottomOffset=offset;
    }
}

QString RToolTip::getText() const
{
    return contentLabel->text();
}

void RToolTip::setText(const QString &text)
{
    contentLabel->setText(text);
}

Qt::Alignment RToolTip::getAlignment() const
{
    return contentLabel->alignment();
}

void RToolTip::setAlignment(Qt::Alignment alignment)
{
    contentLabel->setAlignment(alignment);
}

void RToolTip::anchorTarget(QWidget *target)
{
    if(target&&target!=targetWidget){
        if(targetWidget){
            targetWidget->removeEventFilter(this);
        }
        targetWidget=target;
        targetWidget->installEventFilter(this);
        targetWidget->setMouseTracking(true);
        //如果是随窗口关闭的,看不到析构的打印,难道此时事件循环已停止?
        connect(targetWidget,&QObject::destroyed,this,&QObject::deleteLater);
    }
}

const QLabel *RToolTip::label() const
{
    return contentLabel;
}

void RToolTip::showTip(const QWidget *obj)
{
    if(!obj)
        return;
    showTip(obj->mapToGlobal(QPoint(0, 0)));
}

void RToolTip::showTip(const QPoint &rightBottom)
{
    targetPoint=rightBottom;
    //move(rightBottom.x() - width() + rightOffset,
    //     rightBottom.y() - height() + bottomOffset);
    //直接用size+point得到的位置可能显示不全,这里计算下

    int rect_left=rightBottom.x()-width()+rightOffset;
    int rect_top=rightBottom.y()-height()+bottomOffset;
    if(rect_left<0)
        rect_left=0;
    if(rect_top<0)
        rect_top=0;
    //要考虑多个显示器情况,【待测试】
    //根据当前所在屏幕尺寸计算
    QDesktopWidget * desktop=QApplication::desktop();
    if(desktop){
        QRect desk_rect=desktop->screenGeometry(targetWidget?targetWidget:this);
        if(rect_left+width()>desk_rect.width())
            rect_left=desk_rect.width()-width();
        if(rect_top+height()>desk_rect.height())
            rect_top=desk_rect.height()-height();
    }

    move(rect_left,rect_top);
    if(!showTimer.isActive())
        showTimer.start(200,this);
}

void RToolTip::hideTip()
{
    if(!hideTimer.isActive())
        hideTimer.start(300,this);
}

bool RToolTip::eventFilter(QObject *target, QEvent *event)
{
    if(target==targetWidget){
        switch (event->type()) {
        case QEvent::Enter:
            //showTip(QCursor::pos());
            showTip(targetWidget);
            break;
        case QEvent::Leave:
            hideTip();
            break;
        default:
            break;
        }
    }
    return QWidget::eventFilter(target,event);
}

void RToolTip::timerEvent(QTimerEvent *event)
{
    if(event->timerId()==showTimer.timerId()) {
        showTimer.stop();
        //hideTimer.stop();
        if(!hideTimer.isActive()&&isHidden()){
            show();
        }
    }else if(event->timerId()==hideTimer.timerId()){
        showTimer.stop();
        hideTimer.stop();
        if(!isHidden()){
            hide();
        }
    }else{
        QWidget::timerEvent(event);
    }
}

void RToolTip::resizeEvent(QResizeEvent *event)
{
    //初次show的时候可能size可能还没计算好
    showTip(targetPoint);
    QWidget::resizeEvent(event);
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include "RToolTip.h"

#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    initAppStyleSheet();
    /*配合样式表设置tab背景*/
    ui->tabWidget->setAttribute(Qt::WA_StyledBackground);

    RToolTip *tipA=new RToolTip("tipA","第1个tip");
    tipA->anchorTarget(ui->btnToolTipA);
    connect(ui->btnToolTipA,&QPushButton::clicked,[=]{
        if(ui->btnToolTipB){
            ui->btnToolTipB->deleteLater();
            ui->btnToolTipB=nullptr;
        }
    });

    RToolTip *tipB=new RToolTip("tipB","第2个tip");
    tipB->anchorTarget(ui->btnToolTipB);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::initAppStyleSheet()
{
    qApp->setStyleSheet(R"(
                        .RToolTip#tipA{
                        min-width:100px;
                        max-width:100px;
                        min-height:30px;
                        max-height:30px;
                        }
                        .RToolTip#tipB{
                        qproperty-rightOffset:"20";
                        qproperty-bottomOffset:"3";
                        }
                        .RToolTip QLabel{
                        padding:10px 50px;
                        color:white;
                        border: 1px solid white;
                        background-color:rgb(20,50,90);
                        }
                        .RToolTip#tipA QLabel{
                        padding:0;
                        border-radius:5px;
                        background-color:rgba(250,170,0,150);
                        }
                        )");
}