讓Qt的無邊框窗口支持拖拽、Aero Snap、窗口陰影等特性


環境:Desktop Qt 5.4.1 MSVC2013 32bit

需要的庫:dwmapi.lib 、user32.lib

需要頭文件:<dwmapi.h> 、<windowsx.h>

在要處理的QWidget 構造函數中,添加以下兩行:

1
2
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
SetWidgetBorderless(this);

SetWidgetBorderless的實現如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void SetWidgetBorderless(const QWidget *widget)
{
#ifdef Q_OS_WIN
HWND hwnd = reinterpret_cast<HWND>(widget->winId());

const LONG style = ( WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CLIPCHILDREN );
SetWindowLongPtr(hwnd, GWL_STYLE, style);

const MARGINS shadow = {1, 1, 1, 1};
DwmExtendFrameIntoClientArea(hwnd, &shadow);

SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
#endif
}

這個函數的作用是給無邊框窗口加上陰影、Aero Snap以及其他動畫特效。

這時窗口還無法手動更改大小,需要更改的話,需要自己實現一個QAbstractNativeEventFilter 類,內容如下:

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
class NativeEventFilter : public QAbstractNativeEventFilter
{
public:
bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) Q_DECL_OVERRIDE
{
#ifdef Q_OS_WIN
if (eventType != "windows_generic_MSG")
return false;

MSG* msg = static_cast<MSG*>(message);
QWidget* widget = QWidget::find(reinterpret_cast<WId>(msg->hwnd));
if (!widget)
return false;

switch (msg->message) {
case WM_NCCALCSIZE: {
*result = 0;
return true;
}

case WM_NCHITTEST: {
const LONG borderWidth = 9;
RECT winrect;
GetWindowRect(msg->hwnd, &winrect);
long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam);

// bottom left
if (x >= winrect.left && x < winrect.left + borderWidth &&
y < winrect.bottom && y >= winrect.bottom - borderWidth)
{
*result = HTBOTTOMLEFT;
return true;
}

// bottom right
if (x < winrect.right && x >= winrect.right - borderWidth &&
y < winrect.bottom && y >= winrect.bottom - borderWidth)
{
*result = HTBOTTOMRIGHT;
return true;
}

// top left
if (x >= winrect.left && x < winrect.left + borderWidth &&
y >= winrect.top && y < winrect.top + borderWidth)
{
*result = HTTOPLEFT;
return true;
}

// top right
if (x < winrect.right && x >= winrect.right - borderWidth &&
y >= winrect.top && y < winrect.top + borderWidth)
{
*result = HTTOPRIGHT;
return true;
}

// left
if (x >= winrect.left && x < winrect.left + borderWidth)
{
*result = HTLEFT;
return true;
}

// right
if (x < winrect.right && x >= winrect.right - borderWidth)
{
*result = HTRIGHT;
return true;
}

// bottom
if (y < winrect.bottom && y >= winrect.bottom - borderWidth)
{
*result = HTBOTTOM;
return true;
}

// top
if (y >= winrect.top && y < winrect.top + borderWidth)
{
*result = HTTOP;
return true;
}

return false;
}
default:
break;
}

return false;
#else
return false;
#endif
}
};

然后在窗口創建之前,使用QApplication::installNativeEventFilter 方法把監聽器注冊給主程序。

要手動移動窗口位置的話,還要重截QWidget::mousePressEvent 方法:

1
2
3
4
5
6
7
8
9
10
void MyWidget::mousePressEvent(QMouseEvent *ev)
{
QWidget::mousePressEvent(ev);
if (!ev->isAccepted()) {
#ifdef Q_OS_WIN
ReleaseCapture();
SendMessage(reinterpret_cast<HWND>(winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
}
#endif
}

在實際操作時,有時還需要添加最大化和關閉按扭,這時正常調用QWidget::showMaximized()QWidget::close() 等Qt自帶方法即可。

最終實現效果大概是這樣:

frameless_window_preview

縮放動畫、陰影什么的和Windows本地窗口一樣。

參考鏈接:https://github.com/deimos1877/BorderlessWindow

 

https://blog.yeatse.com/2015/03/01/qt-frameless-window/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM