Use QEnum like a Python Enum
Question:
Let’s say I have a python enum:
class UserState(Enum):
OFFLINE = auto()
ONLINE = auto()
BUSY = auto()
I can access the different options with UserState.ONLINE
, UserState.OFFLINE
or UserState.BUSY
.
If I wanted to make this a QEnum so I can use it in QML, I’d need to wrap it inside a QObject
like this:
class UserState(QObject):
@QEnum
class Options(Enum):
OFFLINE = auto()
ONLINE = auto()
BUSY = auto()
In QML I can access this enum now the same way I’d access a normal python enum in python. However if I wanted to access this enum from python, I’d have to write UserState.Options.ONLINE
.
How can I create an enum that will work in python as well as QML using the same syntax?
I have found a solution for this which I will post in the answers section. However it involves nested metaclasses which just doesn’t look right. I think the optimal solution would be a class that derives from QObject as well as Enum to have all the functionality for every context.
If anyone can provide a version which works like that, I will make that the accepted answer.
Otherwise you can tell me, why my solution actually is a good one.
Answers:
Here is a class I have written which adds the support I’m asking for in the question:
class CustomEnumMeta(type(Enum)):
def __new__(cls, name, bases, attrs):
# don't change type of base class
if name == "CustomEnum":
return super().__new__(cls, name, bases, attrs)
original_attrs = attrs.copy()
enum = super().__new__(cls, name, bases, attrs)
class WrapperMeta(type(QObject)):
def __new__(cls, wrapper_name, wrapper_bases, wrapper_attrs):
return super().__new__(cls, wrapper_name, wrapper_bases, {**original_attrs, **wrapper_attrs})
class Wrapper(QObject, metaclass=WrapperMeta):
QEnum(enum)
Wrapper.__name__ = name
return Wrapper
class CustomEnum(Enum, metaclass=CustomEnumMeta):
pass
The CustomEnum class can be inherited just like a normal python enum:
class UserState(CustomEnum):
OFFLINE = auto()
ONLINE = auto()
BUSY = auto()
Now you can use UserState
the same way in python as in QML. This is a valid statement for both python and QML: UserState.ONLINE
My implementation works by replacing the original class with a QObject, nesting the original class. And then copying all the attributes of the nested class to the outer class to make them accessible from python.
You can just call QEnum inside the class and define the enum outside.
Example (PySide6):
file: main.py
import sys
from enum import Enum
from pathlib import Path
from PySide6.QtCore import Property, QEnum, QObject
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import (
QmlElement,
QQmlApplicationEngine,
qmlRegisterSingletonInstance,
)
QML_IMPORT_NAME = "com.example.app"
QML_IMPORT_MAJOR_VERSION = 1
class Status(Enum):
Connected, Disconnected, Stale = range(3)
@QmlElement
class Enums(QObject):
QEnum(Status)
class App(QObject):
def __init__(self):
super().__init__(None)
@Property(int, constant=True)
def enumValue(self):
return Status.Connected.value
if __name__ == "__main__":
loop = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
app = App()
qmlRegisterSingletonInstance(QObject, "com.example.app", 1, 0, "App", app)
qml_file = Path(__file__).parent / "main.qml"
engine.load(str(qml_file))
sys.exit(loop.exec())
file: main.qml
import QtQuick
import com.example.app 1.0
Window {
visible: true
width: 1000
height: 700
title: "POC"
Rectangle {
color: App.enumValue == Enums.Status.Connected ? "green" : "red"
anchors.fill: parent
}
}
Full code here
Let’s say I have a python enum:
class UserState(Enum):
OFFLINE = auto()
ONLINE = auto()
BUSY = auto()
I can access the different options with UserState.ONLINE
, UserState.OFFLINE
or UserState.BUSY
.
If I wanted to make this a QEnum so I can use it in QML, I’d need to wrap it inside a QObject
like this:
class UserState(QObject):
@QEnum
class Options(Enum):
OFFLINE = auto()
ONLINE = auto()
BUSY = auto()
In QML I can access this enum now the same way I’d access a normal python enum in python. However if I wanted to access this enum from python, I’d have to write UserState.Options.ONLINE
.
How can I create an enum that will work in python as well as QML using the same syntax?
I have found a solution for this which I will post in the answers section. However it involves nested metaclasses which just doesn’t look right. I think the optimal solution would be a class that derives from QObject as well as Enum to have all the functionality for every context.
If anyone can provide a version which works like that, I will make that the accepted answer.
Otherwise you can tell me, why my solution actually is a good one.
Here is a class I have written which adds the support I’m asking for in the question:
class CustomEnumMeta(type(Enum)):
def __new__(cls, name, bases, attrs):
# don't change type of base class
if name == "CustomEnum":
return super().__new__(cls, name, bases, attrs)
original_attrs = attrs.copy()
enum = super().__new__(cls, name, bases, attrs)
class WrapperMeta(type(QObject)):
def __new__(cls, wrapper_name, wrapper_bases, wrapper_attrs):
return super().__new__(cls, wrapper_name, wrapper_bases, {**original_attrs, **wrapper_attrs})
class Wrapper(QObject, metaclass=WrapperMeta):
QEnum(enum)
Wrapper.__name__ = name
return Wrapper
class CustomEnum(Enum, metaclass=CustomEnumMeta):
pass
The CustomEnum class can be inherited just like a normal python enum:
class UserState(CustomEnum):
OFFLINE = auto()
ONLINE = auto()
BUSY = auto()
Now you can use UserState
the same way in python as in QML. This is a valid statement for both python and QML: UserState.ONLINE
My implementation works by replacing the original class with a QObject, nesting the original class. And then copying all the attributes of the nested class to the outer class to make them accessible from python.
You can just call QEnum inside the class and define the enum outside.
Example (PySide6):
file: main.py
import sys
from enum import Enum
from pathlib import Path
from PySide6.QtCore import Property, QEnum, QObject
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import (
QmlElement,
QQmlApplicationEngine,
qmlRegisterSingletonInstance,
)
QML_IMPORT_NAME = "com.example.app"
QML_IMPORT_MAJOR_VERSION = 1
class Status(Enum):
Connected, Disconnected, Stale = range(3)
@QmlElement
class Enums(QObject):
QEnum(Status)
class App(QObject):
def __init__(self):
super().__init__(None)
@Property(int, constant=True)
def enumValue(self):
return Status.Connected.value
if __name__ == "__main__":
loop = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
app = App()
qmlRegisterSingletonInstance(QObject, "com.example.app", 1, 0, "App", app)
qml_file = Path(__file__).parent / "main.qml"
engine.load(str(qml_file))
sys.exit(loop.exec())
file: main.qml
import QtQuick
import com.example.app 1.0
Window {
visible: true
width: 1000
height: 700
title: "POC"
Rectangle {
color: App.enumValue == Enums.Status.Connected ? "green" : "red"
anchors.fill: parent
}
}
Full code here