0

I'm learning PyQt / Qt and I am facing a basic problem. I want to make a child class that inherits from QWidget but for some reason it does not show.

For trouble shooting, I've used a simple dummy child class.

from PyQt6.QtWidgets import QWidget, QApplication,QMainWindow, QLabel
import sys

class TestWidget(QWidget):

    pass

class TestLabel(QLabel):

    pass

app = QApplication(sys.argv)
w = QMainWindow()
w.resize(500,500)
w.setStyleSheet('background-color: white;')
w.show()

#frame = QWidget(w)     # SHOWS
frame = TestWidget(w)   # DOES NOT SHOW
#frame = TestLabel(w)   # SHOWS
frame.resize(200,200)
frame.setStyleSheet('background-color: red;')
frame.show()

app.exec()

In the code sample I have tested the following scenarios (by commenting out the other two options). The expected result is a red rectangle in the upper left corner:

  1. Using a simple QWidget; it shows
  2. Using a dummy child class of QWidget; it does not show
  3. Using a dummy child class of QLabel; it shows

The code is really simple so I'm struggling to understand what's going on.

I'm using Python 3.11 on Mac OS Ventura (Apple Silicon).

Any ideas?

Bearify
  • 43
  • 1
  • 5
  • As explained in the [QSS documentation](https://doc.qt.io/qt-6/stylesheet-reference.html#qwidget-widget): "If you subclass from QWidget, you need to provide a paintEvent for your custom QWidget". The QLabel case works because it implements the drawing made by QStyle (inherited by QFrame, on which QLabel is based). – musicamante Nov 19 '22 at 03:30
  • OK, I was not aware about that - thanks! – Bearify Nov 19 '22 at 03:55

1 Answers1

0

It's because the QWidget class is the base class of all widgets and designed to have no drawing(painting) logic in it even for the background by default. In most cases of deriving from the QWidget, you either implement a custom drawing logic by overriding paintEvent() or use it as an invisible event receiver.

The behavior that a QWidget instance(not a derived one) with the background specified by the style sheet draws the background is documented in the official Qt wiki at this, or in the official reference at this.(The second link was given by musicamante.) But it is rather controversial because it should not draw the background in the view point that the QWidget does not have drawing logic in paintEvent(), but it should draw the background in the view point that an HTML element with the CSS background is drawn with the background.

If you want to draw the background without overriding paintEvent(), use the autoFillBackground property like this.

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPalette
...

class TestWidget(QWidget):
    pass

...
frame = TestWidget(w)
frame.setAutoFillBackground(True)
pal = QPalette()
pal.setColor(QPalette.ColorRole.Window, Qt.GlobalColor.red)
frame.setPalette(pal)
...

Of course, simply deriving your class from the QFrame or QLabel will be much easier.

relent95
  • 3,703
  • 1
  • 14
  • 17
  • Thanks! I was not aware of that. I will probably use QFrame then, at least now I know what's going on. However, I tested your code and it does not compile for me with PyQt6; I get AttributeError: type object 'QPalette' has no attribute 'Window'. Does it work for you? – Bearify Nov 19 '22 at 03:59
  • @Bearify PyQt6 introduced a new way of accessing enums, which now (unfortunately) require the complete name space. In this case, it is `QPalette.ColorRole.Window` and `Qt.GlobalColor.red`. – musicamante Nov 19 '22 at 04:19
  • Thanks, yes I've noticed changes to the enums in PyQt6 and I tested the ones that you mention. It runs, however the red rectangle still does not show. I've had similar problems before when using QPalette, so not sure what is going on. I'm a newbie when it comes to Qt. I'm using the code from @relent95 as is with the changes to the enums that you mentioned. I'm fine with using QFrame though. – Bearify Nov 19 '22 at 04:26
  • @relent95 It's not inconsistent. The default, expected behavior is that a *real* QWidget type instance uses the `PE_Widget` primitive drawing, while any subclass must override painting on its own, because any widget inherits from QWidget: the concept is that a basic QWidget will respect basic QStyle/QSS drawing, but subclassing *requires* overriding, which makes sense, as you'll probably need custom drawing for a subclass, so there's no point in providing a default behavior that would be probably ignored anyway, and could be still be provided by using QStyle's `drawPrimitive(PE_Widget)`. – musicamante Nov 19 '22 at 04:27
  • @Bearify the above code will work only if you did **not** set stylesheets that are valid for the widget at hand. QSS ("Qt Style Sheets"), just like CSS, *override* the default behavior and are *cascading*, meaning that they completely ignore the palette whenever they set color properties, and are inherited by child widgets. If you're still using your original code, you can comment the `setStyleSheet()` lines and it will work. Even better: comment the `frame.setStyleSheet()` line and change the argument of the other to `QMainWindow { background-color: white; }`. – musicamante Nov 19 '22 at 04:33
  • @Bearify The above is because you should generally avoid generic properties in any case, and use [selectors](//doc.qt.io/qt-6/stylesheet-syntax.html#selector-types) instead. Using `setStyleSheet('background: ...')` will cause **all** widgets (the one referred to as `self` and *all* its children) to inherit that background and, as explained above, override the palette settings. Remember: setting color properties in style sheets will always override the palette, so whenever you use `setStyleSheet()` with a basic color value, any palette setting will be completely ignored. – musicamante Nov 19 '22 at 04:37
  • OK understood. I removed the stylesheet for the parent window and it works now. Now I've learnt more about how to give color to widgets. Thanks again – Bearify Nov 19 '22 at 04:39