最新大型开源项目-云游戏,云桌面系统,欢迎关注
本项目代码地址
1.标题栏的位置
如下图所示,标题栏就是我们经常操作拖动,关闭,最大化等窗口的位置,也可以显示程序的标题和icon。
本应用我们采用简洁的设计,只需要添加最小化和关闭。
2. 代码实现
Tips: 完整实现在代码仓库中文件为 title_bar.cpp , title_bar.h
2.1 首先定义一个类TitleBar并继承QWidget,并实现paintEvent方法。
class TitleBar : public QWidget
{
public:
TitleBar(QWidget* parent = nullptr);
~TitleBar();
void paintEvent(QPaintEvent *event) override;
};
在TitleBar的构造函数中,我们先设置一个固定的大小
TitleBar::TitleBar(QWidget* parent) : QWidget(parent) {
setFixedHeight(Settings::kTitleBarHeight);
setFixedWidth(Settings::kWindowWidth);
}
然后在paintEvent方法中将背景绘制一个颜色,用于查看TitleBar的具体大小,方便调整。
void TitleBar::paintEvent(QPaintEvent *event) {
QPainter painter(this);
painter.setBrush(QBrush(0x0099ff));
painter.drawRect(this->rect());
}
2.2 然后将TitleBar添加到 MainWindow中。在mainwindow.cpp中,构造函数中添加它。
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent) {
// 这些代码是第一节添加的,以后将不再展示
setWindowFlags(Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
// 这些代码是第一节添加的,以后将不再展示
auto shadow = new QGraphicsDropShadowEffect(this);
shadow->setOffset(0, 0);
shadow->setColor(QColor(0x666666));
shadow->setBlurRadius(12);
this->setGraphicsEffect(shadow);
// 构造TitleBar
title_bar_ = new TitleBar(this);
// 构造一个垂直方向的布局
auto root_layout = new QVBoxLayout();
root_layout->setSpacing(0);
root_layout->setMargin(0);
// 这里添加一个5像素的距离,是因为第一节为了构造阴影,向内偏移的5像素
root_layout->addSpacing(5);
// 将title_bar_添加到布局中
root_layout->addWidget(title_bar_);
// 添加一个弹簧,将title_bar_挤压到最上面
root_layout->addStretch();
setLayout(root_layout);
}
2.3 此时运行后,我们将得到下面的效果。
2.4 此时,标题栏还没有任何的拖动功能,窗口仍不可移动,接下来我们将添加鼠标事件。
重写如下事件
mousePressEvent 鼠标按下
mouseMoveEvent 鼠标移动
mouseReleaseEvent 鼠标松开
class TitleBar : public QWidget
{
public:
TitleBar(QWidget* parent = nullptr);
~TitleBar();
void paintEvent(QPaintEvent *event) override;
// 鼠标按下
void mousePressEvent(QMouseEvent *event) override;
// 鼠标移动
void mouseMoveEvent(QMouseEvent *event) override;
// 鼠标松开
void mouseReleaseEvent(QMouseEvent *event) override;
private:
// 鼠标按下时,按下位置的global坐标
QPoint click_point_{};
// 鼠标按下时,整个窗口的global坐标
QPoint click_window_pos_{};
// 标识鼠标是否按下的状态
bool left_pressed_ = false;
};
上面的代码注释中,提到了global坐标,具体代表什么意思呢?请看以下图示:
在窗口坐标系下,获得的是窗口内的大小,仅限于窗口之内。
在屏幕坐标系下,获得的是相对屏幕左上角位置的大小,相当于Qt中的global position。
这一点在paintEvent函数中,很容易验证。
void TitleBar::mousePressEvent(QMouseEvent *event) {
// 窗口坐标系
qDebug() << "pos: " << event->pos();
// 屏幕坐标系下
qDebug() << "global: " << event->globalPos();
}
因为我们要在整个屏幕空间内拖动窗口,因此要使用屏幕坐标系(global position)
2.5 实现拖动
有一点要注意,我们拖动事件是在标题栏TitleBar这个类中处理,但我们需要拖动的是整个窗口。在MainWindow中构造TitleBar时,TitleBar的Parent被设置为MainWindow,因此实际要处理移动时,要移动TitleBar的Parent,也就是MainWindow。
按照鼠标移动的相同方向和距离,移动整个窗口的左上角,就能实现窗口移动了。
因为我们只有在鼠标左键按下时,才处理操作,所以加了判断。
void TitleBar::mousePressEvent(QMouseEvent *event) {
if (event->buttons() & Qt::LeftButton) {
// 记录鼠标按下位置的坐标,在屏幕坐标系下
click_point_ = event->globalPos();
// 注意,这里的parent就是MainWindow,记录MainWindow的左上角的位置,在屏幕坐标系下
click_window_pos_ = ((QWidget*)this->parent())->pos();
left_pressed_ = true;
}
}
void TitleBar::mouseMoveEvent(QMouseEvent *event) {
if ((event->buttons() & Qt::LeftButton) && left_pressed_) {
// 计算鼠标在屏幕坐标系下移动了多少
QPoint point_offset = event->globalPos() - click_point_;
// 将整个窗口也移动相同的方向和距离。
// 注意:这里也使用了方向一词,因为正负数据代表不同的方向
auto target_pos = click_window_pos_ + point_offset;
// 使用move方法将整个窗口移动
((QWidget*)this->parent())->move(target_pos);
}
}
void TitleBar::mouseReleaseEvent(QMouseEvent *event) {
left_pressed_ = false;
}