前言:之前项目实现过一个自定义标题栏,是用的过滤器处理鼠标事件(按下、抬起、移动),对于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属性,隐藏原生标题栏。到这里基本上已经完成了,放一个效果展示:
无关正文:
第一次写文章,可能有些错误,望各位大佬指点,主要是翻遍全站没找到个能用的,既然写好了,发出来给大家参考参考吧