简体   繁体   English

显示器之间的 Qt 自定义无框窗口中的绘画问题

[英]Painting issue in Qt custom frameless window between monitors

For one of my applications, I wanted to have a window customized under the Windows operating system (like Firefox, Avast, Microsoft Word, etc.).对于我的一个应用程序,我想在 Windows 操作系统(如 Firefox、Avast、Microsoft Word 等)下自定义一个窗口。 So I reimplemented some messages handling ( QWidget::nativeEvent () ) from Win32 API, in order to preserve the functionality of AeroSnap, and others.所以我从 Win32 API 重新实现了一些消息处理( QWidget::nativeEvent () ),以保留 AeroSnap 等的功能。

Although it works perfectly, when my custom window is transferred from one screen to another (I have two screens), a visual glitch appears (as you can see below).尽管它运行良好,但当我的自定义窗口从一个屏幕转移到另一个屏幕时(我有两个屏幕),会出现视觉故障(如下所示)。 After the glitch appears, resizing the window, correct the bug.出现故障后,调整窗口大小,纠正错误。 Moreover, after some debugging, I noticed that the Qt QWidget::geometry() is not the same as the geometry returned by Win32 API GetWindowRect() when the bug appears.此外,经过一些调试,我注意到 Qt QWidget::geometry()与出现错误时 Win32 API GetWindowRect()返回的几何体不同。

Both of my monitors are HD (1920x1080, so is not the difference of resolutions which cause the bug, but maybe the DPI difference ?).我的两台显示器都是高清(1920x1080,所以不是分辨率的差异导致了错误,但可能是 DPI 差异?)。 The glitch seems to appear when the window is moving between the two screens (when windows transfer the window to one screen to another ?).当窗口在两个屏幕之间移动时(当窗口将窗口转移到一个屏幕到另一个屏幕时?)时,似乎会出现故障。

Screenshot of the window without the glitch没有故障的窗口的屏幕截图

Screenshot of the window with the glitch有故障的窗口的屏幕截图

The geometry reported initially by QWidget::geometry() is QRect(640,280 800x600) , by QWidget::frameGeometry() is QRect(640,280 800x600) , and by the Win32 GetWindowRect() is QRect(640,280 800x600) . QWidget::geometry()最初报告的QWidget::geometry()QRect(640,280 800x600)QWidget::frameGeometry()QRect(640,280 800x600) ,而 Win32 GetWindowRect()QRect(640,280 800x600) So, the same geometry.所以,同样的几何。 But, after the window is moved between the two monitors, the geometry reported by QWidget::geometry() becomes QGeometry(1541,322 784x561) .但是,在两个显示器之间移动窗口后, QWidget::geometry()报告的QWidget::geometry()变为QGeometry(1541,322 784x561) The geometry reported by QWidget::frameGeometry() or GetWindowRect() is unchanged. QWidget::frameGeometry()GetWindowRect()报告的几何图形不变。 Of course, after that, when the window is resized, the geometry is re correctly reported, and the painting issue dissapears.当然,在那之后,当调整窗口大小时,几何会重新正确报告,并且绘画问题消失了。 Conclusion, Qt seems to assume that a "appear" frame when the window is moved between two monitors.结论,当窗口在两个显示器之间移动时,Qt 似乎假设“出现”帧。

The BaseFramelessWindow class implementation can be found here . BaseFramelessWindow类实现可以在这里找到。 This is a Qt QMainWindow subclass re-implementing some Win32 API native events ( QWidget::nativeEvent() ).这是一个 Qt QMainWindow 子类,重新实现了一些 Win32 API 原生事件( QWidget::nativeEvent() )。

So someone would be how to avoid this bug?那么有人会如何避免这个错误呢? Is this a Qt bug?这是一个Qt错误吗? I have tried many things, and nothing has really worked.我尝试了很多东西,但没有任何效果。 And I can't find any mention of this problem on the internet (maybe I'm looking badly?).而且我在互联网上找不到任何提及此问题的内容(也许我看起来很糟糕?)。

Minimal example code:最小示例代码:

// 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;
}

I had this exact error after and solved it by implementing Qt's moveEvent to detect screen changes and then call SetWindowPos to redraw the window in the same position.之后我遇到了这个确切的错误,并通过实现 Qt 的moveEvent来检测屏幕变化,然后调用SetWindowPos在相同位置重新绘制窗口来解决它。

An example would be:一个例子是:

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);
        }

    }
};

I'm not sure if the current_screen == nullptr clause is strictly needed, but I had trouble with multiple windows/dialogs without it (sometimes they would become unresponsive).我不确定是否严格需要current_screen == nullptr子句,但是如果没有它,我在多个窗口/对话框中遇到了麻烦(有时它们会变得无响应)。 I also do not know which flags for SetWindowPos are necessary.我也不知道SetWindowPos需要哪些标志。

I also cannot say whether this method is good practice, and it may cause problems elsewhere.我也不能说这种方法是否是好的做法,它可能会在其他地方引起问题。 Hopefully someone else can comment on this.希望其他人可以对此发表评论。

Either way it worked for me in a simple situation.无论哪种方式,它都在一个简单的情况下对我有用。 Hopefully it provides a solution, or at least a starting point to try and solve this.希望它提供了一个解决方案,或者至少是尝试解决这个问题的起点。

Check out this answer: How to integrate the Qt frameless window in Windows composer?查看这个答案: 如何在 Windows composer 中集成 Qt 无框窗口? (system shortcut doesnt works) (系统快捷方式不起作用)

I had the exact same issue with snapping between screens, as mentioned in the 3rd paragraph of that answer:如该答案的第 3 段所述,我在屏幕之间捕捉时遇到了完全相同的问题:

I ran into some painting issues when snapping from one screen to another, but worked around that with a mask (notes in code comments).从一个屏幕捕捉到另一个屏幕时,我遇到了一些绘画问题,但使用遮罩(代码注释中的注释)解决了这个问题。 Would be nice to find a cleaner solution (it may qualify as a Qt bug actually).找到一个更干净的解决方案会很好(实际上它可能有资格作为 Qt 错误)。

And the workaround is at the bottom of the FramelessWidget code block in the paintEvent() method, after the long comment.解决方法是在paintEvent()方法中FramelessWidget代码块的底部,在长注释之后。 I'll paste it here as well with a little context:我也会把它贴在这里,并附上一些上下文:

  QStyleOption opt;
  opt.initFrom(this);
  // be sure to use the full frame size, not the default rect() which is inside frame.
  opt.rect.setSize(frameSize());  

      .....

  // Setting a mask works around an issue with artifacts when switching screens with Win+arrow
  // keys. I don't think it's the actual mask which does it, rather it triggers the region 
  // around the widget to be polished but I'm not sure. As support for my theory, the mask 
  // doesn't even have to follow the border radius.
  setMask(QRegion(opt.rect));

I don't see a paintEvent() in your code but perhaps you can do something similar from elsewhere.我在您的代码中没有看到paintEvent()但也许您可以从其他地方做类似的事情。

BTW this may also give you a hint about why QWidget::geometry() isn't right... you probably want QWidget::frameGeometry() .顺便说一句,这也可能会给你一个关于为什么QWidget::geometry()不正确的提示......你可能想要QWidget::frameGeometry()

The problem is that Qt does not handle the drawing of the window correctly when WM_NCCALCSIZE is modified!问题是Qt在修改WM_NCCALCSIZEWM_NCCALCSIZE正确处理窗口的绘制!

I realized that you already have a work in progress, but i developed a parallel work that achieves your goals in a simple way that might interest you, it handles all the low level APIs of window for a border less window, while you can just use the setCentralWidget API and your widget will fill the whole window!我意识到您已经有一项正在进行的工作,但我开发了一项并行工作,它以一种您可能感兴趣的简单方式实现您的目标,它处理窗口的所有低级 API,用于无边框窗口,而您只需使用setCentralWidget API 和您的小部件将填满整个窗口!

好窗口

The header of ExampleMinimal (the clean example) is just: ExampleMinimal (干净的示例)的标题只是:

#include <QGoodWindow>

class MainWindow : public QGoodWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
};

See: https://github.com/antonypro/QGoodWindow见: https : //github.com/antonypro/QGoodWindow

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM