好久没来了更新了,今天使用Qt做了个异形窗口,这里记录下,效果如下。
老规矩,开发环境:
System:win10x64
Qt version:Qt 5.14.0
编译器:MINGW_64
Author : 点缀
首先创建一个QWidget的项目,类名自己随便写,基类是继承QWidget,因为窗口是四方的,我们需要把窗口设置成图片的形状。代码如下:
QPixmap pixmap("../res/t6.jpg",nullptr,Qt::AvoidDither|Qt::ThresholdDither|Qt::ThresholdAlphaDither);
resize(pixmap.size());
setMask(pixmap.mask());
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint);
首先在构造函数里,将图片加载进QPixmap,resize函数将窗口大小设置成图片大小。然后利用setMask函数设置窗口的背景掩码,最后一步很重要,就是设置下窗口的属性:无边框,无标题栏,无最大最小化按钮。
到了这里,窗口就能变成你选择的图片的样子了,但是还没结束,我们还要做三点:一、因为我们取消了最大最小化按钮,所以窗口现在关不了了,所以我们得写下右键弹出菜单。二、让图片动起来(也可以不动,但我选择的是一个球体,动起来比较好看一点吧,哈哈)。
三、能移动窗口
一、添加右键菜单
这个功能很简单,直接重写右键菜单函数:
void contextMenuEvent(QContextMenuEvent *event) override;
先设置私有成员变量:
QAction* act_start = nullptr;
QAction* act_stop = nullptr;
QAction* act_close = nullptr;
QMenu* menu = nullptr;
接着在contextMenuEvent函数中添加和设置菜单:
void Widget::contextMenuEvent(QContextMenuEvent *event)
{
Q_UNUSED(event)
if(!menu)
{
menu = new QMenu(this);
//为菜单设置样式,可以设置成自己喜欢的风格样式,感觉在写前端一样,哈哈。
menu->setStyleSheet("QMenu{background-color:#ABABAB;border:1px solid black;margin:2px;}"
"QMenu::item{padding: 2px 25px 2px 20px;margin:1px;border: 1px solid black;border-radius:5px;}"
"QMenu::item:selected{background-color:#654321;border-color:darkblue;}"
);
//下面三个action就是菜单项
act_start = menu->addAction("开始");
act_stop = menu->addAction("停止");
act_close = menu->addAction("关闭");
//为菜单项关联信号与槽函数
connect(act_start,&QAction::triggered,this,&Widget::act_start_slot);
connect(act_stop,&QAction::triggered,this,&Widget::act_stop_slot);
connect(act_close,&QAction::triggered,this,&Widget::close);
}
menu->exec(QCursor::pos()); //这一步很重要,别忘了写,让菜单显示出来。
}
//下面是两个槽函数的实现,最后的关闭函数close是窗口自带的,不需要我们写,直接用。
void Widget::act_start_slot()
{
if(0 == timerId)
{
timerId = startTimer(100);
act_start->setEnabled(false);
act_stop->setEnabled(true);
}
}
void Widget::act_stop_slot()
{
if(timerId)
{
killTimer(timerId);
timerId = 0;
act_start->setEnabled(true);
act_stop->setEnabled(false);
}
}
到这里,右键菜单及其功能都实现了。
二、让星球转起来
上面我们在实现右键菜单的时候,注意到我们使用了startTimer函数,这个函数是QWidget窗口自带的,我们并没有去new一个QTimer,然后为timer的timeout信号函数关联一个自己写的槽函数,并用connect去关联起来。我们也可以这样做,但是我不这样做的原因: 实现转动功能本身只需要一个定时器就够了,而且QWidget自带一个定时器,能偷懒就偷懒呗,哈哈。
现在还有一个问题,既然上面使用了startTimer(100);那么意思就是每隔100ms就会触发定时器,那么定时器触发后去哪里实现超时函数呢?答案就是需要我们重写timerEvent函数:
1 void Widget::timerEvent(QTimerEvent *e)
2 {
3 Q_UNUSED(e)
4 angle+=5;
5 if(angle>=360)
6 angle = 0;
7 update();
8
9 }
angle是我设置的成员变量,代表当前转动的角度,初始值设为0。也就是说每隔100ms,让角度转动5度(就是图片转动5度),当超过360度就重新从0开始计算,最后一个是更新显示函数update,就是我们主动去更新画面。既然使用了update函数,那么该函数会去执行paintEvent函数,所以我们得去实现它。
void Widget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter painter(this);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.translate(width()/2,height()/2);
painter.rotate(angle);
painter.translate(-(width()/2),-(height()/2));
painter.drawPixmap(0,0,QPixmap("../res/test.jpg"));
}
上面的paintEvent函数里主要就是一个设置了防锯齿效果setRenderHint,还有就是转换坐标系,我们知道窗口的坐标系起点是左上角,然而我们要转动星球,当然是以星球的中心为原点来旋转,所以使用translate函数来重新设定坐标系原点为窗口的中心,然后使用rotate函数来旋转一定角度,最后我们需要将坐标系原点重新设置为左上角来绘制图片。搞定!!!
三、实现窗口的移动
功能:鼠标左键按下后移动,窗口也随着鼠标移动。
其实这个最简单了,无非就是实现(重写)鼠标的按下,移动和释放函数,直接上代码。
void Widget::mouseMoveEvent(QMouseEvent *event)
{
if(ispressed)
{
move(event->globalPos() - point_deff);
}
}
void Widget::mousePressEvent(QMouseEvent *event)
{
if(event->button() & Qt::LeftButton)
{
ispressed = true;
point_deff = event->globalPos() - pos();
}
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() & Qt::LeftButton)
ispressed = false;
}
这里稍微解释下,ispressed和point_deff是两个成员变量,分别记录鼠标是否按下和坐标差。
point_deff是记录鼠标按下时,当前鼠标的全局坐标(相对于windows桌面的左上角)和当前窗口的全局坐标之差,说白了就是算出鼠标与当前窗口左上角的距离。然后当鼠标移动时,用鼠标的全局坐标减去point_deff就是当前窗口左上角的坐标位置了,使用move函数移动窗口就行了。
终于记录完了,虽然只是写了一个小玩意,但里面涉及的点还是蛮多的,多玩玩就熟能生巧了,当然也可以根据自己的脑洞继续添加功能,比如星球的正反转啊之类的等等。
不说了,老板喊我搬砖去了。溜了溜了,最后送上一个静态异形窗口小花花,哈哈。