如何用QT利用Windows窗口消息实现自定义标题栏

前言:之前项目实现过一个自定义标题栏,是用的过滤器处理鼠标事件(按下、抬起、移动),对于Windows的原神窗口机制完全没法支持,使用起来总是出现一些奇奇怪怪的bug,后来完全摒弃了,使用了原生的标题栏。后续的开发过程中我就在想,本来Qt就是在Win32窗口基础上进行封装的,为什么不能调用Windows原生的信号和事件实现标题栏的自定义呢?

查了很多的资料,发现了一个窗口事件:

virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result) override;

这个窗口事件可以抓取Windows本身的消息循环,通过eventType区分Windows/Linux消息类型,其中message参数就是Windows发出来的消息,通过result和返回值可以操作这些消息。(具体信息可以参考Win32API帮助文档)

如果我能够重写这个事件,并对这些Windows消息进行处理,是否就能实现依托Windows机制实现自定义标题栏了呢?

答:确实可以,但是如果我一个程序定义了多种不同的标题栏,哪我是不是又该多次重写这个事件呢?这很明显不符合我们的开发-封闭原则。所以我找到了一个能全局处理Windows消息的方法,使用nativeEventFilter过滤器。

具体实现如下:

创建了自定义的本地事件过滤器,继承与QAbstractNativeEventFilter类,并重写nativeEventFilter函数。

安装过滤器:

直接在main函数里安装就行,全局调用。

代码:

qstyleeventfilter_native.h

#ifndef QSTYLEEVENTFILTER_NATIVE_H
#define QSTYLEEVENTFILTER_NATIVE_H

#include <QCoreApplication>
#include <QAbstractNativeEventFilter>
#include <QObject>
#include <windows.h>
#include <windowsx.h>

class QStyleEventFilter_Native : public QAbstractNativeEventFilter
{
public:
    QStyleEventFilter_Native();
    virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override;

private:
    QWidget* GetLastChild(QWidget* widget, const QPoint& point);
    LRESULT GetWindowPostion(const QPoint &pt);
    QWidget* m_active_widget = nullptr;
};

#endif // QSTYLEEVENTFILTER_NATIVE_H

 

qstyleeventfilter_native.cpp
#include "qstyleeventfilter_native.h"
#include <QDebug>
#include <QWidget>
#include <QApplication>
QStyleEventFilter_Native::QStyleEventFilter_Native()
{

}

bool QStyleEventFilter_Native::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
    if (eventType == "windows_generic_MSG")
    {
        m_active_widget = qApp->activeWindow();
        MSG* msg = static_cast<MSG*>(message);
        QWidget* widget = QWidget::find(reinterpret_cast<WId>(msg->hwnd));
        if (!widget || !m_active_widget)
        {
            return false;
        }
        switch (msg->message)
        {
        case WM_NCCALCSIZE:{
            *result = 0;
            return true;
        }
        case WM_NCHITTEST:
        {
            int x = GET_X_LPARAM(msg->lParam);
            int y = GET_Y_LPARAM(msg->lParam);
            QPoint param_point = QPoint(x, y);
            *result = GetWindowPostion(param_point);
            return true;
        }
        default:
            break;

        }
    }
    return false;
}

QWidget *QStyleEventFilter_Native::GetLastChild(QWidget *widget, const QPoint &point)
{
    QPoint child_point = widget->mapFromGlobal(point);
    QWidget* child_widget = widget->childAt(child_point.x(), child_point.y());
    if(child_widget != nullptr && child_widget->property("Windows_Title").toBool() && child_widget->children().size() > 0)
    {
        return GetLastChild(child_widget, point);
    }
    return  child_widget;
}

LRESULT QStyleEventFilter_Native::GetWindowPostion(const QPoint &pt)
{
    QWidget* temp_widget = GetLastChild(m_active_widget, pt);
    if(temp_widget == NULL || temp_widget->property("Windows_Daggable").toBool())
    {
        return HTCAPTION;
    }
    int borderSize = 4;
    int cx = m_active_widget->size().width();
    int cy = m_active_widget->size().height();
    QRect rectTopLeft(0, 0, borderSize, borderSize);
    QPoint child_point = m_active_widget->mapFromGlobal(pt);
    if (rectTopLeft.contains(child_point))
    {
        return HTTOPLEFT;
    }
    QRect rectLeft(0, borderSize, borderSize, cy - borderSize * 2);
    if (rectLeft.contains(child_point))
    {
        return HTLEFT;
    }
    QRect rectTopRight(cx - borderSize, 0, borderSize, borderSize);
    if (rectTopRight.contains(child_point))
    {
        return HTTOPRIGHT;
    }
    QRect rectRight(cx - borderSize, borderSize, borderSize, cy - borderSize * 2);
    if (rectRight.contains(child_point))
    {
        return HTRIGHT;
    }
    QRect rectTop(borderSize, 0, cx - borderSize * 2, borderSize);
    if (rectTop.contains(child_point))
    {
        return HTTOP;
    }
    QRect rectBottomLeft(0, cy - borderSize, borderSize, borderSize);
    if (rectBottomLeft.contains(child_point))
    {
        return HTBOTTOMLEFT;
    }
    QRect rectBottomRight(cx - borderSize, cy - borderSize, borderSize, borderSize);
    if (rectBottomRight.contains(child_point))
    {
        return HTBOTTOMRIGHT;
    }
    QRect rectBottom(borderSize, cy - borderSize, cx - borderSize * 2, borderSize);
    if (rectBottom.contains(child_point))
    {
        return HTBOTTOM;
    }
    return HTCLIENT;
}

 其中GetLastChild(QWidget *widget, const QPoint &point)函数是查找当前鼠标在那个控件上,通过控件的Windows_Daggable自定义属性来判断是否可在当前控件上拖拽窗口。当然,只有在拥有属性Windows_Title的窗口中才进行判断。无论标题栏窗口被嵌套了多少层,只要设置了Windows_Title,即可被识别为标题栏。

其中GetWindowPostion(const QPoint &pt)函数主要是判断当前鼠标处于窗口的哪个位置,用相应的windows标识来给result进行赋值,调用窗口事件,具体事件信息如下:

HTCAPTION 标题栏
HTCLIENT 用户区
HTLEFT 左边拉伸
HTTOPLEFT 左顶拉伸
//。。有兴趣可以自己看,挑几个用到了的说

到这里基本上已经将标题栏的行为接入Windows机制了,剩下的就只需要将标题栏放入对应的窗口中,代码如下:

void MainWindow::InitWindowsTitle()
{
    setWindowFlags(Qt::WindowMinimizeButtonHint | Qt::FramelessWindowHint);
    HWND hwnd = reinterpret_cast<HWND>(this->winId());
    DWORD style = GetWindowLong(hwnd, GWL_STYLE);
    SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME);

    QStyleControl_WindowsTitle* title = new QStyleControl_WindowsTitle(this);
    title->SetParentWidget(this);
    ui->gridLayout_2->addWidget(title);
    ui->gridLayout_2->setAlignment(Qt::AlignLeft | Qt::AlignTop);
    title->show();
}

主要的还是前面一段,调用Win32接口,给窗口设置一些Windows属性,隐藏原生标题栏。到这里基本上已经完成了,放一个效果展示:

无关正文:

 第一次写文章,可能有些错误,望各位大佬指点,主要是翻遍全站没找到个能用的,既然写好了,发出来给大家参考参考吧

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
在Linux下,可以通过自定义QWidget来实现自定义标题栏。具体实现步骤如下: 1. 创建一个继承自QWidget的类,用于实现自定义标题栏。 ```cpp class MyTitleBar : public QWidget { Q_OBJECT public: explicit MyTitleBar(QWidget *parent = nullptr); protected: void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; private: QPoint m_lastPos; }; ``` 2. 在构造函数中设置标题栏的大小、背景色和布局。 ```cpp MyTitleBar::MyTitleBar(QWidget *parent) : QWidget(parent) { setFixedHeight(30); setStyleSheet("background-color: #333333"); QHBoxLayout *layout = new QHBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); setLayout(layout); } ``` 3. 重写mousePressEvent和mouseMoveEvent函数,实现标题栏的拖动。 ```cpp void MyTitleBar::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { m_lastPos = event->globalPos() - this->parentWidget()->geometry().topLeft(); } } void MyTitleBar::mouseMoveEvent(QMouseEvent *event) { if (event->buttons() & Qt::LeftButton) { QPoint pos = event->globalPos() - m_lastPos; this->parentWidget()->move(pos); } } ``` 4. 在主窗口中添加自定义标题栏,并将系统标题栏隐藏。 ```cpp MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setWindowTitle("Custom Title Bar"); setWindowFlags(Qt::FramelessWindowHint | windowFlags()); MyTitleBar *titleBar = new MyTitleBar(this); setMenuWidget(titleBar); // 添加其他控件及布局 // ... } ``` 5. 编译运行程序,即可看到自定义标题栏

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值