Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-117704

Dragging Window handling WM_NCCALCSIZE and WM_NCHITTEST squishes contents

    XMLWordPrintable

Details

    • Windows
    • 03a416420 (dev), d2881ae09 (6.7), 0056998ba (6.6), 0eb11bcee (tqtc/lts-6.5)

    Description

      I'm implementing a custom window on Windows with a custom title bar. To do this, I'm handling WM_NCCALCSIZE to disable the title bar and WM_NCHITTEST to handle resizing/moving.

      After updating from Qt 6.5.0 to Qt 6.5.3, I noticed that when dragging, the window gets squished to the left when moving, but when resizing, the effect vanishes.

      This seems to be caused by https://codereview.qt-project.org/c/qt/qtbase/+/492695, specifically the added call to calculateFullFrameMargins() in QWindowsWindow::handleGeometryChange (removing that call seems to fix this bug). This will be called when the window is moved and will eventually set different margins than the ones set in qWindowsWndProc when handling WM_NCCALCSIZE (or the default margins I suppose).

      I made an example app:

      Before dragging:

      After dragging:

      As explained before, resizing the window fixes the bug.

      In the debug logs, you can see them fighting over the correct margins:

      qt.qpa.window: qWindowsWndProc WM_NCCALCSIZE for 0x1e70696 QMargins(0, 0, 0, 0)
      qt.qpa.window: QWindowsWindow::setFullFrameMargins QWidgetWindow(0x22092a2bf40, name="QWidgetClassWindow") QMargins(8, -8, 8, 8) -> QMargins(0, 0, 0, 0)
      qt.qpa.window: QWindowsWindow::setFullFrameMargins QWidgetWindow(0x22092a2bf40, name="QWidgetClassWindow") QMargins(0, 0, 0, 0) -> QMargins(8, -8, 8, 8)
      qt.qpa.window: qWindowsWndProc WM_NCCALCSIZE for 0x1e70696 QMargins(0, 0, 0, 0)
      qt.qpa.window: QWindowsWindow::setFullFrameMargins QWidgetWindow(0x22092a2bf40, name="QWidgetClassWindow") QMargins(8, -8, 8, 8) -> QMargins(0, 0, 0, 0)
      qt.qpa.window: QWindowsWindow::setFullFrameMargins QWidgetWindow(0x22092a2bf40, name="QWidgetClassWindow") QMargins(0, 0, 0, 0) -> QMargins(8, -8, 8, 8)
      qt.qpa.window: qWindowsWndProc WM_NCCALCSIZE for 0x1e70696 QMargins(0, 0, 0, 0)
      qt.qpa.window: QWindowsWindow::setFullFrameMargins QWidgetWindow(0x22092a2bf40, name="QWidgetClassWindow") QMargins(8, -8, 8, 8) -> QMargins(0, 0, 0, 0)
      qt.qpa.window: QWindowsWindow::setFullFrameMargins QWidgetWindow(0x22092a2bf40, name="QWidgetClassWindow") QMargins(0, 0, 0, 0) -> QMargins(8, -8, 8, 8)
      
      #include <QApplication>
      #include <QHBoxLayout>
      #include <QLabel>
      #include <QPainter>
      #include <QWidget>
      #include <Windows.h>
      #include <windowsx.h>
      
      class Draggy : public QWidget
      {
          bool nativeEvent(const QByteArray &eventType, void *message,
                           qintptr *result) override
          {
              MSG *msg = reinterpret_cast<MSG *>(message);
              switch (msg->message)
              {
                  case WM_NCCALCSIZE: {
                      *result = 0;
                      return true;
                  }
                  break;
                  case WM_NCHITTEST: {
                      const LONG borderWidth = 8;
                      RECT winrect;
                      GetWindowRect(HWND(winId()), &winrect);
      
                      long x = GET_X_LPARAM(msg->lParam);
                      long y = GET_Y_LPARAM(msg->lParam);
      
                      QPoint point(x - winrect.left, y - winrect.top);
      
                      *result = 0;
      
                      bool resizeWidth = minimumWidth() != maximumWidth();
                      bool resizeHeight = minimumHeight() != maximumHeight();
      
                      if (resizeWidth)
                      {
                          // left border
                          if (x < winrect.left + borderWidth)
                          {
                              *result = HTLEFT;
                          }
                          // right border
                          else if (x >= winrect.right - borderWidth)
                          {
                              *result = HTRIGHT;
                          }
                      }
                      if (resizeHeight)
                      {
                          // bottom border
                          if (y >= winrect.bottom - borderWidth)
                          {
                              *result = HTBOTTOM;
                          }
                          // top border
                          else if (y < winrect.top + borderWidth)
                          {
                              *result = HTTOP;
                          }
                      }
                      if (resizeWidth && resizeHeight)
                      {
                          // bottom left corner
                          if (x >= winrect.left && x < winrect.left + borderWidth &&
                              y < winrect.bottom && y >= winrect.bottom - borderWidth)
                          {
                              *result = HTBOTTOMLEFT;
                          }
                          // bottom right corner
                          else if (x < winrect.right &&
                                   x >= winrect.right - borderWidth &&
                                   y < winrect.bottom &&
                                   y >= winrect.bottom - borderWidth)
                          {
                              *result = HTBOTTOMRIGHT;
                          }
                          // top left corner
                          else if (x >= winrect.left &&
                                   x < winrect.left + borderWidth &&
                                   y >= winrect.top && y < winrect.top + borderWidth)
                          {
                              *result = HTTOPLEFT;
                          }
                          // top right corner
                          else if (x < winrect.right &&
                                   x >= winrect.right - borderWidth &&
                                   y >= winrect.top && y < winrect.top + borderWidth)
                          {
                              *result = HTTOPRIGHT;
                          }
                      }
      
                      if (*result == 0)
                      {
                          *result = HTCAPTION;
                      }
      
                      return true;
                  }
                  break;
                  default:
                      return false;
              }
          }
      
          void paintEvent(QPaintEvent *event) override
          {
              QPainter painter(this);
              painter.setRenderHint(QPainter::Antialiasing);
              painter.setBrush(Qt::black);
              auto rect = this->rect();
              painter.drawLine(rect.bottomLeft(), rect.topRight());
              painter.drawLine(rect.topLeft(), rect.bottomRight());
          }
      };
      
      int main(int argc, char **argv)
      {
          QApplication app(argc, argv);
          Draggy wrap;
          QHBoxLayout layout(&wrap);
          QLabel foo("Foo");
          layout.addWidget(&foo, 0, Qt::AlignCenter);
          wrap.show();
      
          return app.exec();
      }
      

      Attachments

        1. after_drag.png
          1 kB
          Tom Fors
        2. before_drag.jpg
          2 kB
          Tom Fors
        3. image-2024-04-03-19-26-26-701.png
          57 kB
          Dmitry Beskorovainov
        4. image-2024-04-05-10-35-57-690.png
          26 kB
          Dmitry Beskorovainov
        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            timothee.keller Timothée Keller (Inactive)
            nerixyz Tom Fors
            Votes:
            0 Vote for this issue
            Watchers:
            7 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes