Add Qt Quick example.

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2020-05-08 14:19:41 +08:00
parent 0b84006b14
commit 7753161f9f
21 changed files with 414 additions and 727 deletions

View File

@ -6,21 +6,19 @@
## Features ## Features
- Frameless but have frame shadow (especially on Windows). - Frameless but have frame shadow.
- Draggable and resizeable. - Draggable and resizeable.
- Automatically high DPI scaling. - Automatically high DPI scaling.
- Multi-monitor support (different resolution and DPI). - Multi-monitor support (different resolution and DPI).
- Have animations when minimizing and maximizing (Windows). - Have animations when minimizing and maximizing.
- Tiled/Stack windows by DWM (Windows). - Tiled/Stack windows by DWM.
- Won't cover the task bar when maximized (Windows). - Won't cover the task bar when maximized.
- Won't block the auto-hide task bar when maximized (Windows). - Won't block the auto-hide task bar when maximized.
- No flickers when resizing (Windows). - No flickers when resizing.
- Load all APIs at run-time, no need to link to any system libraries directly (Windows). - Load all APIs at run-time, no need to link to any system libraries directly.
## Usage ## Usage
### Windows
```cpp ```cpp
// include other files ... // include other files ...
#include "winnativeeventfilter.h" #include "winnativeeventfilter.h"
@ -37,7 +35,9 @@ int main(int argc, char *argv[]) {
} }
``` ```
#### Ignore areas and etc Please refer to [**main.cpp**](/main.cpp) for more detailed information.
### Ignore areas and etc
```cpp ```cpp
WinNativeEventFilter::WINDOWDATA data; WinNativeEventFilter::WINDOWDATA data;
@ -73,43 +73,15 @@ data.borderHeight = 5;
data.titlebarHeight = 30; data.titlebarHeight = 30;
``` ```
### Linux and macOS
```cpp
// include other files ...
#include "framelesshelper.h"
// include other files ...
// Anywhere you like, we use the main function here.
int main(int argc, char *argv[]) {
// ...
QWidget widget;
FramelessHelper helper;
// Do this before the widget is shown.
// Only QWidget and QWindow are supported.
helper.setFramelessWindows({&widget});
widget.show();
// ...
}
```
Notes
- The `setFramelessWindows`/`addFramelessWindow` function must not be called after the widget is shown. However, for `QWindow`, it must be called after they are shown.
- I use `startSystemMove` and `startSystemResize` which are only available since Qt 5.15 for moving and resizing frameless windows on UNIX platforms, so if your Qt version is below that, you can't compile this code. I'm sorry for it but using the two functions is the easiest way to achieve this.
- Any widgets (or Qt Quick elements) in the titlebar or resize area will not respond to any mouse events because they are intercepted by my code. If you want to add somewhere or something to the ignore list, try change the window data's `ignoreAreas`, `draggableAreas`, `ignoreObjects` and `draggableObjects`.
- Only top level windows can be frameless. Applying this code to child windows or widgets or even something that is not a window will result in unexpected behavior. Don't try to do this!
- If you want to use your own border width, border height, titlebar height or maximum/minimum window size, just use the original numbers, no need to scale them according to DPI, this code will do the scaling automatically.
## Supported Platforms ## Supported Platforms
- Windows 7 ~ 10 Windows 7 ~ 10, 32 bit & 64 bit
- Should work on X11, Wayland and macOS, but not tested.
## Notes for Windows developers The code itself should be able to work on Windows Vista in theory, but Qt has drop Vista support long time ago.
- The `FramelessHelper` class is just a simple wrapper of the `WinNativeEventFilter` class and the former is mainly designed to work on UNIX platforms. So if you are developing Windows applications, you should use the latter instead. ## Notes for developers
- **As you may have found, if you use this code, the resize areas will be inside the frameless window, however, a normal Win32 window can be resized outside of it.** Here is the reason: the `WS_THICKFRAME` window style will cause a window has three transparent areas beside the window's left, right and bottom edge. Their width/height is 8px if the window is not scaled. In most cases, they are totally invisible. It's DWM's responsibility to draw and control them. They exist to let the user resize the window, visually outside of it. They are in the window area, but not the client area, so they are in the non-client area actually. But we have turned the whole window area into client area in `WM_NCCALCSIZE`, so the three transparent resize areas also become a part of the client area and thus they become visible. When we resize the window, it looks like we are resizing inside of it, however, that's because the transparent resize areas are visible now, we ARE resizing outside of the window actually. But I don't know how to make them become transparent again without breaking the frame shadow drawn by DWM. If you really want to solve it, you can try to embed your window into a larger transparent window and draw the frame shadow yourself. [See the discussions here](https://github.com/wangwenx190/framelesshelper/issues/3) for more detailed information.
- As you may have found, if you use this code, the resize areas will be inside the frameless window, however, a normal Win32 window can be resized outside of it. Here is the reason: the `WS_THICKFRAME` window style will cause a window has three transparent areas beside the window's left, right and bottom edge. Their width/height is 8px if the window is not scaled. In most cases, they are totally invisible. It's DWM's responsibility to draw and control them. They exist to let the user resize the window, visually outside of it. They are in the window area, but not the client area, so they are in the non-client area actually. But we have turned the whole window area into client area in `WM_NCCALCSIZE`, so the three transparent resize areas also become a part of the client area and thus they become visible. When we resize the window, it looks like we are resizing inside of it, however, that's because the transparent resize areas are visible now, we ARE resizing outside of the window actually. But I don't know how to make them become transparent again without breaking the frame shadow drawn by DWM. If you really want to solve it, you can try to embed your window into a larger transparent window and draw the frame shadow yourself. [See the discussions here](https://github.com/wangwenx190/framelesshelper/issues/3) for more detailed information.
- Don't change the window flags (for example, enable the Qt::FramelessWindowHint flag) because it will break the functionality of this code. I'll get rid of the window frame (including the titlebar of course) in Win32 native events. - Don't change the window flags (for example, enable the Qt::FramelessWindowHint flag) because it will break the functionality of this code. I'll get rid of the window frame (including the titlebar of course) in Win32 native events.
- All traditional Win32 APIs are replaced by their DPI-aware ones, if there is one. - All traditional Win32 APIs are replaced by their DPI-aware ones, if there is one.
- Starting from Windows 10, normal windows usually have a one pixel width border line. After many times of trying, I still can't preserve it if I want to remove the window frame. I don't know how to solve this currently. If you really need it, you have to draw one manually by yourself. [See the discussions here](https://github.com/wangwenx190/framelesshelper/issues/3) for more detailed information. - Starting from Windows 10, normal windows usually have a one pixel width border line. After many times of trying, I still can't preserve it if I want to remove the window frame. I don't know how to solve this currently. If you really need it, you have to draw one manually by yourself. [See the discussions here](https://github.com/wangwenx190/framelesshelper/issues/3) for more detailed information.
@ -118,7 +90,7 @@ Notes
- The border width (8 if not scaled), border height (8 if not scaled) and titlebar height (30 if not scaled) are acquired by Win32 APIs and are the same with other standard windows, and thus you should not modify them. Only modify them when you really have a good reason to do so. - The border width (8 if not scaled), border height (8 if not scaled) and titlebar height (30 if not scaled) are acquired by Win32 APIs and are the same with other standard windows, and thus you should not modify them. Only modify them when you really have a good reason to do so.
- You can also copy all the code to `[virtual protected] bool QWidget::nativeEvent(const QByteArray &eventType, void *message, long *result)` or `[virtual protected] bool QWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)`, it's the same with install a native event filter to the application. - You can also copy all the code to `[virtual protected] bool QWidget::nativeEvent(const QByteArray &eventType, void *message, long *result)` or `[virtual protected] bool QWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)`, it's the same with install a native event filter to the application.
## References for Windows developers ## References for developers
### Microsoft Docs ### Microsoft Docs

View File

@ -1,522 +0,0 @@
/*
* MIT License
*
* Copyright (C) 2020 by wangwenx190 (Yuhang Zhao)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "framelesshelper.h"
#include <QDebug>
#include <QGuiApplication>
#include <QMargins>
#include <QVariant>
#ifdef QT_WIDGETS_LIB
#include <QWidget>
#endif
#include <QWindow>
#include <qpa/qplatformnativeinterface.h>
#ifdef Q_OS_WINDOWS
#include "winnativeeventfilter.h"
#else
#include <QEvent>
#include <QMouseEvent>
#ifdef QT_WIDGETS_LIB
#include <QStyle>
#include <QStyleOption>
#endif
#include <QTouchEvent>
#endif
Q_DECLARE_METATYPE(QMargins)
FramelessHelper::FramelessHelper(QObject *parent) : QObject(parent) {
connect(this, &FramelessHelper::titlebarHeightChanged, this,
&FramelessHelper::updateQtFrame_internal);
connect(this, &FramelessHelper::framelessWindowsChanged,
[this]() { updateQtFrame_internal(m_titlebarHeight); });
#ifdef Q_OS_WINDOWS
m_borderWidth = WinNativeEventFilter::getSystemMetric(
nullptr, WinNativeEventFilter::SystemMetric::BorderWidth, false);
m_borderHeight = WinNativeEventFilter::getSystemMetric(
nullptr, WinNativeEventFilter::SystemMetric::BorderHeight, false);
m_titlebarHeight = WinNativeEventFilter::getSystemMetric(
nullptr, WinNativeEventFilter::SystemMetric::TitleBarHeight, false);
#else
// TODO: The default border width and height on Windows is 8 pixels if DPI
// is 96. Don't know how to acquire these values on UNIX platforms.
m_borderWidth = 8;
m_borderHeight = 8;
#ifdef QT_WIDGETS_LIB
QWidget widget;
QStyleOption styleOption;
styleOption.initFrom(&widget);
m_titlebarHeight =
widget.style()->pixelMetric(QStyle::PixelMetric::PM_TitleBarHeight,
&styleOption) +
m_borderHeight;
#else
m_titlebarHeight = 30 + m_borderHeight;
#endif
qDebug().noquote() << "Window device pixel ratio:"
<< widget.devicePixelRatioF();
qDebug().noquote() << "Window border width:" << m_borderWidth
<< "Window border height:" << m_borderHeight
<< "Window titlebar height:" << m_titlebarHeight;
#endif
updateQtFrame_internal(m_titlebarHeight);
}
void FramelessHelper::updateQtFrame(QWindow *window, int titlebarHeight) {
if (window && (titlebarHeight > 0)) {
// Reduce top frame to zero since we paint it ourselves. Use
// device pixel to avoid rounding errors.
const QMargins margins = {0, -titlebarHeight, 0, 0};
const QVariant marginsVar = QVariant::fromValue(margins);
// The dynamic property takes effect when creating the platform
// window.
window->setProperty("_q_windowsCustomMargins", marginsVar);
// If a platform window exists, change via native interface.
QPlatformWindow *platformWindow = window->handle();
if (platformWindow) {
QGuiApplication::platformNativeInterface()->setWindowProperty(
platformWindow, QString::fromUtf8("WindowsCustomMargins"),
marginsVar);
}
}
}
int FramelessHelper::borderWidth() const { return m_borderWidth; }
void FramelessHelper::setBorderWidth(int val) {
if (m_borderWidth != val) {
m_borderWidth = val;
#ifdef Q_OS_WINDOWS
WinNativeEventFilter::setBorderWidth(val);
#endif
Q_EMIT borderWidthChanged(val);
}
}
int FramelessHelper::borderHeight() const { return m_borderHeight; }
void FramelessHelper::setBorderHeight(int val) {
if (m_borderHeight != val) {
m_borderHeight = val;
#ifdef Q_OS_WINDOWS
WinNativeEventFilter::setBorderHeight(val);
#endif
Q_EMIT borderHeightChanged(val);
}
}
int FramelessHelper::titlebarHeight() const { return m_titlebarHeight; }
void FramelessHelper::setTitlebarHeight(int val) {
if (m_titlebarHeight != val) {
m_titlebarHeight = val;
#ifdef Q_OS_WINDOWS
WinNativeEventFilter::setTitlebarHeight(val);
#endif
Q_EMIT titlebarHeightChanged(val);
}
}
FramelessHelper::Areas FramelessHelper::ignoreAreas() const {
return m_ignoreAreas;
}
void FramelessHelper::setIgnoreAreas(const Areas &val) {
if (m_ignoreAreas != val) {
m_ignoreAreas = val;
#ifdef Q_OS_WINDOWS
auto iter = val.cbegin();
while (iter != val.cend()) {
if (iter.key()) {
const auto hwnd =
static_cast<HWND>(getWindowRawHandle(iter.key()));
if (hwnd) {
const auto data = WinNativeEventFilter::windowData(hwnd);
data->ignoreAreas = iter.value();
}
}
++iter;
}
#endif
Q_EMIT ignoreAreasChanged(val);
}
}
FramelessHelper::Areas FramelessHelper::draggableAreas() const {
return m_draggableAreas;
}
void FramelessHelper::setDraggableAreas(const Areas &val) {
if (m_draggableAreas != val) {
m_draggableAreas = val;
#ifdef Q_OS_WINDOWS
auto iter = val.cbegin();
while (iter != val.cend()) {
if (iter.key()) {
const auto hwnd =
static_cast<HWND>(getWindowRawHandle(iter.key()));
if (hwnd) {
const auto data = WinNativeEventFilter::windowData(hwnd);
data->draggableAreas = iter.value();
}
}
++iter;
}
#endif
Q_EMIT draggableAreasChanged(val);
}
}
QVector<QObject *> FramelessHelper::framelessWindows() const {
return m_framelessWindows;
}
void FramelessHelper::setFramelessWindows(const QVector<QObject *> &val) {
if (m_framelessWindows != val) {
m_framelessWindows = val;
if (!val.isEmpty()) {
for (auto &&object : qAsConst(val)) {
if (object) {
#ifdef Q_OS_WINDOWS
const auto hwnd =
static_cast<HWND>(getWindowRawHandle(object));
if (hwnd) {
WinNativeEventFilter::addFramelessWindow(hwnd);
} else {
qWarning().noquote()
<< "Can't make the window frameless: failed to "
"acquire the window handle.";
}
#else
// Don't miss the Qt::Window flag.
const Qt::WindowFlags flags =
Qt::Window | Qt::FramelessWindowHint;
QWindow *window = getWindowHandle(object);
if (window) {
window->setFlags(flags);
// MouseTracking is always enabled for QWindow.
window->installEventFilter(this);
}
#ifdef QT_WIDGETS_LIB
else {
const auto widget = qobject_cast<QWidget *>(object);
if (widget) {
widget->setWindowFlags(flags);
// We can't get MouseMove events if MouseTracking is
// disabled.
widget->setMouseTracking(true);
widget->installEventFilter(this);
}
}
#endif
#endif
}
}
}
Q_EMIT framelessWindowsChanged(val);
}
}
#ifndef Q_OS_WINDOWS
bool FramelessHelper::eventFilter(QObject *object, QEvent *event) {
const auto isWindowTopLevel = [](QObject *window) -> bool {
if (window) {
if (window->isWindowType()) {
return qobject_cast<QWindow *>(window)->isTopLevel();
}
#ifdef QT_WIDGETS_LIB
else if (window->isWidgetType()) {
return qobject_cast<QWidget *>(window)->isTopLevel();
}
#endif
}
return false;
};
if (!object || !isWindowTopLevel(object)) {
event->ignore();
return false;
}
const auto getWindowEdges = [this](const QPointF &point, int ww,
int wh) -> Qt::Edges {
if (point.y() < m_borderHeight) {
if (point.x() < m_borderWidth) {
return Qt::Edge::TopEdge | Qt::Edge::LeftEdge;
}
if (point.x() > (ww - m_borderWidth)) {
return Qt::Edge::TopEdge | Qt::Edge::RightEdge;
}
return Qt::Edge::TopEdge;
}
if (point.y() > (wh - m_borderHeight)) {
if (point.x() < m_borderWidth) {
return Qt::Edge::BottomEdge | Qt::Edge::LeftEdge;
}
if (point.x() > (ww - m_borderWidth)) {
return Qt::Edge::BottomEdge | Qt::Edge::RightEdge;
}
return Qt::Edge::BottomEdge;
}
if (point.x() < m_borderWidth) {
return Qt::Edge::LeftEdge;
}
if (point.x() > (ww - m_borderWidth)) {
return Qt::Edge::RightEdge;
}
return {};
};
const auto getCursorShape = [](Qt::Edges edges) -> Qt::CursorShape {
if ((edges.testFlag(Qt::Edge::TopEdge) &&
edges.testFlag(Qt::Edge::LeftEdge)) ||
(edges.testFlag(Qt::Edge::BottomEdge) &&
edges.testFlag(Qt::Edge::RightEdge))) {
return Qt::CursorShape::SizeFDiagCursor;
}
if ((edges.testFlag(Qt::Edge::TopEdge) &&
edges.testFlag(Qt::Edge::RightEdge)) ||
(edges.testFlag(Qt::Edge::BottomEdge) &&
edges.testFlag(Qt::Edge::LeftEdge))) {
return Qt::CursorShape::SizeBDiagCursor;
}
if (edges.testFlag(Qt::Edge::TopEdge) ||
edges.testFlag(Qt::Edge::BottomEdge)) {
return Qt::CursorShape::SizeVerCursor;
}
if (edges.testFlag(Qt::Edge::LeftEdge) ||
edges.testFlag(Qt::Edge::RightEdge)) {
return Qt::CursorShape::SizeHorCursor;
}
return Qt::CursorShape::ArrowCursor;
};
const auto isInSpecificAreas = [](int x, int y,
const QVector<QRect> &areas) -> bool {
for (auto &&area : qAsConst(areas)) {
if (area.contains(x, y, true)) {
return true;
}
}
return false;
};
const auto isInTitlebarArea =
[this, &isInSpecificAreas](const QPointF &point,
QObject *window) -> bool {
if (window) {
return (point.y() < m_titlebarHeight) &&
!isInSpecificAreas(point.x(), point.y(),
m_ignoreAreas.value(window)) &&
(m_draggableAreas.isEmpty()
? true
: isInSpecificAreas(point.x(), point.y(),
m_draggableAreas.value(window)));
}
return false;
};
const auto moveOrResize = [this, &getWindowEdges, &isInTitlebarArea](
const QPointF &point, QObject *object) {
QWindow *window = getWindowHandle(object);
if (window) {
const Qt::Edges edges =
getWindowEdges(point, window->width(), window->height());
if (edges == Qt::Edges{}) {
if (isInTitlebarArea(point, object)) {
window->startSystemMove();
}
} else {
if (window->windowStates().testFlag(
Qt::WindowState::WindowNoState)) {
window->startSystemResize(edges);
}
}
} else {
qWarning().noquote() << "Can't move or resize the window: failed "
"to acquire the window handle.";
}
};
switch (event->type()) {
case QEvent::MouseButtonDblClick: {
const auto mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent) {
if (mouseEvent->button() != Qt::MouseButton::LeftButton) {
break;
}
if (isInTitlebarArea(mouseEvent->localPos(), object)) {
// FIXME: If the current object is a QWidget, we can use
// getWindowHandle(object) to get the window handle, but if we
// call showMaximized() of that window, it will not be
// maximized, it will be moved to the top-left edge of the
// screen without changing it's size instead. Why? Convert the
// object to QWidget and call showMaximized() doesn't have this
// issue.
if (object->isWindowType()) {
const auto window = qobject_cast<QWindow *>(object);
if (window) {
if (window->windowStates().testFlag(
Qt::WindowState::WindowFullScreen)) {
break;
}
if (window->windowStates().testFlag(
Qt::WindowState::WindowMaximized)) {
window->showNormal();
} else {
window->showMaximized();
}
window->setCursor(Qt::CursorShape::ArrowCursor);
}
}
#ifdef QT_WIDGETS_LIB
else if (object->isWidgetType()) {
const auto widget = qobject_cast<QWidget *>(object);
if (widget) {
if (widget->isFullScreen()) {
break;
}
if (widget->isMaximized()) {
widget->showNormal();
} else {
widget->showMaximized();
}
widget->setCursor(Qt::CursorShape::ArrowCursor);
}
}
#endif
}
}
break;
}
case QEvent::MouseButtonPress: {
const auto mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent) {
if (mouseEvent->button() != Qt::MouseButton::LeftButton) {
break;
}
moveOrResize(mouseEvent->localPos(), object);
}
break;
}
case QEvent::MouseMove: {
const auto mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent) {
QWindow *window = getWindowHandle(object);
if (window) {
if (window->windowStates().testFlag(
Qt::WindowState::WindowNoState)) {
window->setCursor(getCursorShape(
getWindowEdges(mouseEvent->localPos(), window->width(),
window->height())));
}
}
#ifdef QT_WIDGETS_LIB
else {
const auto widget = qobject_cast<QWidget *>(object);
if (widget) {
if (!widget->isMinimized() && !widget->isMaximized() &&
!widget->isFullScreen()) {
widget->setCursor(getCursorShape(
getWindowEdges(mouseEvent->localPos(),
widget->width(), widget->height())));
}
}
}
#endif
}
break;
}
case QEvent::TouchBegin:
case QEvent::TouchUpdate: {
moveOrResize(
static_cast<QTouchEvent *>(event)->touchPoints().first().pos(),
object);
break;
}
default: {
break;
}
}
event->ignore();
return false;
}
#endif
QWindow *FramelessHelper::getWindowHandle(QObject *val) {
if (val) {
const auto validWindow = [](QWindow *window) -> QWindow * {
return (window && window->handle()) ? window : nullptr;
};
if (val->isWindowType()) {
return validWindow(qobject_cast<QWindow *>(val));
}
#ifdef QT_WIDGETS_LIB
else if (val->isWidgetType()) {
const auto widget = qobject_cast<QWidget *>(val);
if (widget) {
return validWindow(widget->windowHandle());
}
}
#endif
else {
qWarning().noquote() << "Can't acquire the window handle: only "
"QWidget and QWindow are accepted.";
}
}
return nullptr;
}
void FramelessHelper::updateQtFrame_internal(int val) {
if (!m_framelessWindows.isEmpty()) {
for (auto &&object : qAsConst(m_framelessWindows)) {
QWindow *window = getWindowHandle(object);
if (window) {
updateQtFrame(window, val);
} else {
qWarning().noquote() << "Can't modify the window frame: failed "
"to acquire the window handle.";
}
}
}
}
#ifdef Q_OS_WINDOWS
void *FramelessHelper::getWindowRawHandle(QObject *object) {
if (object) {
QWindow *window = getWindowHandle(object);
if (window) {
const auto handle = QGuiApplication::platformNativeInterface()
->nativeResourceForWindow("handle", window);
if (handle) {
return handle;
}
}
#ifdef QT_WIDGETS_LIB
else {
const auto widget = qobject_cast<QWidget *>(object);
if (widget) {
return reinterpret_cast<void *>(widget->winId());
}
}
#endif
}
return nullptr;
}
#endif

View File

@ -1,103 +0,0 @@
/*
* MIT License
*
* Copyright (C) 2020 by wangwenx190 (Yuhang Zhao)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <QHash>
#include <QObject>
#include <QRect>
#include <QVector>
QT_BEGIN_NAMESPACE
QT_FORWARD_DECLARE_CLASS(QWindow)
QT_END_NAMESPACE
class FramelessHelper : public QObject {
Q_OBJECT
Q_DISABLE_COPY_MOVE(FramelessHelper)
Q_PROPERTY(int borderWidth READ borderWidth WRITE setBorderWidth NOTIFY
borderWidthChanged)
Q_PROPERTY(int borderHeight READ borderHeight WRITE setBorderHeight NOTIFY
borderHeightChanged)
Q_PROPERTY(int titlebarHeight READ titlebarHeight WRITE setTitlebarHeight
NOTIFY titlebarHeightChanged)
Q_PROPERTY(Areas ignoreAreas READ ignoreAreas WRITE setIgnoreAreas NOTIFY
ignoreAreasChanged)
Q_PROPERTY(Areas draggableAreas READ draggableAreas WRITE setDraggableAreas
NOTIFY draggableAreasChanged)
Q_PROPERTY(QVector<QObject *> framelessWindows READ framelessWindows WRITE
setFramelessWindows NOTIFY framelessWindowsChanged)
public:
using Areas = QHash<QObject *, QVector<QRect>>;
explicit FramelessHelper(QObject *parent = nullptr);
~FramelessHelper() override = default;
static void updateQtFrame(QWindow *window, int titlebarHeight);
int borderWidth() const;
void setBorderWidth(int val);
int borderHeight() const;
void setBorderHeight(int val);
int titlebarHeight() const;
void setTitlebarHeight(int val);
Areas ignoreAreas() const;
void setIgnoreAreas(const Areas &val);
Areas draggableAreas() const;
void setDraggableAreas(const Areas &val);
QVector<QObject *> framelessWindows() const;
void setFramelessWindows(const QVector<QObject *> &val);
protected:
#ifndef Q_OS_WINDOWS
bool eventFilter(QObject *object, QEvent *event) override;
#endif
private:
QWindow *getWindowHandle(QObject *val);
#ifdef Q_OS_WINDOWS
void *getWindowRawHandle(QObject *object);
#endif
void updateQtFrame_internal(int val);
Q_SIGNALS:
void borderWidthChanged(int);
void borderHeightChanged(int);
void titlebarHeightChanged(int);
void ignoreAreasChanged(const Areas &);
void draggableAreasChanged(const Areas &);
void framelessWindowsChanged(const QVector<QObject *> &);
private:
int m_borderWidth = -1, m_borderHeight = -1, m_titlebarHeight = -1;
Areas m_ignoreAreas, m_draggableAreas;
QVector<QObject *> m_framelessWindows;
};

View File

@ -1,16 +1,11 @@
TARGET = framelesswidget TARGET = framelessapplication
TEMPLATE = app TEMPLATE = app
QT += gui-private widgets QT += gui-private widgets quick
CONFIG += c++17 strict_c++ utf8_source warn_on CONFIG += c++17 strict_c++ utf8_source warn_on windeployqt
win32 { DEFINES += WIN32_LEAN_AND_MEAN QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII
DEFINES += WIN32_LEAN_AND_MEAN CONFIG -= embed_manifest_exe
CONFIG += windeployqt RC_FILE = resources.rc
CONFIG -= embed_manifest_exe HEADERS += winnativeeventfilter.h
RC_FILE = resources.rc SOURCES += winnativeeventfilter.cpp main.cpp
HEADERS += winnativeeventfilter.h RESOURCES += resources.qrc
SOURCES += winnativeeventfilter.cpp OTHER_FILES += manifest.xml
OTHER_FILES += manifest.xml
}
DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII
HEADERS += framelesshelper.h
SOURCES += framelesshelper.cpp main.cpp

148
main.cpp
View File

@ -1,7 +1,59 @@
#include "winnativeeventfilter.h"
#include <QApplication> #include <QApplication>
#include <QHBoxLayout>
#include <QLabel>
#include <QMargins>
#include <QPushButton>
#include <QQmlContext>
#include <QQuickItem>
#include <QQuickView>
#include <QVBoxLayout>
#include <QWidget> #include <QWidget>
#include <qpa/qplatformnativeinterface.h>
#include "framelesshelper.h" Q_DECLARE_METATYPE(QMargins)
static const int m_defaultTitleBarHeight = 30;
static const int m_defaultButtonWidth = 45;
static void updateQtFrame(QWindow *const window, const int titleBarHeight) {
if (window && (titleBarHeight > 0)) {
// Reduce top frame to zero since we paint it ourselves. Use
// device pixel to avoid rounding errors.
const QMargins margins = {0, -titleBarHeight, 0, 0};
const QVariant marginsVar = QVariant::fromValue(margins);
// The dynamic property takes effect when creating the platform
// window.
window->setProperty("_q_windowsCustomMargins", marginsVar);
// If a platform window exists, change via native interface.
QPlatformWindow *platformWindow = window->handle();
if (platformWindow) {
QGuiApplication::platformNativeInterface()->setWindowProperty(
platformWindow, QString::fromUtf8("WindowsCustomMargins"),
marginsVar);
}
}
}
class MyQuickView : public QQuickView {
Q_OBJECT
Q_DISABLE_COPY_MOVE(MyQuickView)
public:
explicit MyQuickView(QWindow *parent = nullptr) : QQuickView(parent) {
setResizeMode(QQuickView::ResizeMode::SizeRootObjectToView);
}
~MyQuickView() override = default;
protected:
void resizeEvent(QResizeEvent *event) override {
QQuickView::resizeEvent(event);
Q_EMIT windowSizeChanged(event->size());
}
Q_SIGNALS:
void windowSizeChanged(const QSize &);
};
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// High DPI scaling is enabled by default from Qt 6 // High DPI scaling is enabled by default from Qt 6
@ -32,11 +84,99 @@ int main(int argc, char *argv[]) {
QApplication application(argc, argv); QApplication application(argc, argv);
FramelessHelper helper; // Qt Widgets example:
QWidget widget; QWidget widget;
helper.setFramelessWindows({&widget}); widget.setContentsMargins(0, 0, 0, 0);
QLabel label;
label.setText(QObject::tr("Hello, World!"));
QObject::connect(&widget, &QWidget::windowTitleChanged, &label,
&QLabel::setText);
QPushButton minimizeButton;
minimizeButton.setText(QObject::tr("Minimize"));
QObject::connect(&minimizeButton, &QPushButton::clicked, &widget,
&QWidget::showMinimized);
QPushButton maximizeButton;
maximizeButton.setText(QObject::tr("Maximize"));
QObject::connect(&maximizeButton, &QPushButton::clicked,
[&widget, &maximizeButton]() {
if (widget.isMaximized()) {
widget.showNormal();
maximizeButton.setText(QObject::tr("Maximize"));
} else {
widget.showMaximized();
maximizeButton.setText(QObject::tr("Restore"));
}
});
QPushButton closeButton;
closeButton.setText(QObject::tr("Close"));
QObject::connect(&closeButton, &QPushButton::clicked, &widget,
&QWidget::close);
QHBoxLayout tbLayout;
tbLayout.setContentsMargins(0, 0, 0, 0);
tbLayout.setSpacing(0);
tbLayout.addSpacing(15);
tbLayout.addWidget(&label);
tbLayout.addStretch();
tbLayout.addWidget(&minimizeButton);
tbLayout.addWidget(&maximizeButton);
tbLayout.addWidget(&closeButton);
QVBoxLayout mainLayout;
mainLayout.setContentsMargins(0, 0, 0, 0);
mainLayout.setSpacing(0);
mainLayout.addLayout(&tbLayout);
mainLayout.addStretch();
widget.setLayout(&mainLayout);
WinNativeEventFilter::WINDOWDATA data_widget;
data_widget.ignoreObjects << &minimizeButton << &maximizeButton
<< &closeButton;
const auto hWnd_widget = reinterpret_cast<HWND>(widget.winId());
const int tbh_widget = WinNativeEventFilter::getSystemMetric(
hWnd_widget, WinNativeEventFilter::SystemMetric::TitleBarHeight, false);
updateQtFrame(widget.windowHandle(),
(tbh_widget > 0 ? tbh_widget : m_defaultTitleBarHeight));
widget.resize(800, 600);
WinNativeEventFilter::addFramelessWindow(hWnd_widget, &data_widget, true);
widget.show(); widget.show();
// Qt Quick example:
MyQuickView view;
const auto hWnd_qml = reinterpret_cast<HWND>(view.winId());
const int tbh_qml_sys = WinNativeEventFilter::getSystemMetric(
hWnd_qml, WinNativeEventFilter::SystemMetric::TitleBarHeight, false);
const int tbh_qml = tbh_qml_sys > 0 ? tbh_qml_sys : m_defaultTitleBarHeight;
updateQtFrame(&view, tbh_qml);
view.rootContext()->setContextProperty(QString::fromUtf8("$TitleBarHeight"),
tbh_qml);
view.setSource(QUrl(QString::fromUtf8("qrc:///qml/main.qml")));
QObject::connect(
&view, &MyQuickView::windowSizeChanged, [hWnd_qml](const QSize &size) {
const auto data = WinNativeEventFilter::windowData(hWnd_qml);
if (data) {
const int tbh_qml = WinNativeEventFilter::getSystemMetric(
hWnd_qml,
WinNativeEventFilter::SystemMetric::TitleBarHeight, false);
data->draggableAreas = {
{0, 0, (size.width() - (m_defaultButtonWidth * 3)),
tbh_qml}};
}
});
const QQuickItem *const rootObject = view.rootObject();
Q_ASSERT(rootObject);
// We can't use the Qt5 syntax here because we can't get the function
// pointers of the signals written in QML.
QObject::connect(rootObject, SIGNAL(minimizeButtonClicked()), &view,
SLOT(showMinimized()));
QObject::connect(rootObject, SIGNAL(maximizeButtonClicked()), &view,
SLOT(showMaximized()));
QObject::connect(rootObject, SIGNAL(restoreButtonClicked()), &view,
SLOT(showNormal()));
QObject::connect(rootObject, SIGNAL(closeButtonClicked()), &view,
SLOT(close()));
view.resize(800, 600);
WinNativeEventFilter::addFramelessWindow(hWnd_qml, nullptr, true);
view.show();
return QApplication::exec(); return QApplication::exec();
} }
#include "main.moc"

18
resources.qrc Normal file
View File

@ -0,0 +1,18 @@
<RCC>
<qresource prefix="/qml">
<file alias="main.qml">resources/qml/main.qml</file>
<file alias="MinimizeButton.qml">resources/qml/MinimizeButton.qml</file>
<file alias="MaximizeButton.qml">resources/qml/MaximizeButton.qml</file>
<file alias="CloseButton.qml">resources/qml/CloseButton.qml</file>
</qresource>
<qresource prefix="/images">
<file alias="button_minimize_black.svg">resources/images/button_minimize_black.svg</file>
<file alias="button_minimize_white.svg">resources/images/button_minimize_white.svg</file>
<file alias="button_maximize_black.svg">resources/images/button_maximize_black.svg</file>
<file alias="button_maximize_white.svg">resources/images/button_maximize_white.svg</file>
<file alias="button_restore_black.svg">resources/images/button_restore_black.svg</file>
<file alias="button_restore_white.svg">resources/images/button_restore_white.svg</file>
<file alias="button_close_black.svg">resources/images/button_close_black.svg</file>
<file alias="button_close_white.svg">resources/images/button_close_white.svg</file>
</qresource>
</RCC>

View File

@ -21,16 +21,16 @@ BEGIN
BEGIN BEGIN
BLOCK "040904b0" BLOCK "040904b0"
BEGIN BEGIN
VALUE "Comments", "Built by Qt Toolkit." VALUE "Comments", "Built by the Qt Toolkit."
VALUE "CompanyName", "wangwenx190" VALUE "CompanyName", "wangwenx190"
VALUE "FileDescription", "A frameless widget based Qt application." VALUE "FileDescription", "A framelesshelper based Qt application."
VALUE "FileVersion", "1.0.0.0" VALUE "FileVersion", "1.0.0.0"
VALUE "InternalName", "flw-test-app" VALUE "InternalName", "flh-test-app"
VALUE "LegalCopyright", "MIT License" VALUE "LegalCopyright", "MIT License"
#ifdef _DEBUG #ifdef _DEBUG
VALUE "OriginalFilename", "framelesswidgetd.exe" VALUE "OriginalFilename", "framelessapplicationd.exe"
#else #else
VALUE "OriginalFilename", "framelesswidget.exe" VALUE "OriginalFilename", "framelessapplication.exe"
#endif #endif
VALUE "ProductName", "Test Application" VALUE "ProductName", "Test Application"
VALUE "ProductVersion", "1.0.0.0" VALUE "ProductVersion", "1.0.0.0"

View File

@ -0,0 +1,6 @@
<svg width="45" height="30" version="1.1" viewBox="0 0 11.906 7.9375" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="#000" stroke-width=".26458px">
<path d="m4.575 2.7229 2.4917 2.4917z"/>
<path d="m7.0667 2.7229-2.4917 2.4917z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@ -0,0 +1,6 @@
<svg width="45" height="30" version="1.1" viewBox="0 0 11.906 7.9375" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="#fff" stroke-width=".26458px">
<path d="m4.575 2.7229 2.4917 2.4917z"/>
<path d="m7.0667 2.7229-2.4917 2.4917z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@ -0,0 +1,3 @@
<svg width="45" height="30" version="1.1" viewBox="0 0 11.906 7.9375" xmlns="http://www.w3.org/2000/svg">
<rect x="4.6302" y="2.7781" width="2.3813" height="2.3813" fill="none" stroke="#000" stroke-width=".26458"/>
</svg>

After

Width:  |  Height:  |  Size: 223 B

View File

@ -0,0 +1,3 @@
<svg width="45" height="30" version="1.1" viewBox="0 0 11.906 7.9375" xmlns="http://www.w3.org/2000/svg">
<rect x="4.6302" y="2.7781" width="2.3813" height="2.3813" fill="none" stroke="#fff" stroke-width=".26458"/>
</svg>

After

Width:  |  Height:  |  Size: 223 B

View File

@ -0,0 +1,3 @@
<svg width="45" height="30" version="1.1" viewBox="0 0 11.906 7.9375" xmlns="http://www.w3.org/2000/svg">
<path d="m4.7625 4.101h2.6458z" fill="none" stroke="#000" stroke-width=".26458"/>
</svg>

After

Width:  |  Height:  |  Size: 196 B

View File

@ -0,0 +1,3 @@
<svg width="45" height="30" version="1.1" viewBox="0 0 11.906 7.9375" xmlns="http://www.w3.org/2000/svg">
<path d="m4.7625 4.101h2.6458z" fill="none" stroke="#fff" stroke-width=".26458"/>
</svg>

After

Width:  |  Height:  |  Size: 196 B

View File

@ -0,0 +1,11 @@
<svg width="45" height="30" version="1.1" viewBox="0 0 11.906 7.9375" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="#000">
<rect x="4.6302" y="3.3073" width="1.8521" height="1.8521" stroke-width=".26458"/>
<g stroke-width=".26458px">
<path d="m5.1594 3.175v-0.52917z"/>
<path d="m7.0115 2.9104v1.8521z"/>
<path d="m6.6146 4.6302h0.26458z"/>
<path d="m5.2917 2.7781h1.8521z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 426 B

View File

@ -0,0 +1,11 @@
<svg width="45" height="30" version="1.1" viewBox="0 0 11.906 7.9375" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="#fff">
<rect x="4.6302" y="3.3073" width="1.8521" height="1.8521" stroke-width=".26458"/>
<g stroke-width=".26458px">
<path d="m5.1594 3.175v-0.52917z"/>
<path d="m7.0115 2.9104v1.8521z"/>
<path d="m6.6146 4.6302h0.26458z"/>
<path d="m5.2917 2.7781h1.8521z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 426 B

View File

@ -0,0 +1,24 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
Button {
id: button
width: 45
height: 30
ToolTip.visible: hovered && !down
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
ToolTip.text: qsTr("Close")
contentItem: Image {
anchors.fill: parent
source: button.down
|| button.hovered ? "qrc:/images/button_close_white.svg" : "qrc:/images/button_close_black.svg"
}
background: Rectangle {
visible: button.down || button.hovered
color: button.down ? "#8c0a15" : (button.hovered ? "#e81123" : "transparent")
}
}

View File

@ -0,0 +1,25 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
Button {
id: button
width: 45
height: 30
property bool maximized: false
ToolTip.visible: hovered && !down
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
ToolTip.text: maximized ? qsTr("Restore") : qsTr("Maximize")
contentItem: Image {
anchors.fill: parent
source: maximized ? "qrc:/images/button_restore_black.svg" : "qrc:/images/button_maximize_black.svg"
}
background: Rectangle {
visible: button.down || button.hovered
color: button.down ? "#808080" : (button.hovered ? "#c7c7c7" : "transparent")
}
}

View File

@ -0,0 +1,23 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
Button {
id: button
width: 45
height: 30
ToolTip.visible: hovered && !down
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
ToolTip.text: qsTr("Minimize")
contentItem: Image {
anchors.fill: parent
source: "qrc:/images/button_minimize_black.svg"
}
background: Rectangle {
visible: button.down || button.hovered
color: button.down ? "#808080" : (button.hovered ? "#c7c7c7" : "transparent")
}
}

64
resources/qml/main.qml Normal file
View File

@ -0,0 +1,64 @@
import QtQuick 2.15
Item {
id: root
signal minimizeButtonClicked
signal maximizeButtonClicked
signal restoreButtonClicked
signal closeButtonClicked
Rectangle {
id: titleBar
height: $TitleBarHeight
color: "white"
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
Text {
id: titleBarText
text: qsTr("Hello, World!")
font.family: "Noto Sans CJK SC"
font.pointSize: 15
color: "black"
anchors.left: parent.left
anchors.leftMargin: 15
anchors.verticalCenter: parent.verticalCenter
}
Row {
anchors.top: parent.top
anchors.right: parent.right
MinimizeButton {
onClicked: root.minimizeButtonClicked()
}
MaximizeButton {
onClicked: {
if (maximized) {
root.restoreButtonClicked()
maximized = false
} else {
root.maximizeButtonClicked()
maximized = true
}
}
}
CloseButton {
onClicked: root.closeButtonClicked()
}
}
}
Rectangle {
id: content
color: "#f0f0f0"
anchors.top: titleBar.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
}
}

View File

@ -183,7 +183,7 @@ const UINT m_defaultDotsPerInch = USER_DEFAULT_SCREEN_DPI;
const qreal m_defaultDevicePixelRatio = 1.0; const qreal m_defaultDevicePixelRatio = 1.0;
int m_borderWidth = -1, m_borderHeight = -1, m_titlebarHeight = -1; int m_borderWidth = -1, m_borderHeight = -1, m_titleBarHeight = -1;
using HPAINTBUFFER = HANDLE; using HPAINTBUFFER = HANDLE;
@ -1117,24 +1117,32 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
case WM_NCPAINT: { case WM_NCPAINT: {
// 边框阴影处于非客户区的范围,因此如果直接阻止非客户区的绘制,会导致边框阴影丢失 // 边框阴影处于非客户区的范围,因此如果直接阻止非客户区的绘制,会导致边框阴影丢失
if (IsDwmCompositionEnabled()) { if (!IsDwmCompositionEnabled()) {
break;
} else {
// Only block WM_NCPAINT when DWM composition is disabled. If // Only block WM_NCPAINT when DWM composition is disabled. If
// it's blocked when DWM composition is enabled, the frame // it's blocked when DWM composition is enabled, the frame
// shadow won't be drawn. // shadow won't be drawn.
*result = 0; *result = 0;
return true; return true;
} }
break;
} }
case WM_NCACTIVATE: { case WM_NCACTIVATE: {
// DefWindowProc won't repaint the window border if lParam (normally if (IsDwmCompositionEnabled()) {
// a HRGN) is -1. See the following link's "lParam" section: // DefWindowProc won't repaint the window border if lParam
// https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-ncactivate // (normally a HRGN) is -1. See the following link's "lParam"
// Don't use "*result = 0" otherwise the window won't respond to // section:
// the window active state change. // https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-ncactivate
*result = // Don't use "*result = 0" otherwise the window won't respond to
m_lpDefWindowProcW(msg->hwnd, msg->message, msg->wParam, -1); // the window active state change.
*result = m_lpDefWindowProcW(msg->hwnd, msg->message,
msg->wParam, -1);
} else {
if (static_cast<BOOL>(msg->wParam)) {
*result = FALSE;
} else {
*result = TRUE;
}
}
return true; return true;
} }
case WM_NCHITTEST: { case WM_NCHITTEST: {
@ -1280,13 +1288,13 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
mouse.x = GET_X_LPARAM(_lParam); mouse.x = GET_X_LPARAM(_lParam);
mouse.y = GET_Y_LPARAM(_lParam); mouse.y = GET_Y_LPARAM(_lParam);
m_lpScreenToClient(_hWnd, &mouse); m_lpScreenToClient(_hWnd, &mouse);
const RECT frame = GetFrameSizeForWindow(_hWnd, true);
// These values are DPI-aware. // These values are DPI-aware.
const LONG bw = frame.left; // identical to right const LONG bw =
// identical to top, if the latter doesn't include the title bar getSystemMetric(_hWnd, SystemMetric::BorderWidth);
// height const LONG bh =
const LONG bh = frame.bottom; getSystemMetric(_hWnd, SystemMetric::BorderHeight);
const LONG tbh = frame.top; const LONG tbh =
getSystemMetric(_hWnd, SystemMetric::TitleBarHeight);
const qreal dpr = GetDevicePixelRatioForWindow(_hWnd); const qreal dpr = GetDevicePixelRatioForWindow(_hWnd);
const bool isInIgnoreAreas = isInSpecificAreas( const bool isInIgnoreAreas = isInSpecificAreas(
mouse.x, mouse.y, _data->windowData.ignoreAreas, dpr); mouse.x, mouse.y, _data->windowData.ignoreAreas, dpr);
@ -1311,10 +1319,11 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
const bool isInIgnoreObjects = false; const bool isInIgnoreObjects = false;
const bool isInDraggableObjects = true; const bool isInDraggableObjects = true;
#endif #endif
const bool isResizePermitted = !isInIgnoreAreas && const bool isResizePermitted =
isInDraggableAreas && !isInIgnoreObjects && !isInIgnoreAreas && !isInIgnoreObjects;
isInDraggableObjects; const bool isTitlebar = (mouse.y <= tbh) &&
const bool isTitlebar = (mouse.y <= tbh) && isResizePermitted; isInDraggableAreas && isInDraggableObjects &&
isResizePermitted;
if (IsMaximized(_hWnd)) { if (IsMaximized(_hWnd)) {
if (isTitlebar) { if (isTitlebar) {
return HTCAPTION; return HTCAPTION;
@ -1488,8 +1497,8 @@ void WinNativeEventFilter::setBorderWidth(int bw) { m_borderWidth = bw; }
void WinNativeEventFilter::setBorderHeight(int bh) { m_borderHeight = bh; } void WinNativeEventFilter::setBorderHeight(int bh) { m_borderHeight = bh; }
void WinNativeEventFilter::setTitlebarHeight(int tbh) { void WinNativeEventFilter::setTitleBarHeight(int tbh) {
m_titlebarHeight = tbh; m_titleBarHeight = tbh;
} }
void WinNativeEventFilter::updateWindow(HWND handle, bool triggerFrameChange, void WinNativeEventFilter::updateWindow(HWND handle, bool triggerFrameChange,
@ -1545,16 +1554,12 @@ int WinNativeEventFilter::getSystemMetric(HWND handle, SystemMetric metric,
} }
} }
case SystemMetric::TitleBarHeight: { case SystemMetric::TitleBarHeight: {
const int tbh = userData->windowData.titlebarHeight; const int tbh = userData->windowData.titleBarHeight;
if (tbh > 0) { if (tbh > 0) {
return qRound(tbh * dpr); return qRound(tbh * dpr);
} else { } else {
const int result = m_lpGetSystemMetrics(SM_CYSIZEFRAME) + const int result = m_lpGetSystemMetrics(SM_CYCAPTION);
m_lpGetSystemMetrics(SM_CXPADDEDBORDER) +
m_lpGetSystemMetrics(SM_CYCAPTION);
const int result_dpi = const int result_dpi =
GetSystemMetricsForWindow(handle, SM_CYSIZEFRAME) +
GetSystemMetricsForWindow(handle, SM_CXPADDEDBORDER) +
GetSystemMetricsForWindow(handle, SM_CYCAPTION); GetSystemMetricsForWindow(handle, SM_CYCAPTION);
return dpiAware ? result_dpi : result; return dpiAware ? result_dpi : result;
} }
@ -1575,8 +1580,8 @@ int WinNativeEventFilter::getSystemMetric(HWND handle, SystemMetric metric,
break; break;
} }
case SystemMetric::TitleBarHeight: { case SystemMetric::TitleBarHeight: {
if (m_titlebarHeight > 0) { if (m_titleBarHeight > 0) {
return qRound(m_titlebarHeight * dpr); return qRound(m_titleBarHeight * dpr);
} }
break; break;
} }

View File

@ -41,7 +41,7 @@ public:
using WINDOWDATA = struct _WINDOWDATA { using WINDOWDATA = struct _WINDOWDATA {
BOOL fixedSize = FALSE, mouseTransparent = FALSE, BOOL fixedSize = FALSE, mouseTransparent = FALSE,
notLayeredWindow = FALSE; notLayeredWindow = FALSE;
int borderWidth = -1, borderHeight = -1, titlebarHeight = -1; int borderWidth = -1, borderHeight = -1, titleBarHeight = -1;
QVector<QRect> ignoreAreas = {}, draggableAreas = {}; QVector<QRect> ignoreAreas = {}, draggableAreas = {};
QVector<QPointer<QObject>> ignoreObjects = {}, draggableObjects = {}; QVector<QPointer<QObject>> ignoreObjects = {}, draggableObjects = {};
QSize maximumSize = {-1, -1}, minimumSize = {-1, -1}; QSize maximumSize = {-1, -1}, minimumSize = {-1, -1};
@ -72,7 +72,7 @@ public:
static void removeFramelessWindow(HWND window); static void removeFramelessWindow(HWND window);
static void clearFramelessWindows(); static void clearFramelessWindows();
// Set borderWidth, borderHeight or titlebarHeight to a negative value to // Set borderWidth, borderHeight or titleBarHeight to a negative value to
// restore default behavior. // restore default behavior.
// Note that it can only affect one specific window. // Note that it can only affect one specific window.
// If you want to change these values globally, use setBorderWidth instead. // If you want to change these values globally, use setBorderWidth instead.
@ -86,7 +86,7 @@ public:
// them yourself. Just pass the original value. // them yourself. Just pass the original value.
static void setBorderWidth(int bw); static void setBorderWidth(int bw);
static void setBorderHeight(int bh); static void setBorderHeight(int bh);
static void setTitlebarHeight(int tbh); static void setTitleBarHeight(int tbh);
// System metric value of the given window (if the pointer is null, // System metric value of the given window (if the pointer is null,
// return the system's standard value). // return the system's standard value).