Qt无边框窗体(Windows)
去掉标题栏和边框
首先第一步我们要通过设置系统绘制的边框消失
setWindowFlags(Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint);
通过上述设置我们可以看见原来一个带标题栏的窗体变成了一个白块,这算是无边框窗体的第一步,但是一个窗体需要有窗体应有的功能,比如可以拖拽,我们还需要再进行几步。
实现拖拽功能
一个白色的框框,我们发现不能像原来那样拖动了,相信大家在网上也看过许多如何实现无边框窗体的拖拽方法,现在我给出这样一个方法,重写鼠标事件
void mousePressEvent(QMouseEvent* event)
{
#ifdef Q_OS_WIN
if (ReleaseCapture())
SendMessage(HWND(winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
event->ignore();
#else
#endif
}
还原窗体功能
首先我们得设置成这样
HWND hWnd = (HWND)this->winId();
DWORD style = ::GetWindowLong(hWnd, GWL_STYLE);
SetWindowLong(hWnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION | CS_DBLCLKS);
我们可以看见在Windows上你最大化窗口,或者说是最小化窗口,拖拽到边缘停靠,会有动画的,我们设置了无边框以后,这些动画也就随之消失了,通过上述的设置,可以让这些动画重新出现。
接下来就是可以拉伸窗体的部分了,首先我们要了解一个事件,nativeEvent(),我们要做的就是重写它。
bool BaseView::nativeEvent(const QByteArray & eventType, void * message, long * result)
{
Q_UNUSED(eventType)
MSG* msg = reinterpret_cast<MSG*>(message);
switch (msg->message) {
case WM_NCHITTEST:
{
*result = 0;
const LONG border_width = 5;
RECT winrect;
GetWindowRect(reinterpret_cast<HWND>(winId()), &winrect);
long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam);
auto resizeWidth = minimumWidth() != maximumWidth();
auto resizeHeight = minimumHeight() != maximumHeight();
if (resizeWidth)
{
//左边
if (x >= winrect.left && x < winrect.left + border_width)
{
*result = HTLEFT;
}
//右边
if (x < winrect.right && x >= winrect.right - border_width)
{
*result = HTRIGHT;
}
}
if (resizeHeight)
{
//底边
if (y < winrect.bottom && y >= winrect.bottom - border_width)
{
*result = HTBOTTOM;
}
//top border
if (y >= winrect.top && y < winrect.top + border_width)
{
*result = HTTOP;
}
}
if (resizeWidth && resizeHeight)
{
//左底边
if (x >= winrect.left && x < winrect.left + border_width &&
y < winrect.bottom && y >= winrect.bottom - border_width)
{
*result = HTBOTTOMLEFT;
}
//右底边
if (x < winrect.right && x >= winrect.right - border_width &&
y < winrect.bottom && y >= winrect.bottom - border_width)
{
*result = HTBOTTOMRIGHT;
}
//左上边
if (x >= winrect.left && x < winrect.left + border_width &&
y >= winrect.top && y < winrect.top + border_width)
{
*result = HTTOPLEFT;
}
//右上边
if (x < winrect.right && x >= winrect.right - border_width &&
y >= winrect.top && y < winrect.top + border_width)
{
*result = HTTOPRIGHT;
}
}
if (*result != 0)
return true;
break;
}
default:
return QWidget::nativeEvent(eventType, message, result);
}
return QWidget::nativeEvent(eventType, message, result);
}
其实上述方法在MSDN里也是可以查到的,只是在这里我们用到了Qt里边。
注意点
到这里其实这个窗体已经做到了一个窗体该有的功能,当然一些功能性的按钮还是要自定义的,但是它已经相当于一个很好的白板,可以提供给你*发挥。但是仔细的人会发现,这样的窗体在最大化之后,会出现一点像素点偏移的情况。
经过查找资料得知,这是因为一个窗体在最大化的时候,系统会把边框的宽度考虑进去,经过计算再最大化,但是现在我们去掉了边框,可系统还是把边框的宽度考虑进去,所以会出现偏移几个像素点的情况,现在给出一个本人认为不错的解决方案。
在头文件里写下几个函数
private:
auto composition_enabled() -> bool {
auto composition_enabled = FALSE;
auto success = ::DwmIsCompositionEnabled(&composition_enabled) == S_OK;
return composition_enabled && success;
}
auto ifMaximized(HWND hwnd) -> bool
{
WINDOWPLACEMENT placement{};
if (!::GetWindowPlacement(hwnd, &placement)) { return false; }
return placement.showCmd == SW_MAXIMIZE;
}
auto adjust_maximized_client_rect(HWND window, RECT& rect) -> void
{
if (!ifMaximized(window)) { return; }
auto monitor = ::MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
if (!monitor) { return; }
MONITORINFO monitor_info{};
monitor_info.cbSize = sizeof(monitor_info);
if (!::GetMonitorInfoW(monitor, &monitor_info)) { return; }
rect = monitor_info.rcWork;
}
对于这些函数的应用,我们还是要回到**nativeEvent()**里面。
case WM_GETMINMAXINFO: {
MINMAXINFO* mmi = reinterpret_cast<MINMAXINFO*>(msg->lParam);
if (ifMaximized(msg->hwnd)) {
RECT window_rect{};
if (!GetWindowRect(msg->hwnd, &window_rect)) {
return false;
}
HMONITOR monitor = MonitorFromRect(&window_rect, MONITOR_DEFAULTTONULL);
if (!monitor) {
return false;
}
MONITORINFO monitor_info = { 0 };
monitor_info.cbSize = sizeof(monitor_info);
GetMonitorInfo(monitor, &monitor_info);
RECT work_area = monitor_info.rcWork;
RECT monitor_rect = monitor_info.rcMonitor;
mmi->ptMaxPosition.x = abs(work_area.left - monitor_rect.left);
mmi->ptMaxPosition.y = abs(work_area.top - monitor_rect.top);
mmi->ptMaxSize.x = abs(work_area.right - work_area.left);
mmi->ptMaxSize.y = abs(work_area.bottom - work_area.top);
mmi->ptMaxTrackSize.x = mmi->ptMaxSize.x;
mmi->ptMaxTrackSize.y = mmi->ptMaxSize.y;
*result = 1;
return true;
}
}
case WM_NCACTIVATE: {
if (!composition_enabled()) {
*result = 1;
return true;
}
break;
}
case WM_SIZE: {
RECT winrect;
GetClientRect(msg->hwnd, &winrect);
WINDOWPLACEMENT wp;
wp.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(msg->hwnd, &wp);
if (this)
{
if (wp.showCmd == SW_MAXIMIZE)
{
::SetWindowPos(reinterpret_cast<HWND>(winId()), Q_NULLPTR, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
}
}
}
关于Windows平台上的这些消息,不在本文的内容里,如果想要了解更多,可以自己去搜索相关方面的资料。
我们可以做的更好
添加阴影
窗体的阴影让窗体的辨识度更高,窗体也更好看,既然在Windows平台下,我们就使用Windows平台的解决方案。
Windows画出阴影,使用的是DWM,我们也是要借助它。
首先添加
#pragma comment (lib,"Dwmapi.lib")
接着添加如下代码
BOOL bEnable = false;
::DwmIsCompositionEnabled(&bEnable);
if (bEnable)
{
DWMNCRENDERINGPOLICY ncrp = DWMNCRP_ENABLED;
::DwmSetWindowAttribute((HWND)winId(), DWMWA_NCRENDERING_POLICY, &ncrp, sizeof(ncrp));
MARGINS margins = { -1 };
::DwmExtendFrameIntoClientArea((HWND)winId(), &margins);
}
亚克力面板效果
我们很喜欢的一个效果有毛玻璃效果,也叫高斯模糊的效果,关于Windows7开启毛玻璃效果在Qt的实例里面已经有了,本文也不再赘述。Windows10下的叫亚克力面板效果,但是通常在一些UWP软件里才看到这种效果的运用,其实我们在Qt里也能运用。
首先要自定义一个这样的头文件。
WindowCompositionAttribute.h
#pragma once
#include <QtWinExtras>
#include <Windows.h>
#include <dwmapi.h>
typedef enum _WINDOWCOMPOSITIONATTRIB
{
WCA_UNDEFINED = 0,
WCA_NCRENDERING_ENABLED = 1,
WCA_NCRENDERING_POLICY = 2,
WCA_TRANSITIONS_FORCEDISABLED = 3,
WCA_ALLOW_NCPAINT = 4,
WCA_CAPTION_BUTTON_BOUNDS = 5,
WCA_NONCLIENT_RTL_LAYOUT = 6,
WCA_FORCE_ICONIC_REPRESENTATION = 7,
WCA_EXTENDED_FRAME_BOUNDS = 8,
WCA_HAS_ICONIC_BITMAP = 9,
WCA_THEME_ATTRIBUTES = 10,
WCA_NCRENDERING_EXILED = 11,
WCA_NCADORNMENTINFO = 12,
WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
WCA_VIDEO_OVERLAY_ACTIVE = 14,
WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
WCA_DISALLOW_PEEK = 16,
WCA_CLOAK = 17,
WCA_CLOAKED = 18,
WCA_ACCENT_POLICY = 19,
WCA_FREEZE_REPRESENTATION = 20,
WCA_EVER_UNCLOAKED = 21,
WCA_VISUAL_OWNER = 22,
WCA_LAST = 23
} WINDOWCOMPOSITIONATTRIB;
typedef struct _WINDOWCOMPOSITIONATTRIBDATA
{
WINDOWCOMPOSITIONATTRIB Attrib;
PVOID pvData;
SIZE_T cbData;
} WINDOWCOMPOSITIONATTRIBDATA;
typedef enum _ACCENT_STATE
{
ACCENT_DISABLED = 0,
ACCENT_ENABLE_GRADIENT = 1,
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
ACCENT_ENABLE_BLURBEHIND = 3,
ACCENT_INVALID_STATE = 4
} ACCENT_STATE;
typedef struct _ACCENT_POLICY
{
ACCENT_STATE AccentState;
DWORD AccentFlags;
DWORD GradientColor;
DWORD AnimationId;
} ACCENT_POLICY;
WINUSERAPI
BOOL
WINAPI
GetWindowCompositionAttribute(
_In_ HWND hWnd,
_Inout_ WINDOWCOMPOSITIONATTRIBDATA* pAttrData);
typedef BOOL(WINAPI*pfnGetWindowCompositionAttribute)(HWND, WINDOWCOMPOSITIONATTRIBDATA*);
WINUSERAPI
BOOL
WINAPI
SetWindowCompositionAttribute(
_In_ HWND hWnd,
_Inout_ WINDOWCOMPOSITIONATTRIBDATA* pAttrData);
typedef BOOL(WINAPI*pfnSetWindowCompositionAttribute)(HWND, WINDOWCOMPOSITIONATTRIBDATA*);
这使用的是为未公开的api,具体运用是这样的
HWND hWnd = HWND(winId());
HMODULE hUser = GetModuleHandle(L"user32.dll");
if (hUser)
{
pfnSetWindowCompositionAttribute setWindowCompositionAttribute =
(pfnSetWindowCompositionAttribute)GetProcAddress(hUser, "SetWindowCompositionAttribute");
if (setWindowCompositionAttribute)
{
ACCENT_POLICY accent = { ACCENT_ENABLE_BLURBEHIND, 0, 0, 0 };
WINDOWCOMPOSITIONATTRIBDATA data;
data.Attrib = WCA_ACCENT_POLICY;
data.pvData = &accent;
data.cbData = sizeof(accent);
setWindowCompositionAttribute(hWnd, &data);
}
}
结语
本文零零碎碎给出了一些代码片段,如果想具体知道如何运用在项目里,欢迎来我的仓库:翟长腿的小仓库
如果觉得好可以点个星星