Qt5:Windows 10 标题栏自定义期间的 Aero Snap

Qt5 : Aero Snap during title bar customization on Windows 10

在Windows10自定义标题栏的时候,有没有人用Aero Snap成功了?

基础标题栏太丑

这个问题我都一周没解决了。如果有解决办法请告诉我,无论是Widget还是Qt Quick。


我解决了这个问题。如果我的问题很粗鲁,我深表歉意。我英语不好

起初,我打算用 QWidget 实现 Frameless Windows 并在其上嵌入 QQuickWidget,但我找到了更好的方法。

framelesshelper.h

#ifndef FRAMELESSHELPER_H
#define FRAMELESSHELPER_H

#include <QObject>
#include <QQmlParserStatus>
#include <QQuickItem>
#include <QAbstractNativeEventFilter>

// QML : import FramelessHelper 1.0
class QQuickWindow;
class FramelessHelper : public QObject, public QQmlParserStatus
{
    Q_OBJECT
    Q_INTERFACES(QQmlParserStatus)
    Q_PROPERTY(QQuickItem* titlebar READ titlebar WRITE setTitlebar NOTIFY titlebarChanged)
    Q_PROPERTY(qreal borderWidth READ borderWidth WRITE setBorderWidth NOTIFY borderWidthChanged)
public:
    explicit FramelessHelper(QObject *parent = nullptr);
    ~FramelessHelper();

    static void QmlInitialize();

    virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result);

    virtual void classBegin();
    virtual void componentComplete();

    QQuickItem *titlebar() const;
    void setTitlebar(QQuickItem *titlebar);

    qreal borderWidth() const;
    void setBorderWidth(qreal borderWidth);

signals:
    void borderWidthChanged();
    void titlebarChanged();


private:
    bool containAcceptMouseButtons(QQuickItem *, qreal x, qreal y);
    QQuickItem *mTitlebar = nullptr;
    QQuickWindow *mQuickWindow = nullptr;

    qreal mBorderWidth = 7;

    class NativeEventFilter : public QAbstractNativeEventFilter
    {
    public:
        virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result);

        static void deliver(QQuickWindow* window, FramelessHelper *helper);

        static NativeEventFilter *instance;
        static QHash<WId, FramelessHelper*> helpers;
    };
};

#endif // FRAMELESSHELPER_H

framelesshelper.cpp

#include "framelesshelper.h"
#include <QQuickWindow>
using namespace std;
FramelessHelper::FramelessHelper(QObject *parent) : QObject(parent)
{

}

FramelessHelper::~FramelessHelper()
{
    NativeEventFilter::helpers.remove(mQuickWindow->winId());
}

void FramelessHelper::QmlInitialize()
{
    qmlRegisterType<FramelessHelper>("FramelessHelper", 1,0, "FramelessHelper");
}

QQuickItem *FramelessHelper::titlebar() const
{
    return mTitlebar;
}

void FramelessHelper::setTitlebar(QQuickItem *titlebar)
{
    if (mTitlebar != titlebar) {
        mTitlebar = titlebar;
        emit titlebarChanged();
    }
}

qreal FramelessHelper::borderWidth() const
{
    return mBorderWidth;
}

void FramelessHelper::setBorderWidth(qreal borderWidth)
{
    if (mBorderWidth != borderWidth) {
        mBorderWidth = borderWidth;
        emit borderWidthChanged();
    }
}

bool FramelessHelper::containAcceptMouseButtons(QQuickItem *item, qreal x, qreal y)
{
    if (item->acceptedMouseButtons() != Qt::NoButton)
        return true;
    QPointF pos = item->mapToGlobal(QPoint(0, 0));
    int rx = x - pos.x();
    int ry = y - pos.y();

    QQuickItem *child = item->childAt(rx, ry);
    return child ? containAcceptMouseButtons(child, x, y) : false;
}

#ifdef Q_OS_WIN

#include <QPoint>
#include <QScreen>
#include <QSize>
#include <windowsx.h>
#include <QCoreApplication>
//#include <dwmapi.h>
//#include <objidl.h>
//#include <gdiplus.h>
//#include <GdiPlusColor.h>
//#include <windows.h>
//#include <WinUser.h>
//#pragma comment (lib,"Dwmapi.lib")
//#pragma comment (lib,"user32.lib")

bool FramelessHelper::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
    Q_UNUSED(eventType)
    #if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
    MSG* msg = *reinterpret_cast<MSG**>(message);
    #else
    MSG* msg = reinterpret_cast<MSG*>(message);
    #endif

    switch (msg->message) {
    case WM_NCHITTEST: {
        *result = 0;

        const LONG border_width = mBorderWidth;
        RECT winrect;
        GetWindowRect(HWND(mQuickWindow->winId()), &winrect);

        long x = GET_X_LPARAM(msg->lParam);
        long y = GET_Y_LPARAM(msg->lParam);



        if (mQuickWindow->visibility() == QWindow::Windowed) {
            bool resizeWidth = mQuickWindow->minimumWidth() != mQuickWindow->maximumWidth();
            bool resizeHeight = mQuickWindow->minimumHeight() != mQuickWindow->maximumHeight();

            if(resizeWidth)
            {
                //left border
                if (x >= winrect.left && x < winrect.left + border_width)
                {
                    *result = HTLEFT;
                }
                //right border
                if (x < winrect.right && x >= winrect.right - border_width)
                {
                    *result = HTRIGHT;
                }
            }
            if(resizeHeight)
            {
                //bottom border
                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)
            {
                //bottom left corner
                if (x >= winrect.left && x < winrect.left + border_width &&
                        y < winrect.bottom && y >= winrect.bottom - border_width)
                {
                    *result = HTBOTTOMLEFT;
                }
                //bottom right corner
                if (x < winrect.right && x >= winrect.right - border_width &&
                        y < winrect.bottom && y >= winrect.bottom - border_width)
                {
                    *result = HTBOTTOMRIGHT;
                }
                //top left corner
                if (x >= winrect.left && x < winrect.left + border_width &&
                        y >= winrect.top && y < winrect.top + border_width)
                {
                    *result = HTTOPLEFT;
                }
                //top right corner
                if (x < winrect.right && x >= winrect.right - border_width &&
                        y >= winrect.top && y < winrect.top + border_width)
                {
                    *result = HTTOPRIGHT;
                }
            }
            if (0!=*result) return true;
        }

        if (!mTitlebar) return false;

        QPointF tbPos = mTitlebar->mapToGlobal(QPoint(0, 0));
        if (mQuickWindow->visibility() != QWindow::FullScreen &&
            tbPos.y() <= y && tbPos.y() + mTitlebar->height() >= y &&
            tbPos.x() <= x && tbPos.x() + mTitlebar->width() >= x &&
            !containAcceptMouseButtons(mTitlebar, x, y))
        {
            *result = HTCAPTION;
            return true;
        }

        break;
    }
    case WM_NCCALCSIZE:
    {
        NCCALCSIZE_PARAMS& params = *reinterpret_cast<NCCALCSIZE_PARAMS*>(msg->lParam);
        if (params.rgrc[0].top != 0)
            params.rgrc[0].top -= 1;

        *result = WVR_REDRAW;
        return true;
    }}

    return false;
}

void FramelessHelper::classBegin()
{

}

void FramelessHelper::componentComplete()
{
    auto obj = parent();
    while (obj != nullptr) {
        if (obj->inherits("QQuickRootItem")) {
            auto rootItem = qobject_cast<QQuickItem *>(obj);
            mQuickWindow = qobject_cast<QQuickWindow *>(rootItem->window());
            if (mQuickWindow) {
                NativeEventFilter::deliver(mQuickWindow, this);
                // 이동한 모니터의 DPI가 다를경우 화면을 다시 그린다.
                connect(mQuickWindow, &QWindow::screenChanged, this, [this](QScreen *){
                    SetWindowPos((HWND) mQuickWindow->winId(), NULL, 0, 0, 0, 0,
                                 SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
                                 SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE);
                });
                break;
            }
        }
        obj = obj->parent();
    }
}

void FramelessHelper::NativeEventFilter::deliver(QQuickWindow *window, FramelessHelper *helper)
{
    if (instance == nullptr) {
        instance = new NativeEventFilter;
        if (instance) qApp->installNativeEventFilter(instance);
    }
    if (window && helper) {
        auto wid = window->winId();
        if (!helpers.contains(wid)) {
            auto hwnd = reinterpret_cast<HWND>(wid);
            // set new window style
            DWORD oldStyle = GetWindowLong(hwnd, GWL_STYLE);
            SetWindowLong(hwnd, GWL_STYLE, oldStyle | WS_CAPTION
                          | WS_MINIMIZEBOX |  WS_MAXIMIZEBOX | WS_THICKFRAME);
            helpers.insert(wid, helper);
        }
    }

    if (window && helper == nullptr) {
        helpers.remove(window->winId());
    }
}

bool FramelessHelper::NativeEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
    Q_UNUSED(eventType)
    auto lpMsg = (LPMSG)message;

    auto wid = reinterpret_cast<WId>(lpMsg->hwnd);

    if (auto helper = helpers.value(wid)) {
        return helper->nativeEventFilter(eventType, message, result);
    }

    return false;
}
FramelessHelper::NativeEventFilter *FramelessHelper::NativeEventFilter::instance = nullptr;
QHash<WId, FramelessHelper*> FramelessHelper::NativeEventFilter::helpers;

#else
void FramelessHelper::classBegin()
{

}

void FramelessHelper::componentComplete()
{

}

void FramelessHelper::NativeEventFilter::deliver(QQuickWindow *window, FramelessHelper *helper)
{
    Q_UNUSED(window)
    Q_UNUSED(helper)
}

bool FramelessHelper::NativeEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
    Q_UNUSED(eventType)
    Q_UNUSED(message)
    Q_UNUSED(result)
    return false;
}

bool FramelessHelper::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
    Q_UNUSED(eventType)
    Q_UNUSED(message)
    Q_UNUSED(result)
    return false;
}

#endif

导入FramelessHelper 1.0 导入 QtQuick 2.15 导入 QtQuick.Window 2.15 导入 QtQuick.Controls 2.15

ApplicationWindow {
    id: window
    readonly property real borderWidth: 7
    visible: true
    width: 1024
    height: 800
    minimumWidth: 800
    minimumHeight: 600
    flags: Qt.Window | Qt.FramelessWindowHint
    FramelessHelper {
        titlebar: menuBar
        borderWidth: window.borderWidth
    }

    menuBar: Titlebar {  }
}

使用 QWindow::startSystemMove() and Window::startSystemResize(Qt::Edges edges) 而不是手动更改 window 的位置和尺寸。

在您的标题栏中按下鼠标时调用: onPressed: Window.window.startSystemMove() // or windowId.startSystemMove()