简体   繁体   中英

Custom widget in pyside (python/Qt) won't show up

I am just trying my first prototype in pyside (python/Qt). The application itself starts up fine, creates a window with widgets according to my layout. Threads are started and execute, all fine. Except...

I want to enhance the GUI by adding some custom widget indicating the execution state of the threads. So I thought flashing LEDs would be fine for that. For this I try to implement a custom LED widget.

Remember that I currently try to learn python, so there might be some strange approaches in this. Anyway, here is the LED widgets class in its current state:

from PySide import QtCore, QtGui

class LED(QtGui.QWidget):

    class Mode:
        STATIC_OFF   = 0
        STATIC_ON    = 1
        FLASH_SLOW   = 2
        FLASH_MEDIUM = 2
        FLASH_FAST   = 3

    class Color:
        BLACK  = '#000000'
        GREEN  = '#00FF00'
        RED    = '#FF0000'
        BLUE   = '#0000FF'
        YELLOW = '#FFFF00' 
        WHITE  = '#FFFFFF'

    mode   = Mode.STATIC_ON
    color  = Color.BLACK
    radius = 10
    status = False
    timer  = None

    outdated = QtCore.Signal()

    def __init__(self, mode, color, radius, parent=None):
        super(LED, self).__init__(parent)
        self.outdated.connect(self.update)
        self.setMode(mode,False)
        self.setColor(color,False)
        self.setRadius(radius,False)
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.adjustAppearance)
        self.adjustAppearance()

    def getCenter(self):
        return QtCore.QPoint(self.radius, self.radius)

    def getBox(self):
        return QtCore.QRect(self.radius, self.radius)

    def setColor(self, color, update=True):
        assert color in (self.Color.GREEN,self.Color.RED,self.Color.BLUE,self.Color.YELLOW,self.Color.WHITE), "invalid color"
        self.color = color
        if update:
            self.adjustAppearance()

    def setMode(self, mode, update=True):
        assert mode in (self.Mode.STATIC_OFF,self.Mode.STATIC_ON,self.Mode.FLASH_SLOW,self.Mode.FLASH_MEDIUM,self.Mode.FLASH_FAST),"invalid mode"
        self.mode = mode
        if update:
            self.adjustAppearance()

    def setRadius(self, radius, update=True):
        assert isinstance(radius, int), "invalid radius type (integer)"
        assert 10<=radius<=100, "invalid radius value (0-100)"
        self.radius = radius
        if update:
            self.adjustAppearance()

    def switchOn(self):
        self.status = True
        self.adjustAppearance()

    def switchOff(self):
        self.status = False
        self.adjustAppearance()

    def adjustAppearance(self):
        if self.mode is self.Mode.STATIC_OFF:
            self.status = False
            self.timer.stop()
        elif self.mode is self.Mode.STATIC_ON:
            self.status = True
            self.timer.stop()
        elif self.mode is self.Mode.FLASH_SLOW:
            self.status = not self.status
            self.timer.start(200)
        elif self.mode is self.Mode.FLASH_SLOW:
            self.status = not self.status
            self.timer.start(500)
        elif self.mode is self.Mode.FLASH_SLOW:
            self.status = not self.status
            self.timer.start(1000)
            self.outdated.emit()

    def paintEvent(self, event):
        painter = QtGui.QPainter()
        painter.begin(self)
        self.drawWidget(event, painter)
        painter.end()

    def drawWidget(self, event, painter):
        if self.status:
            shade = QtGui.QColor(self.color).darker
        else:
            shade = QtGui.QColor(self.color).lighter
        #painter.setPen(QtGui.QColor('black'), 1, QtCore.Qt.SolidLine)
        painter.setPen(QtGui.QColor('black'))
        painter.setBrush(QtCore.Qt.RadialGradientPattern)
        painter.drawEllipse(self.getCenter(), self.radius, self.radius)

My problem is that the widget simply does not show when I add it to the windows layout. Other widgets (non-custome, plain Qt widgets) do show, so I gues it is not a question of creating the widget, not a question of how I use the widget. Nevertheless here is the (shortened) instanciation if the widget:

class View(QtGui.QMainWindow):

    ui = None

    def __init__(self, config, parent=None):
        log.debug("window setup")
        self.config = config
        super(View, self).__init__(parent)
        try:
            self.ui = self.Ui(self)
            self.setObjectName("appView")
            self.setWindowTitle("AvaTalk")
            self.show()
        except RuntimeError as e:
            log.error(e.message)

    class Ui(QtCore.QObject):

        # [...]
        iconDetector = None
        buttonDetector = None
        # [...]

        def __init__(self, window, parent=None):
            log.debug("ui setup")
            super(View.Ui, self).__init__(parent)
            self.window = window

            # central widget
            log.debug("widget setup")
            self.centralWidget = QtGui.QWidget()
            self.widgetLayout = QtGui.QVBoxLayout(self.centralWidget)

            # create toolbars
            #self.createMenubar()
            #self.createCanvas()
            self.createToolbar()
            #self.createStatusbar()

            # visualize widget
            self.window.setCentralWidget(self.centralWidget)

            # actions
            log.debug("actions setup")
            self.actionQuit = QtGui.QAction(self.window)
            self.actionQuit.setObjectName("actionQuit")
            self.menuFile.addAction(self.actionQuit)
            self.menubar.addAction(self.menuFile.menuAction())

            log.debug("connections setup")
            QtCore.QObject.connect(self.actionQuit, QtCore.SIGNAL("activated()"), self.window.close)
            QtCore.QMetaObject.connectSlotsByName(self.window)

        def createToolbar(self):
            log.debug("toolbar setup")
            self.toolbar = QtGui.QHBoxLayout()
            self.toolbar.setObjectName("toolbar")
            self.toolbar.addStretch(1)
            # camera
            # detector
            self.iconDetector = LED(LED.Mode.STATIC_OFF,LED.Color.GREEN,10,self.window)
            self.buttonDetector = IconButton("Detector", "detector",self.window)
            self.toolbar.addWidget(self.iconDetector)
            self.toolbar.addWidget(self.buttonDetector)
            self.toolbar.addStretch(1)
            # analyzer
            # extractor
            # layout
            self.widgetLayout.addLayout(self.toolbar)

It might well be that the actual painting using QPainter is still nonsense. I did not yet come to test that: actually when testing I find that isVisible() returns False on the widget after the setup has completed. So I assume I miss a central point. Unfortunately I am unable to find out what I miss...

Maybe someone can spot my issue? Thanks !

One thing to be careful when implementing custom widgets derived from QWidget is: sizeHint or minimumSizeHint for QWidget returns invalid QSize by default. This means, if it is added to a layout, depending on the other widgets, it will shrink to 0 . This effectively makes it 'not-visible'. Although, isVisible would still return True . Widget is 'visible', but it just doesn't have anything to show (0 size). So, if you're getting False , there is definitely another issue at hand.

So it is necessary to define these two methods with sensible sizes:

class LED(QtGui.QWidget):
    # ...

    def sizeHint(self):
        size = 2 * self.radius + 2
        return QtCore.QSize(size, size)

    def minimumSizeHint(self):
        size = 2 * self.radius + 2
        return QtCore.QSize(size, size)

Note: There are other issues:

  • Like defining mode , color , etc as class attributes and then overriding them with instance attributes. They won't break anything but they are pointless.
  • painter.setBrush(QtCore.Qt.RadialGradientPattern) is wrong. You can't create a brush with QtCore.Qt.RadialGradientPattern . It is there, so that brush.style() can return something. If you want a gradient pattern you should create a brush with QGradient constructor .
  • if self.mode is self.Mode.STATIC_OFF : comparing with is is wrong. is compares identity, you want == here. (also, FLASH_SLOW and FLASH_MEDIUM are both 2 )
  • assert is for debugging and unit tests. You shouldn't use it in real code. Raise an exception if you want. Or have sensible default s, where invalid values would be replaced with that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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