Widgets are not updated - is this a bug?

Jeremy Katz jkatz at volexity.com
Thu Sep 17 09:08:41 BST 2020


On 15/Sep/20 06:29, Phil Thompson wrote:

[...]
> 
> However I am currently at a loss about what is happening. I am using a
> version of PyQt that only uses "plain" Qt classes which does not allow
> Python re-implementations of C++ virtuals. This means that while the Qt
> event loop is running (ie. in the call to exec()), *no* PyQt is executed
> - yet the problem still exists. It's almost as if the very presence of
> Python is having an effect.
> 
> Any insight would be welcome...
> 
> Phil


Short version:

Setting the environment variable QT_MAC_WANTS_LAYER to 1 may fix the
issue for macOS 10.15. The code at
https://code.woboq.org/qt5/qtbase/src/plugins/platforms/cocoa/qnsview_drawing.mm.html#108
indicates this won't work for 10.14.


Getting into more detail, apparently there are differences in how the
windows and/or backing stores are configured. The C++ version creates a
dark mode window. The Python version uses the light palette.

Turning on the qt.qpa.drawing logging category reveals further
differences in a session that involves starting the program, clicking
the clear button, and then terminating it with the window manager's
close button. The C++ version ouputs this:

qt.qpa.drawing: Making <QNSView: ...> layer-backed with
<_NSViewBackingLayer: ...> due to being enabled by macOS
qt.qpa.drawing: Backing properties changed for <QNSView: ...>
qt.qpa.drawing: Updating <_NSViewBackingLayer: ...> content scale to 2
qt.qpa.drawing: [QNSView displayLayer] QWidgetWindow(...)
qt.qpa.drawing: QCocoaWindow::handleExposeEvent QWidgetWindow(...)
QRegion(0,0 296x266) isExposed true
qt.qpa.drawing: [QNSView displayLayer] QWidgetWindow(...)
qt.qpa.drawing: QCocoaWindow::handleExposeEvent QWidgetWindow(...)
QRegion(0,0 296x266) isExposed true

A final handleExposeEvent is reported when the window is closed.
Otherwise, it is quiet regardless of any text entered in the QTextEdit,
or clicks of the clear button.

"Making <QNSView..." is output from (void)setLayer:(CALayer *)layer in
plugins/platform/cocoa/qnsview_drawing.mm, which executes when the top
level widget is shown for the first time.
https://code.woboq.org/qt5/qtbase/src/plugins/platforms/cocoa/qnsview_drawing.mm.html#153

C++ code:

#include <QApplication>
#include <QTextEdit>
#include <QPushButton>
#include <QLayout>
#include <QWidget>
#include <QObject>
#include <QLoggingCategory>

int main(int argc, char *argv[])
{
    QLoggingCategory::setFilterRules("qt.qpa.drawing=true");
    QApplication a(argc, argv);

    QTextEdit edit;
    edit.setText(qVersion());

    QPushButton button("clear");
    QObject::connect(&button, &QPushButton::clicked,
                     &edit,   &QTextEdit::clear);

    QVBoxLayout layout;
    layout.addWidget(&button);
    layout.addWidget(&edit);

    QWidget w;
    w.setLayout(&layout);
    w.show();
    w.raise();

    return a.exec();
}


The PyQt version ouputs:

qt.qpa.drawing: Backing properties changed for <QNSView: ...>
qt.qpa.drawing: [QNSView drawRect:] QWidgetWindow(...) QRegion(0,0 296x266)
qt.qpa.drawing: QCocoaWindow::handleExposeEvent QWidgetWindow(...)
QRegion(0,0 296x266) isExposed false
qt.qpa.drawing: [QNSView drawRect:] QWidgetWindow(...) QRegion(0,0 296x266)
qt.qpa.drawing: QCocoaWindow::handleExposeEvent QWidgetWindow(...)
QRegion(0,0 296x266) isExposed false
qt.qpa.drawing: [QNSView drawRect:] QWidgetWindow(...) QRegion(0,0 296x266)
qt.qpa.drawing: QCocoaWindow::handleExposeEvent QWidgetWindow(...)
QRegion(0,0 296x266) isExposed true
...

The drawRect and handleExposeEvent pair are repeated twice per click of
the clear button, plus once each time the window gains or loses focus. I
don't see anything different between the ineffective redraw, and the
loss of focus redraw that works.

PyQt code:

import sys
from PyQt5.QtWidgets import (QApplication, QTextEdit, QPushButton,
                             QVBoxLayout, QWidget)
from PyQt5.QtCore import (QLoggingCategory, qVersion)

QLoggingCategory.setFilterRules("qt.qpa.drawing=true")
app = QApplication(sys.argv)

edit = QTextEdit()
edit.setText(qVersion())

button = QPushButton("clear")
button.clicked.connect(edit.clear)

layout = QVBoxLayout()
layout.addWidget(button)
layout.addWidget(edit)

w = QWidget()
w.setLayout(layout)
w.show()
w.raise_()

app.exec_()


- Jeremy


More information about the PyQt mailing list