Проблема рисования в произвольном окне Qt между мониторами - PullRequest
1 голос
/ 22 февраля 2020

Для одного из моих приложений я хотел настроить окно под операционную систему Windows (например, Firefox, Avast, Microsoft Word и др. c.). Поэтому я переопределил обработку некоторых сообщений (QWidget::nativeEvent ()) из Win32 API, чтобы сохранить функциональность AeroSnap и других.

Хотя это прекрасно работает, когда мое пользовательское окно переносится с одного экрана на другой ( У меня два экрана), появляется визуальный сбой (как вы можете видеть ниже). После появления глюка, изменяя размеры окна, исправьте ошибку. Более того, после некоторой отладки я заметил, что Qt QWidget::geometry() не совпадает с геометрией, возвращаемой Win32 API GetWindowRect() при появлении ошибки.

Оба моих монитора HD (1920x1080, поэтому не разница в разрешениях, которые вызывают ошибку, но, может быть, разница в DPI?). Кажется, что появляется сбой, когда окно перемещается между двумя экранами (когда windows переносит окно с одного экрана на другой?).

Снимок экрана окна без сбоев

Снимок экрана с глюком

Геометрия, первоначально сообщаемая QWidget::geometry(), равна QRect(640,280 800x600), QWidget::frameGeometry() равна QRect(640,280 800x600) а по Win32 GetWindowRect() составляет QRect(640,280 800x600). Итак, та же геометрия. Но после перемещения окна между двумя мониторами геометрия, сообщаемая QWidget::geometry(), становится QGeometry(1541,322 784x561). Геометрия, сообщаемая QWidget::frameGeometry() или GetWindowRect(), не изменяется. Конечно, после этого, когда размер окна изменяется, правильно сообщается геометрия, и проблема рисования исчезает. Заключение: Qt, похоже, предполагает, что «окно» появляется, когда окно перемещается между двумя мониторами.

Реализация класса BaseFramelessWindow может быть найдена здесь . Это подкласс Qt QMainWindow, повторно реализующий некоторые нативные события Win32 API (QWidget::nativeEvent()).

Так что, кто-то может сказать, как избежать этой ошибки? Это ошибка Qt? Я перепробовал много вещей, и ничего не получилось. И я не могу найти упоминания об этой проблеме в inte rnet (может, я плохо выгляжу?).

Минимальный пример кода:

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

Ответы [ 2 ]

0 голосов
/ 16 марта 2020

Проблема в том, что Qt неправильно обрабатывает рисунок окна при изменении WM_NCCALCSIZE!

Я понял, что у вас уже есть работа, но я разработал параллельную работу, которая достигает вашей цели простым способом, который может вас заинтересовать, он обрабатывает все низкоуровневые API окна для окна без границ, в то время как вы можете просто использовать setCentralWidget API, и ваш виджет заполнит все окно!

QGoodWindow

Заголовок ExampleMinimal (чистый пример) просто:

#include <QGoodWindow>

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

См .: https://github.com/antonypro/QGoodWindow

0 голосов
/ 22 февраля 2020

Проверьте этот ответ: Как интегрировать безрамное окно Qt в Windows composer? (ярлык системы не работает)

У меня была точно такая же проблема с привязкой между экранами, как упомянуто в 3-м абзаце этого ответа:

Я столкнулся с некоторой картиной проблемы при привязке с одного экрана на другой, но обходили это с помощью маски (примечания в комментариях к коду). Было бы неплохо найти более чистое решение (на самом деле оно может рассматриваться как ошибка Qt).

И обходной путь находится в нижней части блока кода FramelessWidget в методе paintEvent(), после длинный комментарий. Я также вставлю его сюда с небольшим контекстом:

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

Я не вижу paintEvent() в вашем коде, но, возможно, вы можете сделать что-то подобное из других источников.

Кстати, это может также дать вам подсказку о том, почему QWidget::geometry() не правильно ... вы, вероятно, хотите QWidget::frameGeometry().

...