首页 > 解决方案 > 显示器之间的 Qt 自定义无框窗口中的绘画问题

问题描述

对于我的一个应用程序,我想在 Windows 操作系统(如 Firefox、Avast、Microsoft Word 等)下定制一个窗口。所以我QWidget::nativeEvent ()从 Win32 API 重新实现了一些消息处理 (),以保留 AeroSnap 和其他的功能。

虽然效果很好,但当我的自定义窗口从一个屏幕转移到另一个屏幕(我有两个屏幕)时,会出现视觉故障(如下所示)。故障出现后,调整窗口大小,纠正错误。而且,经过一些调试,我注意到Qt出现错误时与QWidget::geometry()Win32 API返回的几何图形不一样。GetWindowRect()

我的两台显示器都是高清的(1920x1080,所以不是导致错误的分辨率差异,而是 DPI 差异?)。当窗口在两个屏幕之间移动时似乎会出现故障(当窗口将窗口从一个屏幕转移到另一个屏幕时?)。

没有故障的窗口截图

有故障的窗口的屏幕截图

最初由QWidget::geometry()is QRect(640,280 800x600)、by QWidget::frameGeometry()isQRect(640,280 800x600)和 Win32 GetWindowRect()is报告的几何图形QRect(640,280 800x600)。因此,相同的几何形状。但是,在窗口在两个监视器之间移动后,由 报告的几何图形QWidget::geometry()变为QGeometry(1541,322 784x561)QWidget::frameGeometry()由or报告的几何图形GetWindowRect()不变。当然,在那之后,当调整窗口大小时,几何图形会重新正确报告,绘画问题就会消失。结论,当窗口在两个监视器之间移动时,Qt 似乎假设“出现”帧。

BaseFramelessWindow实现可以在这里找到。这是一个 Qt QMainWindow 子类,重新实现了一些 Win32 API 原生事件 ( QWidget::nativeEvent())。

那么有人会如何避免这个bug呢?这是一个Qt错误吗?我已经尝试了很多东西,但没有什么真正奏效。而且我在互联网上找不到任何关于这个问题的提及(也许我看起来很糟糕?)。

最小示例代码:

// main.cpp
#include "MainWindow.h"

#include <QtWidgets/qapplication.h>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}
// MainWindow.h
#pragma once

#include <QtWidgets/qmainwindow.h>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = Q_NULLPTR);

protected:
    void paintEvent(QPaintEvent* event) override;
    bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;
};
// MainWindow.cpp
#include "MainWindow.h"

#include <QtGui/qpainter.h>

#include <Windows.h>
#include <Windowsx.h>

MainWindow::MainWindow(QWidget* parent)
    : QMainWindow(parent)
{
    ::SetWindowLongPtr((HWND)winId(), GWL_STYLE, WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX);
}

void MainWindow::paintEvent(QPaintEvent* event)
{
    QPainter painter(this);

    // Using GetWindowRect() instead of rect() seems to be a valid
    // workaround for the painting issue. However many other things
    // do not work cause of the bad geometry reported by Qt.
    RECT winrect;
    GetWindowRect(reinterpret_cast<HWND>(winId()), &winrect);
    QRect rect = QRect(0, 0, winrect.right - winrect.left, winrect.bottom - winrect.top);

    // Background
    painter.fillRect(rect, Qt::red);

    // Border
    painter.setBrush(Qt::NoBrush);
    painter.setPen(QPen(Qt::blue, 1));
    painter.drawRect(rect.adjusted(0, 0, -1, -1));

    // Title bar
    painter.fillRect(QRect(1, 1, rect.width() - 2, 19), Qt::yellow);
}
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
    MSG* msg = reinterpret_cast<MSG*>(message);

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

        RECT winrect;
        GetWindowRect(reinterpret_cast<HWND>(winId()), &winrect);

        // Code allowing to resize the window with the mouse is omitted.

        long x = GET_X_LPARAM(msg->lParam);
        long y = GET_Y_LPARAM(msg->lParam);
        if (x > winrect.left&& x < winrect.right && y > winrect.top&& y < winrect.top + 20) {
            // To allow moving the window.
            *result = HTCAPTION;
            return true;
        }

        repaint();
        return false;
    }
    default:
        break;
    }

    return false;
}

标签: c++windowsqtwinapidwm

解决方案


之后我遇到了这个确切的错误,并通过实施 QtmoveEvent来检测屏幕变化然后调用SetWindowPos以在相同位置重绘窗口来解决它。

一个例子是:

class MainWindow : public QMainWindow
{
    // rest of the class

private:

    QScreen* current_screen = nullptr;

    void moveEvent(QMoveEvent* event)
    {
        if (current_screen == nullptr)
        {
            current_screen = screen();
        }
        else if (current_screen != screen())
        {
            current_screen = screen();

            SetWindowPos((HWND) winId(), NULL, 0, 0, 0, 0,
                         SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
                         SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE);
        }

    }
};

我不确定是否current_screen == nullptr严格需要该子句,但是如果没有它,我在多个窗口/对话框中遇到了麻烦(有时它们会变得无响应)。我也不知道哪些标志SetWindowPos是必要的。

我也不能说这种方法是否是好的做法,它可能会在其他地方引起问题。希望其他人可以对此发表评论。

无论哪种方式,它在一个简单的情况下都对我有用。希望它提供了一个解决方案,或者至少是一个尝试解决这个问题的起点。


推荐阅读