c++windows-10qmlqt5aero-snap

Qt5 : Aero Snap during title bar customization on Windows 10


When you customized the title bar on Windows 10, did anyone succeed in working with Aero Snap?

The basic title bar is too ugly.

I haven't solved this problem for a week. Please let me know if there is a solution, whether it is Widget or Qt Quick.


I solved the problem. I apologize if my question was rude. I am not good at English.

At first, I was going to implement Frameless Windows with QWidget and embed QQuickWidget on top of it, but I found a better way.

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

import FramelessHelper 1.0 import QtQuick 2.15 import QtQuick.Window 2.15 import 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 {  }
}

Solution

  • Use QWindow::startSystemMove() and Window::startSystemResize(Qt::Edges edges) instead of manually changing the position and dimension of your window.

    In your title bar call this when the mouse is pressed: onPressed: Window.window.startSystemMove() // or windowId.startSystemMove()