Search code examples
pythonqtqmlpyside6

Is there a cleaner way for backend in PySide6 for QML?


Hello I am using Model class to supply items for lists and comboboxes. The problem is that I use the setContextProperty() function every time for each element. I'm looking for a solution where all elements(list and comboboxes) use the same ContextProperty. Furthermore with this way I guess JSON files can be loaded dynamically instead of loading all of them at the beginning.

main.py

class Model(QAbstractListModel, QObject):
    """ it reads JSON file, that is given as argument,
    and creates the model"""

if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    model1 = Model("file1.json")
    model2 = Model("file2.json")
    model3 = Model("file3.json")
    model4 = Model("file4.json")
    model5 = Model("file5.json")

    engine.rootContext().setContextProperty("model1", model1)
    engine.rootContext().setContextProperty("model2", model2)
    engine.rootContext().setContextProperty("model3", model3)
    engine.rootContext().setContextProperty("model4", model4)
    engine.rootContext().setContextProperty("model5", model5)

    engine.rootContext().setContextProperty("applicationDirPath", os.path.dirname(__file__))
    engine.load(os.path.join(os.path.dirname(__file__), "main.qml"))

    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec())

Solution

  • You can create a QObject that exposes all the models as a property list and then use a Repeater to dynamically create the comboboxes.

    The following is a demo:

    import os
    import sys
    from pathlib import Path
    
    from PySide6.QtCore import Property, QCoreApplication, QObject, Qt, QUrl, Signal
    from PySide6.QtGui import QGuiApplication, QStandardItem, QStandardItemModel
    from PySide6.QtQml import QQmlApplicationEngine
    
    CURRENT_DIRECTORY = Path(__file__).resolve().parent
    
    
    class Model(QStandardItemModel):
        def __init__(self, values, parent=None):
            super().__init__(parent)
            for value in values:
                item = QStandardItem(value)
                self.appendRow(item)
    
    
    class Manager(QObject):
        models_changed = Signal(name="modelsChanged")
    
        def __init__(self, parent=None):
            super().__init__(parent)
            self._models = []
    
        @Property("QVariantList", notify=models_changed)
        def models(self):
            return self._models
    
        def append_model(self, model):
            self._models.append(model)
            self.models_changed.emit()
    
    
    def main():
        app = QGuiApplication(sys.argv)
    
        manager = Manager(app)
    
        manager.append_model(Model(["item11", "item12", "item13"]))
        manager.append_model(Model(["item21", "item22", "item23"]))
        manager.append_model(Model(["item31", "item32", "item33"]))
        manager.append_model(Model(["item41", "item42", "item43"]))
    
        engine = QQmlApplicationEngine()
    
        context = engine.rootContext()
        context.setContextProperty("applicationDirPath", os.fspath(CURRENT_DIRECTORY))
        context.setContextProperty("managerModel", manager)
    
        filename = os.fspath(CURRENT_DIRECTORY / "main.qml")
        url = QUrl.fromLocalFile(filename)
    
        def handle_object_created(obj, obj_url):
            if obj is None and url == obj_url:
                QCoreApplication.exit(-1)
    
        engine.objectCreated.connect(handle_object_created, Qt.QueuedConnection)
        engine.load(url)
    
        sys.exit(app.exec())
    
    
    if __name__ == "__main__":
        main()
    
    import QtQuick
    import QtQuick.Controls
    import QtQuick.Layouts
    
    ApplicationWindow {
        width: 640
        height: 480
        visible: true
    
        ColumnLayout {
            anchors.centerIn: parent
    
            Repeater {
                id: repeater
    
                model: managerModel.models
    
                ComboBox {
                    model: modelData
                    textRole: "display"
                    Layout.fillWidth: true
                }
    
            }
    
        }
    
    }
    

    Side note: A QAbstractListModel is a QObject so the double inherence is useless so you should change it to: class Model(QAbstractListModel):