diff --git a/include/FramelessHelper/Core/private/framelesshelpercore_global_p.h b/include/FramelessHelper/Core/private/framelesshelpercore_global_p.h index 6ffa120..599799e 100644 --- a/include/FramelessHelper/Core/private/framelesshelpercore_global_p.h +++ b/include/FramelessHelper/Core/private/framelesshelpercore_global_p.h @@ -61,6 +61,7 @@ using GetPropertyCallback = std::function; using UnsetCursorCallback = std::function; using GetWidgetHandleCallback = std::function; +using ForceChildrenRepaintCallback = std::function; struct SystemParameters { @@ -90,6 +91,7 @@ struct SystemParameters SetCursorCallback setCursor = nullptr; UnsetCursorCallback unsetCursor = nullptr; GetWidgetHandleCallback getWidgetHandle = nullptr; + ForceChildrenRepaintCallback forceChildrenRepaint = nullptr; }; using FramelessParams = SystemParameters *; diff --git a/include/FramelessHelper/Quick/private/framelessquickhelper_p.h b/include/FramelessHelper/Quick/private/framelessquickhelper_p.h index c60b39a..35c68da 100644 --- a/include/FramelessHelper/Quick/private/framelessquickhelper_p.h +++ b/include/FramelessHelper/Quick/private/framelessquickhelper_p.h @@ -88,6 +88,8 @@ public: Q_NODISCARD bool isReady() const; void waitForReady(); + void repaintAllChildren(const int delay = 0) const; + private: Q_NODISCARD QRect mapItemGeometryToScene(const QQuickItem * const item) const; Q_NODISCARD bool isInSystemButtons(const QPoint &pos, QuickGlobal::SystemButtonType *button) const; diff --git a/include/FramelessHelper/Widgets/private/framelesswidgetshelper_p.h b/include/FramelessHelper/Widgets/private/framelesswidgetshelper_p.h index 511fca8..5b6db18 100644 --- a/include/FramelessHelper/Widgets/private/framelesswidgetshelper_p.h +++ b/include/FramelessHelper/Widgets/private/framelesswidgetshelper_p.h @@ -26,6 +26,7 @@ #include #include +#include FRAMELESSHELPER_BEGIN_NAMESPACE @@ -89,6 +90,8 @@ public: Q_NODISCARD bool isReady() const; void waitForReady(); + void repaintAllChildren(const int delay = 0) const; + private: Q_NODISCARD QRect mapWidgetGeometryToScene(const QWidget * const widget) const; Q_NODISCARD bool isInSystemButtons(const QPoint &pos, Global::SystemButtonType *button) const; @@ -106,6 +109,7 @@ private: QPointer m_window = nullptr; bool m_destroying = false; bool m_qpaReady = false; + QSizePolicy m_savedSizePolicy = {}; }; FRAMELESSHELPER_END_NAMESPACE diff --git a/src/core/framelesshelper_win.cpp b/src/core/framelesshelper_win.cpp index f95d064..e837227 100644 --- a/src/core/framelesshelper_win.cpp +++ b/src/core/framelesshelper_win.cpp @@ -1200,16 +1200,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me Utils::rescaleSize(data.restoreGeometry.size(), oldDpi.x, newDpi.x)); } g_win32Helper()->mutex.unlock(); -#if (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2)) - // We need to wait until Qt has handled this message, otherwise everything - // we have done here will always be overwritten. - QWindow *window = data.params.getWindowHandle(); - QTimer::singleShot(0, qApp, [window](){ - // Sync the internal window frame margins with the latest DPI, otherwise - // we will get wrong window sizes after the DPI change. - Utils::updateInternalWindowFrameMargins(window, true); - }); -#endif // (QT_VERSION <= QT_VERSION_CHECK(6, 4, 2)) + data.params.forceChildrenRepaint(500); } break; case WM_DWMCOMPOSITIONCHANGED: { // Re-apply the custom window frame if recovered from the basic theme. diff --git a/src/core/utils_win.cpp b/src/core/utils_win.cpp index 5c41faf..473b28b 100644 --- a/src/core/utils_win.cpp +++ b/src/core/utils_win.cpp @@ -186,6 +186,7 @@ FRAMELESSHELPER_STRING_CONSTANT(SendMessageTimeoutW) FRAMELESSHELPER_STRING_CONSTANT(AttachThreadInput) FRAMELESSHELPER_STRING_CONSTANT(BringWindowToTop) FRAMELESSHELPER_STRING_CONSTANT(SetActiveWindow) +FRAMELESSHELPER_STRING_CONSTANT(RedrawWindow) struct Win32UtilsHelperData { @@ -636,11 +637,18 @@ void Utils::triggerFrameChange(const WId windowId) return; } const auto hwnd = reinterpret_cast(windowId); - static constexpr const UINT flags = + static constexpr const UINT swpFlags = (SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); - if (SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, flags) == FALSE) { + if (SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, swpFlags) == FALSE) { WARNING << getSystemErrorMessage(kSetWindowPos); + return; + } + static constexpr const UINT rdwFlags = + (RDW_ERASE | RDW_FRAME | RDW_INVALIDATE + | RDW_UPDATENOW | RDW_ALLCHILDREN); + if (RedrawWindow(hwnd, nullptr, nullptr, rdwFlags) == FALSE) { + WARNING << getSystemErrorMessage(kRedrawWindow); } } diff --git a/src/quick/framelessquickhelper.cpp b/src/quick/framelessquickhelper.cpp index a152860..ef0d28c 100644 --- a/src/quick/framelessquickhelper.cpp +++ b/src/quick/framelessquickhelper.cpp @@ -224,6 +224,7 @@ void FramelessQuickHelperPrivate::attach() params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); }; params.unsetCursor = [window]() -> void { window->unsetCursor(); }; params.getWidgetHandle = []() -> QObject * { return nullptr; }; + params.forceChildrenRepaint = [this](const int delay) -> void { repaintAllChildren(delay); }; FramelessManager::instance()->addWindow(¶ms); @@ -685,6 +686,38 @@ void FramelessQuickHelperPrivate::waitForReady() #endif } +void FramelessQuickHelperPrivate::repaintAllChildren(const int delay) const +{ + Q_Q(const FramelessQuickHelper); + QQuickWindow * const window = q->window(); + if (!window) { + return; + } + const auto update = [window]() -> void { + window->requestUpdate(); +#ifdef Q_OS_WINDOWS + // Sync the internal window frame margins with the latest DPI, otherwise + // we will get wrong window sizes after the DPI change. + Utils::updateInternalWindowFrameMargins(window, true); +#endif // Q_OS_WINDOWS + const QList items = window->findChildren(); + if (items.isEmpty()) { + return; + } + for (auto &&item : std::as_const(items)) { + // Only items with the "QQuickItem::ItemHasContents" flag enabled are allowed to call "update()". + if (item->flags() & QQuickItem::ItemHasContents) { + item->update(); + } + } + }; + if (delay > 0) { + QTimer::singleShot(delay, this, update); + } else { + update(); + } +} + QRect FramelessQuickHelperPrivate::mapItemGeometryToScene(const QQuickItem * const item) const { Q_ASSERT(item); diff --git a/src/widgets/framelesswidgetshelper.cpp b/src/widgets/framelesswidgetshelper.cpp index 287ed3d..7da7ca6 100644 --- a/src/widgets/framelesswidgetshelper.cpp +++ b/src/widgets/framelesswidgetshelper.cpp @@ -88,6 +88,76 @@ struct WidgetsHelper Q_GLOBAL_STATIC(WidgetsHelper, g_widgetsHelper) +[[nodiscard]] static inline bool isWidgetFixedSize(const QWidget * const widget) +{ + Q_ASSERT(widget); + if (!widget) { + return false; + } + // "Qt::MSWindowsFixedSizeDialogHint" is used cross-platform actually. + if (widget->windowFlags() & Qt::MSWindowsFixedSizeDialogHint) { + return true; + } + // Caused by setFixedWidth/Height/Size(). + const QSize minSize = widget->minimumSize(); + const QSize maxSize = widget->maximumSize(); + if (!minSize.isEmpty() && !maxSize.isEmpty() && (minSize == maxSize)) { + return true; + } + // Usually set by the user. + const QSizePolicy sizePolicy = widget->sizePolicy(); + if ((sizePolicy.horizontalPolicy() == QSizePolicy::Fixed) + && (sizePolicy.verticalPolicy() == QSizePolicy::Fixed)) { + return true; + } + return false; +} + +static inline void forceWidgetRepaint(QWidget * const widget) +{ + Q_ASSERT(widget); + if (!widget) { + return; + } + // Tell the widget to repaint itself, but it may not happen due to QWidget's + // internal painting optimizations. + widget->update(); + // Try to force the widget to repaint itself, in case: + // (1) It's a child widget; + // (2) It's a top level window but not minimized/maximized/fullscreen. + if (!widget->isWindow() || !(widget->windowState() & (Qt::WindowMinimized | Qt::WindowMaximized | Qt::WindowFullScreen))) { + // A widget will most likely repaint itself if it's size is changed. + if (!isWidgetFixedSize(widget)) { + const QSize originalSize = widget->size(); + static constexpr const auto margins = QMargins{1, 1, 1, 1}; + widget->resize(originalSize.shrunkBy(margins)); + widget->resize(originalSize.grownBy(margins)); + widget->resize(originalSize); + } + // However, some widgets won't repaint themselves unless their position is changed. + const QPoint originalPosition = widget->pos(); + static constexpr const auto offset = QPoint{1, 1}; + widget->move(originalPosition - offset); + widget->move(originalPosition + offset); + widget->move(originalPosition); + } +#ifdef Q_OS_WINDOWS + // There's some additional things to do for top level windows on Windows. + if (widget->isWindow()) { + // Don't crash if the QWindow instance has not been created yet. + if (QWindow * const window = widget->windowHandle()) { + // Sync the internal window frame margins with the latest DPI, otherwise + // we will get wrong window sizes after the DPI change. + Utils::updateInternalWindowFrameMargins(window, true); + } + } +#endif // Q_OS_WINDOWS + // Let's try again with the ordinary way. + widget->update(); + // ### TODO: I observed the font size is often wrong after DPI changes, + // do we need to refresh the font settings here as well? +} + FramelessWidgetsHelperPrivate::FramelessWidgetsHelperPrivate(FramelessWidgetsHelper *q) : QObject(q) { Q_ASSERT(q); @@ -126,18 +196,7 @@ bool FramelessWidgetsHelperPrivate::isWindowFixedSize() const if (!m_window) { return false; } - if (m_window->windowFlags() & Qt::MSWindowsFixedSizeDialogHint) { - return true; - } - const QSize minSize = m_window->minimumSize(); - const QSize maxSize = m_window->maximumSize(); - if (!minSize.isEmpty() && !maxSize.isEmpty() && (minSize == maxSize)) { - return true; - } - if (m_window->sizePolicy() == QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)) { - return true; - } - return false; + return isWidgetFixedSize(m_window); } void FramelessWidgetsHelperPrivate::setWindowFixedSize(const bool value) @@ -149,8 +208,11 @@ void FramelessWidgetsHelperPrivate::setWindowFixedSize(const bool value) return; } if (value) { + m_savedSizePolicy = m_window->sizePolicy(); + m_window->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_window->setFixedSize(m_window->size()); } else { + m_window->setSizePolicy(m_savedSizePolicy); m_window->setMinimumSize(kDefaultWindowSize); m_window->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); } @@ -345,6 +407,28 @@ void FramelessWidgetsHelperPrivate::waitForReady() #endif } +void FramelessWidgetsHelperPrivate::repaintAllChildren(const int delay) const +{ + if (!m_window) { + return; + } + const auto update = [this]() -> void { + forceWidgetRepaint(m_window); + const QList widgets = m_window->findChildren(); + if (widgets.isEmpty()) { + return; + } + for (auto &&widget : std::as_const(widgets)) { + forceWidgetRepaint(widget); + } + }; + if (delay > 0) { + QTimer::singleShot(delay, this, update); + } else { + update(); + } +} + bool FramelessWidgetsHelperPrivate::isContentExtendedIntoTitleBar() const { return getWindowData().ready; @@ -487,6 +571,7 @@ void FramelessWidgetsHelperPrivate::attach() params.setCursor = [window](const QCursor &cursor) -> void { window->setCursor(cursor); }; params.unsetCursor = [window]() -> void { window->unsetCursor(); }; params.getWidgetHandle = [window]() -> QObject * { return window; }; + params.forceChildrenRepaint = [this](const int delay) -> void { repaintAllChildren(delay); }; FramelessManager::instance()->addWindow(¶ms);