nativeEvent()

MSG

使用MSG结构体时,需要#include <windows.h>头文件;
在Qt项目中重载nativeEvent()时,通常会自动包含<windows.h>,因为该头文件在Qt的底层实现中已经被使用。
详细资料可查阅Microsoft官方文档:https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-msg

1
2
3
4
5
6
7
8
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG,*PMSG,*NPMSG,*LPMSG;
  • HWND hwnd:发送消息的窗口的句柄;
  • UINT message:消息类型;
  • WPARAM wParam:消息的相关信息(如按键代码、鼠标按键状态);
  • LPARAM lParam:消息的额外相关信息(如鼠标坐标、窗口位置);
  • DWORD time:发送消息的时间;
  • POINT pt:发生消息时鼠标的光标位置;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#ifndef XWIDGET_H
#define XWIDGET_H

#include <QWidget>
#include <QDebug>
#include <qt_windows.h>

namespace Ui {
class XWidget;
}

class XWidget : public QWidget
{
Q_OBJECT

public:
explicit XWidget(QWidget *parent = nullptr);
~XWidget();

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

long hitTest(HWND hWnd, POINT cursor) const;

bool isCompositionEnabled();

void adjustRectIfMaximized(HWND window, RECT& rect);

bool isMaximized(HWND hwnd);

private:
Ui::XWidget *ui;
};

#endif // XWIDGET_H

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#include "XWidget.h"
#include "ui_XWidget.h"

#include <windowsx.h>
#include <dwmapi.h>
#include <windows.h>

XWidget::XWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::XWidget)
{
ui->setupUi(this);
setWindowFlag(Qt::Window);
setWindowTitle(u8"无边框窗口测试");
setGeometry(400, 400, 800, 500);
HWND hWnd = (HWND)winId();
SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE);
}

XWidget::~XWidget()
{
delete ui;
}

bool XWidget::nativeEvent(const QByteArray &/*eventType*/, void *message, long *result)
{
MSG* msg = (MSG*)message;
switch (msg->message)
{
case WM_NCCALCSIZE:
if (msg->wParam == TRUE)
{
NCCALCSIZE_PARAMS* params = reinterpret_cast<NCCALCSIZE_PARAMS*>(msg->lParam);
adjustRectIfMaximized(msg->hwnd, params->rgrc[0]);
*result = 0;
return true;
}
break;
case WM_NCHITTEST:
{
*result = hitTest(msg->hwnd, { GET_X_LPARAM(msg->lParam), GET_Y_LPARAM(msg->lParam) });
return true;
}
break;
case WM_NCACTIVATE:
if (!isCompositionEnabled())
{
*result = TRUE;
return true;
}
break;
default:
break;
}
return false;
}

long XWidget::hitTest(HWND hWnd, POINT cursor) const
{
// 缓存系统边框大小
const int borderX = GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(92);
const int borderY = GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(92);

RECT window;
if (!GetWindowRect(hWnd, &window))
{
return HTNOWHERE;
}

// 将光标坐标转换为窗口坐标
POINT localCursor = { cursor.x, cursor.y };
ScreenToClient(hWnd, &localCursor);

// 将 POINT 转换为 QPoint
QPoint qCursor(localCursor.x, localCursor.y);
// 检查是否在 topWidget 区域内
QRect topWidgetRect = ui->contentWidget->geometry().adjusted(4, 4, -4, -4); // 获取 topWidget 的几何形状
if (topWidgetRect.contains(qCursor))
{
return HTCLIENT; // 返回可以拖动窗口的区域
}

enum RegionMask
{
CLIENT = 0b0000,
LEFT = 0b0001,
RIGHT = 0b0010,
TOP = 0b0100,
BOTTOM = 0b1000,
};

// 计算鼠标位置相对于窗口的区域
int result = CLIENT;
if (localCursor.x < (borderX)) result |= LEFT;
if (localCursor.x >= (window.right - window.left - borderX)) result |= RIGHT;
if (localCursor.y < (borderY)) result |= TOP;
if (localCursor.y >= (window.bottom - window.top - borderY)) result |= BOTTOM;

// 返回拖动区域
if (result == CLIENT) return HTCAPTION;
if (result & LEFT && result & TOP) return HTTOPLEFT;
if (result & LEFT && result & BOTTOM) return HTBOTTOMLEFT;
if (result & RIGHT && result & TOP) return HTTOPRIGHT;
if (result & RIGHT && result & BOTTOM) return HTBOTTOMRIGHT;
if (result & LEFT) return HTLEFT;
if (result & RIGHT) return HTRIGHT;
if (result & TOP) return HTTOP;
if (result & BOTTOM) return HTBOTTOM;

return HTNOWHERE;
}

/**
* @brief XWidget::isCompositionEnabled 用于检测系统是否启用了 Aero Glass 效果
* @return
*/
bool XWidget::isCompositionEnabled()
{
BOOL res = FALSE;
if ( DwmIsCompositionEnabled(&res) == S_OK )
{
return res;
}
return false;
}

void XWidget::adjustRectIfMaximized(HWND window, RECT &rect)
{
if (!isMaximized(window))
{
return;
}
auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
if (!monitor)
{
return;
}
MONITORINFO monitor_info = { 0 };
monitor_info.cbSize = sizeof(monitor_info);
if (GetMonitorInfo(monitor, &monitor_info))
{
rect.left = monitor_info.rcWork.left - 6;
rect.top = monitor_info.rcWork.top - 6;
rect.right = monitor_info.rcWork.right + 6;
rect.bottom = monitor_info.rcWork.bottom + 6;
}
}

bool XWidget::isMaximized(HWND hwnd)
{
WINDOWPLACEMENT placement;
if (GetWindowPlacement(hwnd, &placement))
{
return placement.showCmd == SW_MAXIMIZE;
}
return false;
}