Search code examples
pythonenumspropertiespyqtqt-designer

How to use Q_ENUMS in PyQt


I am creating a custom widget that inherits from QLabel, and I would like to have a property on my widget to represent how the data must be formatted when presenting to the user.

For that I am trying to use Q_ENUMS, but I'm not having much success. I can get the property to show in Designer, but the UI file saved shows the enum as PyDMLabel::STRING and not as I would expect DisplayFormat::STRING.

Here is my code for the widget:

class PyDMLabel(QLabel, PyDMWidget):
    class DisplayFormat:
        DEFAULT = 0
        STRING = 1
        DECIMAL = 2
        EXPONENTIAL = 3
        HEX = 4
        BINARY = 5

    Q_ENUMS(DisplayFormat)

    """
    A QLabel with support for Channels and more from PyDM

    Parameters
    ----------
    parent : QWidget
        The parent widget for the Label
    init_channel : str, optional
        The channel to be used by the widget.
    """
    def __init__(self, parent=None, init_channel=None):
        QLabel.__init__(self, parent)
        PyDMWidget.__init__(self, init_channel=init_channel)

        self.setTextFormat(Qt.PlainText)
        self.setTextInteractionFlags(Qt.NoTextInteraction)
        self.setText("PyDMLabel")
        self._display_format_type = PyDMLabel.DisplayFormat.DEFAULT

    @pyqtProperty(DisplayFormat)
    def displayFormat(self):
        return self._display_format_type

    @displayFormat.setter
    def displayFormat(self, new_type):
        if self._display_format_type != new_type:
            self._display_format_type = new_type

What is the correct way to deal with Q_ENUMS and PyQt?


Solution

  • UPDATE:

    As of PyQt-5.11, Q_ENUMS/Q_FLAGS have been deprecated, so that Q_ENUM/Q_FLAG should be used instead. This does not affect the original solution given below, which will still work with either Q_ENUM or Q_ENUMS. However, in PyQt6, these functions are no longer available and have been replaced by pyqtEnum. This means user-defined enums must now be subclasses of enum.Enum and be decorated accordingly. For consistency with the built-in APIs, they should probably also be fully scoped (i.e. so that PyDMLabel.STRING is no longer allowed), but this is not strictly necessary:

    from enum import Enum
    from PyQt6.QtCore import pyqtEnum
    
    @pyqtEnum
    class DisplayFormat(Enum):
        DEFAULT = 0
        STRING = 1
        DECIMAL = 2
        EXPONENTIAL = 3
        HEX = 4
        BINARY = 5
    
    class PyDMLabel(QLabel, PyDMWidget):
        DisplayFormat = DisplayFormat
    

    Original Solution (PyQt5):

    In order for Qt (Designer) to see an enum, PyQt has to add it to the meta-object of the custom class. So it could never be referred to by Qt as DisplayFormat::STRING.

    In Qt, enums declared in the class scope expose their constants as members of the class. So for example, the QComboBox class defines an InsertPolicy enum, and the constants can be referred to like this: QComboBox::InsertAtTop. So in that respect, the behaviour of PyQt enums in Qt Designer plugins is exactly as expected, since the ui file shows PyDMLabel::STRING.

    However, getting fully equivalent behaviour in Python code requires some extra work. The nearest I could come up with is this:

    class DisplayFormat:
        DEFAULT = 0
        STRING = 1
        DECIMAL = 2
        EXPONENTIAL = 3
        HEX = 4
        BINARY = 5
    
    class PyDMLabel(QLabel, PyDMWidget, DisplayFormat):
        DisplayFormat = DisplayFormat
    
        Q_ENUM(DisplayFormat)
    

    This will still result in Qt Designer using PyDMLabel::STRING (as expected). But Python code can now access the constants in any of these ways:

    PyDMLabel.STRING
    PyDMLabel.DisplayFormat.STRING
    DisplayFormat.STRING
    

    And in fact, if you don't mind losing the second of these options, you could simplify things even further to this:

    class DisplayFormat:
        DEFAULT = 0
        ...    
    
    class PyDMLabel(QLabel, PyDMWidget, DisplayFormat):    
        Q_ENUM(DisplayFormat)
    

    PS:

    In PyQt5, it does not matter what the type of the enum class is - so it could be a subclass of int, a plain object, or even a python enum.