[toc]

Qt 自定义气泡

效果

自定义气泡

实现逻辑

1. 绘制弹出的气泡

弹出气泡的主要部分已经用ui文件生成了,剩下的就是气泡的三角区域,也是比较难的一个部分

1.1 ui文件代码:

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
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>qFloatWidget</class>
<widget class="QWidget" name="qFloatWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>191</width>
<height>105</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>2</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>1</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

1.2绘制三角区域代码

三角区域的位置可以根据自己的需求修改。

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
QPainter painter(this);
QPainterPath drawPath;

painter.setRenderHint(QPainter::Antialiasing, true);
// painter.setPen(QPen(Qt::blue, 1));
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::white);

// 小三角区域;
QPolygon trianglePolygon;

QRect myRect(ui->stackedWidget->x(), ui->stackedWidget->y(), ui->stackedWidget->width(), ui->stackedWidget->height());

// 设置小三的具体位置
int tri_pos_x, tri_pos_y;

m_offset = ui->stackedWidget->width() / 2 - m_triangleWidth / 2;
switch (derect)
{
case up:{
// 小三角左边的点的位置
tri_pos_x = myRect.x() + m_offset;
tri_pos_y = myRect.y();
trianglePolygon << QPoint(tri_pos_x, tri_pos_y); // 小三角起点
trianglePolygon << QPoint(tri_pos_x + m_triangleWidth, tri_pos_y);
trianglePolygon << QPoint(tri_pos_x + m_triangleWidth / 2, tri_pos_y - m_triangleHeight);
}
break;
case left:{
// 小三上边点的位置
tri_pos_x = myRect.x();
tri_pos_y = myRect.y() + m_offset;
trianglePolygon << QPoint(tri_pos_x, tri_pos_y);
trianglePolygon << QPoint(tri_pos_x - m_triangleHeight, tri_pos_y + m_triangleWidth / 2);
trianglePolygon << QPoint(tri_pos_x, tri_pos_y + m_triangleWidth);
}
break;
case right:{
// 小三上边点的位置
tri_pos_x = myRect.x() + myRect.width();
tri_pos_y = myRect.y() + m_offset;
trianglePolygon << QPoint(tri_pos_x, tri_pos_y);
trianglePolygon << QPoint(tri_pos_x + m_triangleHeight, tri_pos_y + m_triangleWidth / 2);
trianglePolygon << QPoint(tri_pos_x, tri_pos_y + m_triangleWidth);
}

break;
case down:{
// 小三左边点的位置
tri_pos_x = myRect.x() + m_offset;
tri_pos_y = myRect.y() + myRect.height();
trianglePolygon << QPoint(tri_pos_x, tri_pos_y);
trianglePolygon << QPoint(tri_pos_x + m_triangleWidth / 2, tri_pos_y + m_triangleHeight);
trianglePolygon << QPoint(tri_pos_x + m_triangleWidth, tri_pos_y);
}
break;
default:
break;
}
drawPath.addRoundedRect(myRect, BORDER_RADIUS, BORDER_RADIUS);
drawPath.addPolygon(trianglePolygon);
painter.drawPath(drawPath);

以上就是主要的绘图事件,创建了一个带三角的气泡界面。接下来就是在主界面去响应了。

2. 鼠标事件

鼠标时间还是用到了QLabel的过滤器,监听 QEvent::EnterQEvent::Leave 两个事件。具体代码如下所示:

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

bool Widget::eventFilter(QObject *obj, QEvent *event)
{
QString str = QString("<style> span {text-decoration: none; color: #006FFF;font-family: Microsoft YaHei;}</style> <span>连接%1</span> <br> 虚拟地址:%2<br>登录地址:%3").arg("已成功").arg("192.168.0.0").arg("192.168.0.1");

static int x = 0;
static int y = 0;
static uint8_t flag=0;
if(obj == ui->label)
{
if(event->type() == QEvent::Enter)
{
ui->label->setText("进入");
QPoint GlobalPoint(ui->label->mapToGlobal(QPoint(0, 0)));//获取控件在窗体中的坐标
x = GlobalPoint.x();
y = GlobalPoint.y() + ui->label->height();

qDebug() << x << ":" << y ;
m_widget->myMove(x, y);
m_widget->setDerection(qFloatWidget::up);
m_widget->show();
}
else if(event->type() == QEvent::Leave)
{
ui->label->setText(("离开"));
m_widget->hide();
}
}

return QWidget::eventFilter(obj,event);
}

3. 动画

动画内容比较简单,只需要创建一个简单的位移动画就可以了,当然也可以去掉动画,直接让气泡弹出。

1
2
3
4
5
6
7
8
// 添加动画
// 位移
QPropertyAnimation *pPosAnimation1 = new QPropertyAnimation(m_widget, "pos");
pPosAnimation1->setDuration(1000);
pPosAnimation1->setStartValue(QCursor::pos());
pPosAnimation1->setEndValue(QPoint(x,y));
pPosAnimation1->setEasingCurve(QEasingCurve::InOutQuad);
pPosAnimation1->start();

完整代码

QFloatWidget.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
#ifndef QFLOATWIDGET_H
#define QFLOATWIDGET_H

#include <QWidget>

#if defined(_MSC_VER) && (_MSC_VER >= 1600)
#pragma execution_character_set("utf-8")
#endif

const int SHADOW_WIDTH = 30; // 窗口阴影宽度;
const int TRIANGLE_WIDTH = 30; // 小三角的宽度;
const int TRIANGLE_HEIGHT = 10; // 小三角的高度;
const int BORDER_RADIUS = 15; // 窗口边角的弧度;


namespace Ui {
class qFloatWidget;
}

class qFloatWidget : public QWidget
{
Q_OBJECT

public:
qFloatWidget(QWidget *parent = nullptr);
~qFloatWidget();

enum Derection{
left,
right,
up,
down
};

// 设置小三角起始位置;
void setStartPos(int startX);
// 设置小三角宽和高;
void setTriangleInfo(int width, int height);
// 设置小三角的位置
void setDerection(Derection d);
// 比起左上角的位置 用户更关心小三角的尖尖的位置 重载move以便用户更容易定位气泡框的位置
// x,y 是气泡窗口小贱贱的坐标
void myMove(int x, int y);

void setWidgetIndex(int i);

protected:
void paintEvent(QPaintEvent *);

private:
// 小三角的偏移量;
int m_offset;
// 小三角的宽度;
int m_triangleWidth;
// 小三角高度;
int m_triangleHeight;
Derection derect;

Ui::qFloatWidget *ui;
};

#endif // QFLOATWIDGET_H

QFloatWidget.cpp

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
#include "qfloatwidget.h"
#include "ui_qfloatwidget.h"

#include <QGraphicsDropShadowEffect>
#include <QHBoxLayout>
#include <QPoint>
#include <QPainter>
#include <QImage>
#include <QVariant>
#include <QPropertyAnimation>

qFloatWidget::qFloatWidget(QWidget *parent) :
QWidget(parent),
m_offset(50),
m_triangleWidth(TRIANGLE_WIDTH),
m_triangleHeight(TRIANGLE_HEIGHT),
ui(new Ui::qFloatWidget)
{
ui->setupUi(this);
setWindowFlags(Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
setAttribute(Qt::WA_NoSystemBackground);

//设置具体阴影
QGraphicsDropShadowEffect *shadow_effect = new QGraphicsDropShadowEffect(this);
shadow_effect->setOffset(0, 0);
shadow_effect->setColor(QColor(0, 133, 255));
shadow_effect->setBlurRadius(5);
this->setGraphicsEffect(shadow_effect);


}

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

void qFloatWidget::setStartPos(int startX)
{
m_offset = startX;
repaint();
}

void qFloatWidget::setTriangleInfo(int width, int height)
{
m_triangleWidth = width;
m_triangleHeight = height;
}

void qFloatWidget::setDerection(Derection d)
{
derect = d;
}

void qFloatWidget::myMove(int x, int y)
{
int top_left_x, top_left_y;
switch (derect) {
case down:
top_left_x = x - m_offset - m_triangleWidth / 2 - ui->stackedWidget->x();
top_left_y = y - m_triangleHeight - ui->stackedWidget->height() - ui->stackedWidget->y();
move(QPoint(top_left_x, top_left_y));
break;
case up:
top_left_x = x - m_offset - m_triangleWidth / 2 - ui->stackedWidget->x();
top_left_y = y + m_triangleHeight - ui->stackedWidget->y();
move(QPoint(top_left_x, top_left_y));
break;
case left:
top_left_x = x + m_triangleHeight - ui->stackedWidget->x();
top_left_y = y - m_offset - m_triangleWidth / 2 - ui->stackedWidget->y();
move(QPoint(top_left_x, top_left_y));
break;
case right:
top_left_x = x - m_triangleHeight - ui->stackedWidget->width() - ui->stackedWidget->x();
top_left_y = y - m_triangleWidth / 2 - m_offset - ui->stackedWidget->y();
move(QPoint(top_left_x, top_left_y));
break;
default:
break;
}
}

void qFloatWidget::setWidgetIndex(int i)
{
ui->stackedWidget->setCurrentIndex(i);
}

void qFloatWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPainterPath drawPath;

painter.setRenderHint(QPainter::Antialiasing, true);
// painter.setPen(QPen(Qt::blue, 1));
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::white);

// 小三角区域;
QPolygon trianglePolygon;

QRect myRect(ui->stackedWidget->x(), ui->stackedWidget->y(), ui->stackedWidget->width(), ui->stackedWidget->height());

// 设置小三的具体位置
int tri_pos_x, tri_pos_y;

m_offset = ui->stackedWidget->width() / 2 - m_triangleWidth / 2;
switch (derect)
{
case up:{
// 小三角左边的点的位置
tri_pos_x = myRect.x() + m_offset;
tri_pos_y = myRect.y();
trianglePolygon << QPoint(tri_pos_x, tri_pos_y); // 小三角起点
trianglePolygon << QPoint(tri_pos_x + m_triangleWidth, tri_pos_y);
trianglePolygon << QPoint(tri_pos_x + m_triangleWidth / 2, tri_pos_y - m_triangleHeight);
}
break;
case left:{
// 小三上边点的位置
tri_pos_x = myRect.x();
tri_pos_y = myRect.y() + m_offset;
trianglePolygon << QPoint(tri_pos_x, tri_pos_y);
trianglePolygon << QPoint(tri_pos_x - m_triangleHeight, tri_pos_y + m_triangleWidth / 2);
trianglePolygon << QPoint(tri_pos_x, tri_pos_y + m_triangleWidth);
}
break;
case right:{
// 小三上边点的位置
tri_pos_x = myRect.x() + myRect.width();
tri_pos_y = myRect.y() + m_offset;
trianglePolygon << QPoint(tri_pos_x, tri_pos_y);
trianglePolygon << QPoint(tri_pos_x + m_triangleHeight, tri_pos_y + m_triangleWidth / 2);
trianglePolygon << QPoint(tri_pos_x, tri_pos_y + m_triangleWidth);
}

break;
case down:{
// 小三左边点的位置
tri_pos_x = myRect.x() + m_offset;
tri_pos_y = myRect.y() + myRect.height();
trianglePolygon << QPoint(tri_pos_x, tri_pos_y);
trianglePolygon << QPoint(tri_pos_x + m_triangleWidth / 2, tri_pos_y + m_triangleHeight);
trianglePolygon << QPoint(tri_pos_x + m_triangleWidth, tri_pos_y);
}
break;
default:
break;
}
drawPath.addRoundedRect(myRect, BORDER_RADIUS, BORDER_RADIUS);
drawPath.addPolygon(trianglePolygon);
painter.drawPath(drawPath);
}

QFloatWidget.ui

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
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>qFloatWidget</class>
<widget class="QWidget" name="qFloatWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>191</width>
<height>105</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>2</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>1</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

widget.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
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QMouseEvent>

#include "qmylabel.h"
#include "qfloatwidget.h"

#if defined(_MSC_VER) && (_MSC_VER >= 1600)
#pragma execution_character_set("utf-8")
#endif

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
Q_OBJECT

public:
Widget(QWidget *parent = nullptr);
~Widget();

protected:
bool eventFilter(QObject *obj, QEvent *event);

private:
Ui::Widget *ui;
qFloatWidget *m_widget;
};
#endif // WIDGET_H

widget.cpp

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
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include<QPropertyAnimation>

#include <QGraphicsEffect>

Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
m_widget = new qFloatWidget();
ui->label->installEventFilter(this);
}

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


bool Widget::eventFilter(QObject *obj, QEvent *event)
{
QString str = QString("<style> span {text-decoration: none; color: #006FFF;font-family: Microsoft YaHei;}</style> <span>连接%1</span> <br> 虚拟地址:%2<br>登录地址:%3").arg("已成功").arg("192.168.0.0").arg("192.168.0.1");

static int x = 0;
static int y = 0;
static uint8_t flag=0;
if(obj == ui->label)
{
if(event->type() == QEvent::Enter)
{
ui->label->setText("进入");
QPoint GlobalPoint(ui->label->mapToGlobal(QPoint(0, 0)));//获取控件在窗体中的坐标
x = GlobalPoint.x();
y = GlobalPoint.y() + ui->label->height();

qDebug() << x << ":" << y ;
m_widget->myMove(x, y);
m_widget->setDerection(qFloatWidget::up);
m_widget->show();
}
else if(event->type() == QEvent::Leave)
{
ui->label->setText(("离开"));
m_widget->hide();
}
}

// 添加动画
// 位移
QPropertyAnimation *pPosAnimation1 = new QPropertyAnimation(m_widget, "pos");
pPosAnimation1->setDuration(1000);
pPosAnimation1->setStartValue(QCursor::pos());
pPosAnimation1->setEndValue(QPoint(x,y));
pPosAnimation1->setEasingCurve(QEasingCurve::InOutQuad);
pPosAnimation1->start();
return QWidget::eventFilter(obj,event);
}

widget.ui

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
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>497</width>
<height>252</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>123123123123</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>