轻量级 Qt无边框、带阴影、可变大小窗口方案(Windows)

要想功能完善,兼容多平台,建议你使用这个开源库:GitHub - stdware/qwindowkit: Cross-platform frameless window framework for Qt. Support Windows, macOS, Linux.

我这里介绍的是一个仅支持Windows的轻量级方案:

构造函数

首先自定义一个 QWidget ,在做好布局,添加好子控件之后,执行如下三段代码:

setAttribute(Qt::WA_DontCreateNativeAncestors);
HWND hwnd = reinterpret_cast<HWND>(winId());
MARGINS margins = { 1, 1, 1, 1 };
DwmExtendFrameIntoClientArea(hwnd, &margins);
int value = 2;
DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &value, sizeof(value));
DwmSetWindowAttribute(hwnd, DWMWA_ALLOW_NCPAINT, &value, sizeof(value));

 上面这段代码用于为窗口添加阴影效果


LONG style = GetWindowLongPtr(hwnd, GWL_STYLE);
style &= ~(WS_CAPTION | WS_THICKFRAME | WS_BORDER | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
SetWindowLongPtr(hwnd, GWL_STYLE, style);
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);

上面这段代码用于移除窗口的标题栏和边框,注意此处不能使用 setWindowFlags(Qt::FramelessWindowHint | Qt::Window); 来设置无边框窗口,不然窗口最大化之后无法还原,参见我之前的文章。

setGeometry(0, 0, 1200, 800);
//show();
showMaximized();

这段代码用于设置窗口位置、大小,并显示窗口,注意,必须等设置完阴影效果,移除边框之后,才能设置窗口大小并显示。不然窗口内容大小和定位会出问题。

处理系统消息

现在来处理窗口的原生事件

bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override {
    MSG* msg = static_cast<MSG*>(message);
    switch (msg->message) {
       //......
    }
    return QWidget::nativeEvent(eventType, message, result);
}

第一个原生消息:

case WM_GETMINMAXINFO: {
    MINMAXINFO* mmi = reinterpret_cast<MINMAXINFO*>(msg->lParam);
    RECT workArea;
    SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
    mmi->ptMaxPosition.x = workArea.left;
    mmi->ptMaxPosition.y = workArea.top;
    mmi->ptMaxSize.x = workArea.right - workArea.left;
    mmi->ptMaxSize.y = workArea.bottom - workArea.top;
    *result = 0;
    return true;
}

这段代码用于设置窗口最大化的尺寸,避免窗口最大化时,把任务栏都盖住了。

第二个原生消息:

case WM_NCHITTEST: {
    const static int borderWidth = 8;
    QPoint pos = mapFromGlobal(QCursor::pos());
    bool left = pos.x() < borderWidth;
    bool right = pos.x() > width() - borderWidth;
    bool top = pos.y() < borderWidth;
    bool bottom = pos.y() > height() - borderWidth;
    if (left && top) *result = HTTOPLEFT;
    else if (right && top) *result = HTTOPRIGHT;
    else if (left && bottom) *result = HTBOTTOMLEFT;
    else if (right && bottom) *result = HTBOTTOMRIGHT;
    else if (left) *result = HTLEFT;
    else if (right) *result = HTRIGHT;
    else if (top) *result = HTTOP;
    else if (bottom) *result = HTBOTTOM;
    else if (pos.x() < titleBar->btnMin->pos().x() && pos.y() < 34) {
        *result = HTCAPTION;
    }
    else {
        *result = HTCLIENT;
    }
    return true;
}

这段代码用于配置窗口的自定义边框和标题栏可拖拽区域,用于拖拽改变窗口大小和位置

第三个原生消息:

case WM_NCLBUTTONDBLCLK: {
    QPoint pos = mapFromGlobal(QCursor::pos());
    if (pos.x() < titleBar->btnMin->pos().x() && pos.y() < 34) {
        if (windowState() & Qt::WindowMaximized) {
			showNormal();
        }
        else {
            showMaximized();
        }
        return true;
    }
    break;
}

这段代码鼠标双击自定义标题栏区域时,切换窗口最大化,最小化的状态。

窗口显示时发送鼠标点击消息

接下来在窗口显示时发送鼠标点击消息

void showEvent(QShowEvent* event) override
{
	QWidget::showEvent(event);
    HWND hwnd = reinterpret_cast<HWND>(winId());
    LPARAM pos = MAKELPARAM(1, 1);
    PostMessage(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, pos);
    PostMessage(hwnd, WM_LBUTTONUP, 0, pos);
}

这个消息主要是用于让窗口聚焦,不然 在最小化窗口后再通过点击任务栏,显示窗口,此时鼠标移入 setSystemButton 设置的组件(自定义 QWidget ),无法触发此组件的 enterEvent
鼠标移出组件时,也无法触发此组件的 leaveEvent。

这是正常状态

这是异常状态

有了上面一段代码,就不会再有这个问题

最大化状态切换时改变最大化按钮图标

void changeEvent(QEvent* event) override
{
    if (event->type() == QEvent::WindowStateChange) {
        if (windowState() & Qt::WindowMaximized) {
            titleBar->btnMax->code = 0xe6ea;
            titleBar->update();
        }
        else {
            titleBar->btnMax->code = 0xe6e5;
            titleBar->update();
        }
    }
    QWidget::changeEvent(event);
}

这段代码用于在窗口最大化状态切换时改变最大化按钮图标

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liulun

如果文章真帮到了你,谢谢您打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值