【Qt C++自绘制界面音乐播放器-2】增加自定义标题栏,实现拖动

27 阅读4分钟

最新大型开源项目-云游戏,云桌面系统,欢迎关注

GammaRay源码地址

本项目代码地址

源码

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;
}
2.6 最后我们得到效果