Home Website Youtube GitHub

Better Connect UI Solution for Custom Component

Currently There are a lot of attribute connection code need to manage.

I found a way in Qt System which can easy connect attribute with a simple mapping.
Utilize the Qt Meta Object System, we can find the property related changed signal automatically.
With this idea I developed a Data Binding Framework which much easier for multiple data sync value.


First of all, We can create a dynamic property in Qt Designer.

mgear_attr describe what component attribute that it related.
Then we can read this attr in code and establish connection automatically.


class ConnectAttributeMixin(object):

    PROPERTY_MAPPING = {
        QtWidgets.QCheckBox: "checked",
        QtWidgets.QSpinBox: "value",
        QtWidgets.QDoubleSpinBox: "value",
        QtWidgets.QComboBox: "currentIndex",
        QtWidgets.QLineEdit: "text",
    }
    PROPERTY = "mgear_attr"

    def connect_attributes(self, root):
        # NOTES: iterate all QWidget
        for widget in self.findChildren(QtWidgets.QWidget):
            # NOTES: get custom property value
            attr = widget.property(self.PROPERTY)
            if not attr:
                continue
            # NOTES: get attribute from root
            for cls,prop in self.PROPERTY_MAPPING.items():
                if isinstance(widget,cls):
                    break
            else:
                continue
            
            attr = root.attr(attr)
            
            # NOTES: set attribute
            widget.setProperty(prop, attr.get())
            
            # NOTES: find the updater signal
            meta = widget.metaObject()
            index = meta.indexOfProperty(prop)
            meta_prop = meta.property(index)
            signal_name = meta_prop.notifySignal().name()
            signal = getattr(widget, bytes(signal_name).decode())
            
            # NOTES: connect signal to auto update value
            signal.connect(lambda v=0, a=attr, p=prop, w=widget: a.set(w.property(p)))

class settingsTab(QtWidgets.QDialog, ConnectAttributeMixin, sui.Ui_Form):
    """The Component settings UI"""

    def __init__(self, parent=None):
        super(settingsTab, self).__init__(parent)
        self.setupUi(self)

class componentSettings(MayaQWidgetDockableMixin, guide.componentMainSettings):
    """Create the component setting window"""

    def __init__(self, parent=None):
        self.toolName = GuideInfo.compType
        # Delete old instances of the componet settings window.
        pyqt.deleteInstances(self, MayaQDockWidget)
        super(self.__class__, self).__init__(parent=parent)
        self.settingsTab = settingsTab()
        self.settingsTab.connect_attributes(self.root)

I create a ConnectAttributeMixin class inherit by the settingsTab.
then just pass the self.root into connect_attributes, then everything setup correctly.
Much easier to read and write.

Currently, this solution not support for QListWidget, More widget support can add to PROPERTY_MAPPING constans variable.

How it work

PROPERTY_MAPPING is mapping the Qt property, you can use QMetaObject to find what kind of the porperty inside the QObject.

widget = QtWidgets.QLineEdit()
meta = widget .metaObject()
for i in range(meta.propertyCount()):
    prop = meta.property(i)
    print(prop.name())

This code will print available property for a certain type of QObject.
Such as QLineEdit, we can find a property call text.
Then alternatively, we can using setProperty to modify the text value other than setText.

widget = QtWidgets.QLineEdit()
widget.setProperty("text",'asd')
print(widget.text()) # widget.property("text") also get the same value
# asd

But the good things here, with the help of QMetaProperty, we can find the reletated notifySignal and then setup the auto connection.

2 Likes

Hello @timmyliang
That is very interesting! Thanks for the contributions. :smiley: