有些情况下,我们需要实现不规则窗口,比如在软件的加载窗口,可以使用背景透明的图片填充窗口,从而创建炫酷的不规则窗口。下面介绍使用背景透明图片设计不规则窗口以及实现鼠标拖拽不规则窗口的方法。
首先,我们需要创建一个C++类(),这个类继承于QWidget(当然也可以使用设计师界面类,具体看个人需求),接下来的代码均是基于这个派生类。
1、设置窗口无边框且背景透明
首先,设置窗口的属性:
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
{
//设置窗口风格为无边框
this->setWindowFlag(Qt::FramelessWindowHint);
//设置背景为透明()
this->setAttribute(Qt::WA_TranslucentBackground);
}
2、使用背景透明图片填充窗口
首先,需要重写QPaintEvent()事件,用于绘制窗口背景,将其写在类的保护成员里:
//声明(mywidget.h中)
protected:
//绘图事件
void paintEvent(QPaintEvent*);
//定义(mywidget.cpp中)
void MyWidget::paintEvent(QPaintEvent* )
{
QPainter p(this);
p.drawPixmap(0,0,this->width(),this->height(),QPixmap("E:\image\sunny.png"));
}
其中,图片sunny.png是一张背景透明的图片而不是说图片的背景为白色。如下图所示:
而我们使用
QWidget::setAttribute(Qt::WA_TranslucentBackground);
设置窗口为背景透明则指的是窗口开启了阿尔法通道,关于Qt::WA_TranslucentBackground属性,QT文档的官方文档的说明如下:
Qt::WA_TranslucentBackground 120
Indicates that the widget should have a translucent background, i.e., any non-opaque regions of the widgets will be translucent because the widget will have an alpha channel. Setting this flag causes WA_NoSystemBackground to be set. On Windows the widget also needs the Qt::FramelessWindowHint window flag to be set. This flag is set or cleared by the widget’s author.
到目前为止,我们已经完成了不规则窗口的创建,运行程序可以看到,显示效果如下所示:
但是此时的窗口还无法拖动,接下来从数学上证明如何拖动窗口,并给出现相关代码。
3、鼠标按下拖拽不规则窗口证明
上面的窗口已经做到了无边框且背景透明,但是正是由于无边框,我们无法拖动也无法关闭它(除非在任务栏右键关闭),下面通过重写鼠标按下事件使得窗口可拖动。先说明证明窗口拖动的原理。
3.1 鼠标移动与鼠标追踪
鼠标追踪是相对于mouseMoveEvent()所属的父组件而言,比如我们在MyWidget中声明了一个鼠标移动事件MyWidget::mouseMoveEvent(),那么鼠标追踪就是指的当鼠标移动进此窗口时,是否会被追踪。
关闭鼠标追踪: 鼠标如果没有按下,则在MyWidget移动时不会触发鼠标点击事件(即不会调用mouseMoveEvent()),鼠标追踪是默认关闭的。
开启鼠标追踪: 鼠标只要在窗口内移动,就会触发鼠标移动事件。
开启方式是在窗口的构造函数中使QWidget::setMouseTracking()参数为true,即:
//关闭鼠标追踪
this->setMouseTracking(false);
//开启鼠标追踪
this->setMouseTracking(true);
如果开启了鼠标追踪,则只要鼠标在窗口内移动,就会触发mouseMoveEvent(),而我们希望只是在鼠标按下的时候才触发鼠标移动事件,因此我们选择将此窗口的鼠标追踪关闭:
//关闭鼠标追踪
this->setMouseTracking(false);
3.2 窗口移动原理以证明
对于桌面系统,其坐标系如下图所示:
上图中,绿色区域表示电脑桌面,蓝色区域表示被背景透明的图片填充的窗口,A点为鼠标按下的点,B点为鼠标按下并移动后的点(注意A点相对于窗口的位置是没有的变的,鼠标从A点移动到B指的是相对桌面从A点移动到B点)。
我们不设置鼠标追踪,保证只有在鼠标按下的时候移动才会触发鼠标移动事件。
●首先在鼠标按下事件mousePressEvent()中获取到鼠标按下位置相对于窗口左上角的位置A:QPoint mousePositon
●假设鼠标按下后移动到桌面B点,那么我们需要做的是将原窗口从相对桌面的A点移动到相对桌面的B点。而要移动窗口,需要一个函数:
QWidget::move(QPoint newPosition)
这个函数的作用是:以窗口的左上角顶点坐标为参考点,将窗口移动到newPositon点,move()函数使用的坐标系统是桌面坐标系,即全局坐标系。
所以我们需要将鼠标的位置量转换为窗口左上角顶点的位移。
下面做一个推导:
假设鼠标按下并从桌面A移动到桌面B点,则我们希望鼠标拖动从A移动到B时,窗口也能按相同的轨迹移动到B,我们用GB(global)代表全局坐标,用LC(local)代表局部坐标则,窗口左上角的全局坐标为O,则:
窗口移动到A点时左上角全局坐标:
(
X
W
1
G
B
,
Y
W
1
G
B
)
\left( X_{{W1}_{GB}}, Y_{{W1}_{GB}} \right)
(XW1GB,YW1GB)
点A的本地坐标:
(
x
A
L
C
,
y
A
L
C
)
\left( x_{A_{LC}}, y_{A_{LC}} \right)
(xALC,yALC)
点A的全局坐标为:
(
x
A
G
B
,
y
A
G
B
)
=
(
X
W
G
B
,
Y
W
G
B
)
+
(
x
A
L
C
,
y
A
L
C
)
=
(
X
W
G
B
+
x
A
L
C
,
Y
W
G
B
+
y
A
L
C
)
\left( {x_{A_{GB}},y_{A_{GB}}} \right) = \left( {X_{W_{GB}},Y_{W_{GB}}} \right) + \left( {x_{A_{LC}},y_{A_{LC}}} \right) = \left( {X_{W_{GB}} + x_{A_{LC}},Y_{W_{GB}} + y_{A_{LC}}} \right)
(xAGB,yAGB)=(XWGB,YWGB)+(xALC,yALC)=(XWGB+xALC,YWGB+yALC)
点B的全局坐标为:
(
x
B
G
B
,
y
B
G
B
)
\left( {x_{B_{GB}},y_{B_{GB}}} \right)
(xBGB,yBGB)
设窗口移动到B点时左上角全局坐标:
X
W
2
G
B
,
Y
W
2
G
B
)
\left. X_{{W2}_{GB}},Y_{{W2}_{GB}} \right)
XW2GB,YW2GB)
由于点B的本地坐标没有变,即为点A的本地坐标。
点B的本地坐标为:
(
x
A
L
C
,
y
A
L
C
)
\left( {x_{A_{LC}},y_{A_{LC}}} \right)
(xALC,yALC)
则点B的全局坐标可以表示为:
(
x
B
G
B
,
y
B
G
B
)
=
(
X
W
2
G
B
,
Y
W
2
G
B
)
+
(
x
A
L
C
,
y
A
L
C
)
\left( {x_{B_{GB}},y_{B_{GB}}} \right) = \left( {X_{{W2}_{GB}},Y_{{W2}_{GB}}} \right) + \left( {x_{A_{LC}},y_{A_{LC}}} \right)
(xBGB,yBGB)=(XW2GB,YW2GB)+(xALC,yALC)
从而,当窗口移动到点B时其左上角的坐标为:
(
X
W
2
G
B
,
Y
W
2
G
B
)
=
(
x
B
G
B
,
y
B
G
B
)
−
(
x
A
L
C
,
y
A
L
C
)
\left( {X_{{W2}_{GB}},Y_{{W2}_{GB}}} \right) = \left( {x_{B_{GB}},y_{B_{GB}}} \right) - \left( {x_{A_{LC}},y_{A_{LC}}} \right)
(XW2GB,YW2GB)=(xBGB,yBGB)−(xALC,yALC)
而这个
(
X
W
2
G
B
,
Y
W
2
G
B
)
\left( {X_{{W2}_{GB}},Y_{{W2}_{GB}}} \right)
(XW2GB,YW2GB) 正是我们move()函数需要的参数。
实际上,我们可以使用向量可以反映这个问题的本质,下面用向量来说明窗口移动原理:
其中,O(0,0)为电脑桌面零点,W1为鼠标在A点时的窗口左上角全局坐标,W2为鼠标在B点时的窗口左上角全局坐标,A为鼠标位于点A时的全局坐标,B为鼠标位于B点时全局坐标,则move()函数需要的量为W2的坐标,下满进行推导:
由于鼠标按下的点相对窗口的位置不变,所以W和A的相对位置不会变,即:
由于O点的坐标坐标为(0,0),W1点的局部坐标为(0,0),所以上式表明:
窗口新位置的全局坐标W2=鼠标新位置的全局坐标B – 鼠标原位置的全局坐标A
至此,已经说明了如何进行窗口移动。
4、完整代码
下面附上完整代码,供大家参考:
//mywidget.h 头文件
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(QWidget *parent = nullptr);
~MyWidget();
protected:
//绘图事件
void paintEvent(QPaintEvent*);
//鼠标按下事件
void mousePressEvent(QMouseEvent* e);
//鼠标移动事件
void mouseMoveEvent(QMouseEvent* e);
private:
QPoint mouseLocalPos;
};
#endif // MYWIDGET_H
//mywidget.cpp 源文件
#include "mywidget.h"
#include<QPainter>
#include<QPixmap>
#include<QMouseEvent>
#include<QDebug>
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
{
this->setWindowFlag(Qt::FramelessWindowHint);
//设置背景为透明
this->setAttribute(Qt::WA_TranslucentBackground);
//关闭鼠标追踪(默认为关闭)
this->setMouseTracking(false);
}
MyWidget::~MyWidget()
{
}
void MyWidget::paintEvent(QPaintEvent* )
{
QPainter p(this);
p.drawPixmap(0,0,this->width(),this->height(),QPixmap("E:/image/sunny.png"));
}
void MyWidget::mousePressEvent(QMouseEvent* e)
{
//如果是右键,关闭窗口
if(e->button()==Qt::RightButton)
{
this->close();
}
//如果是左键,拖动窗口
if(e->button()==Qt::LeftButton)
{
//求坐标差值
//鼠标相对窗口左上角的位置=当前坐标(相对屏幕)-窗口左上角坐标(相对屏幕)
mouseLocalPos=e->pos();
//或者
//mouseLocalPos=e->globalPos()-this->frameGeometry().topLeft();
qDebug()<<"点击本地坐标:("<<mouseLocalPos.x()<<","<<mouseLocalPos.y()<<")";
}
}
//鼠标追踪默认是关闭的,只有在鼠标按下且移动的时候才会触发鼠标移动事件
void MyWidget::mouseMoveEvent(QMouseEvent* e)
{
QPoint newLeftPos; //窗口移动距离
newLeftPos=e->globalPos()-mouseLocalPos;
this->move(newLeftPos);
qDebug()<<"窗口左上角全局坐标:("<<newLeftPos.x()<<","<<newLeftPos.y()<<")";
}
//main()函数
#include "mywidget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyWidget w;
w.show();
return a.exec();
}