pythonpython-3.xpyqt5

Getting and Setting the values of PyQT Widgets that have been dynamically created


I am trying to create a settings page for my application that takes a dictionary list of settings and automatically builds the configurable list of settings. The issue I am running into is what is the best way to get and set the value of the widget if the widget class can be anything. For example, when loading up the program it will need to fetch the lastest config for the settings and load that when the settings page is built. If you have a QTextEdit and QCheckBox, the methods you would use to do this would be different (setChecked for the checkbox and setText for the text box). I have a semi-functional script, but it feels extremely messy, and I want to know if there is a better way.

From what I have managed to come up with so far it looks like this

The settings dictionary looks like this:

settings = {
     'name' : {'value' : 'test', 'control' : QLabel(), 'label' : 'Name'},
     'enabled' : {'value' : True, 'control' : QCheckBox(), 'label' : 'Enabled'}
}

The code that builds the layout looks like this:

    def build_settings(settings):
        widget = QWidget()
        layout = QVBoxLayout()

        for key, setting in settings.items():
            display = QHBoxLayout()
            display.addWidget(QLabel(setting['label']))
            control = setting['control']
            control = set_widget_value(control, setting['value'])
            signal = widget_listener(control)
            if signal != 'null':
                signal.connect(update_settings(key, get_widget_value(control)))
            
            display.addWidget(control)
            layout.addLayout(display)

        widget.setLayout(layout)
        return widget

I have added a few helper functions to try and make this work. I created a dictionary of strings that maps the widget type to a method and used getattr() to try and make functions that work as generic getters and setters for any widget based on the type passed in, but this is going to get cumbersome if I have to manually do this for each widget type (I have only done the two below for testing).

QtWidgets = {
    'QCheckBox' : {'update' : 'setChecked', 'signal' : 'stateChanged', 'value': 'checkState'},
    'QLabel' : {'update' : 'setText', 'signal' : 'null', 'value': 'text'},
}

def set_widget_value(widget, value):
    update = QtWidgets[widget.__class__.__name__]['update']
    if update != 'null':
        set = getattr(widget, update)
        set(value)
        return widget
    else:
        return 'null'

def get_widget_value(widget):
    value = QtWidgets[widget.__class__.__name__]['value']
    if value != 'null':
        value = getattr(widget, value)
        return value
    else:
        return 'null'

def widget_listener(widget):   
    signal = QtWidgets[widget.__class__.__name__]['signal']
    if signal != 'null':
        listener = getattr(widget, signal)
        return listener
    else:
        return 'null'

Do you know of any better method for handling this? Given I am new to PyQT maybe I am missing a built-in way to do this, but I have been searching for documentation and haven't figured out how this might be handled.


Solution

  • Based on Musicamante's suggestions I have modified my helper functions to get rid of the dictionary of functions and instead used QObjectMeta to get and set properties. As noted there might be some additional error handling required but for now this is a working solution.

    def set_widget_value(widget, value):
        widget_meta = widget.metaObject()
        if widget_meta.userProperty().name() != None:
            widget_user_prop = widget_meta.userProperty().name()
            widget.setProperty(widget_user_prop, value)
        else:
            widget.setProperty('text', value)
        return widget
    
    def get_widget_value(widget):
        widget_meta = widget.metaObject()
        if widget_meta.userProperty().name() != None:
            widget_user_prop = widget_meta.userProperty().name()
            return widget.property(widget_user_prop)
        else:
            return widget.Property('text')
        
    def get_widget_signal(widget):
        widget_meta_property = widget.metaObject().userProperty()
        if widget_meta_property.name() != None:
            signal_name = widget_meta_property.notifySignal().name().data().decode('utf8')
            return signal_name
        else:
            return None