Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Is it possible to pass an instance of a derived `QAbstractListModel` from backend to QML without using `setContextProperty`?



  • Hello,

    I'm trying to figure out how to pass an instance of a model to QML from the backend, but I don't want to use setContextProperty to expose said model.
    Because I want to dynamically change the model the view is using.

    I know that I can register the model as a QML type and then instantiate it on the QML side, but doing this will lock me out of changing the model data from the backend,
    because as far as I know, the backend doesn't have references to QML instantiated classes.

    I tried registering QAbstractListModel as a QML type and then pass it as a return value of a slot, like so:

        @Slot(result=VideoModel)
        def get_video_model(self):
            return self.video_model
    

    And in QML

        Shortcut {
            id: getModelShortuct
            sequences: ["Ctrl+W"]
            onActivated: {
                console.log("getModelShortuct activated")
                console.log("bridge.get_video_model()= " + bridge.get_video_model())
            }
        }
    

    Where bridge is the class that's exposed through setContextProperty .
    When trying this, I get "Error: Unknown method return type: QAbstractListModel* ". This also happens when registering the video model class specifically as a QML type, not just QAbstractListModel

    Alternatively, I wonder if it's possible to expose a class throughsetContextProperty and have one of its class variables be a model and then change the value of the said class variable. For example

    class ModelWrapper(QObject):
    
        def __init__(self):
        super().__init__()
        self.model = SomeModel()
    
        def switch_model(self):
            self.model = AnotherModel()
    
    

    And in QML:

    GridView{
        id:myGridview
        model:ModelWrapper.model
    }
    
    Button{
        id:modelSwitchButton
        onClicked{
            ModelWrapper.switch_model()
        }
    }
    


  • So, answering my own question:

    Do you mean like it's described here?

    Yes, it is indeed how it's described here.

    Also, do SomeModel and AnotherModel needs to be registered as a QML type, or is it enough to register QAbstractListModel if both of them derive from it?

    No. Apparently, you need to specify that the 'return' type is just a QObject neither QAbstractListModel nor its derived classes need to be registered.


    This is what I ended up doing to test this:

    
    class CurrentView(QObject):
    
        def __init__(self):
            super().__init__()
            self.person_model: PersonModel = PersonModel()
            self.video_model: VideoModel = VideoModel()
            self.search_backend: SearchBackend = SearchBackend(self.person_model, self.video_model)
            self._current_model = self.person_model # setting current model to this for test
            self.search_backend.video_search("", "Name", False) #fills models with data
    
        def _current_model(self):
            return self._current_model
    
        @Signal
        def current_model_changed(self):
            pass
    
        @Slot()
        def swap_model(self): #called from QML
            logger.info(f"Swapping Model...")
            if self._current_model == self.video_model:
                self._current_model = self.person_model
            else:
                self._current_model = self.video_model
            self.current_model_changed.emit()
    
        #the return type needs to be QObject.
        #At first I tried QAbstractListModel or it's derived classes. Didn't work.
        current_model = Property(QObject, _current_model, notify=current_model_changed) 
    
    

    In QML:

    Shortcut {
        id: swapModels
        sequences: ["Ctrl+W"]
        onActivated: {
            console.log("Swapping Models...")
            current_view.swap_model()
        }
    }
    
    GridView {
        id: gridview
        anchors.fill: parent
        model: current_view.current_model
        delegate: MyDelegate{}
    
    }
    

    CurrentView is exposed through:

        cw = CurrentView()
        engine.rootContext().setContextProperty("current_view", cw)
    

    It works like a charm, when I press Ctrl+W the model instantly switches.

    This SO answer helped me figure out the missing pieces. Thank you @raven-worx for pointing me in the right direction.


  • Moderators

    @Curtwagner1984
    sure, if ModelWrapper.model is a property with a notifier signal



  • Thank you for the reply.

    Do you mean like it's described here?

    Also, do SomeModel and AnotherModel needs to be registered as a QML type, or is it enough to register QAbstractListModel if both of them derive from it?



  • So, answering my own question:

    Do you mean like it's described here?

    Yes, it is indeed how it's described here.

    Also, do SomeModel and AnotherModel needs to be registered as a QML type, or is it enough to register QAbstractListModel if both of them derive from it?

    No. Apparently, you need to specify that the 'return' type is just a QObject neither QAbstractListModel nor its derived classes need to be registered.


    This is what I ended up doing to test this:

    
    class CurrentView(QObject):
    
        def __init__(self):
            super().__init__()
            self.person_model: PersonModel = PersonModel()
            self.video_model: VideoModel = VideoModel()
            self.search_backend: SearchBackend = SearchBackend(self.person_model, self.video_model)
            self._current_model = self.person_model # setting current model to this for test
            self.search_backend.video_search("", "Name", False) #fills models with data
    
        def _current_model(self):
            return self._current_model
    
        @Signal
        def current_model_changed(self):
            pass
    
        @Slot()
        def swap_model(self): #called from QML
            logger.info(f"Swapping Model...")
            if self._current_model == self.video_model:
                self._current_model = self.person_model
            else:
                self._current_model = self.video_model
            self.current_model_changed.emit()
    
        #the return type needs to be QObject.
        #At first I tried QAbstractListModel or it's derived classes. Didn't work.
        current_model = Property(QObject, _current_model, notify=current_model_changed) 
    
    

    In QML:

    Shortcut {
        id: swapModels
        sequences: ["Ctrl+W"]
        onActivated: {
            console.log("Swapping Models...")
            current_view.swap_model()
        }
    }
    
    GridView {
        id: gridview
        anchors.fill: parent
        model: current_view.current_model
        delegate: MyDelegate{}
    
    }
    

    CurrentView is exposed through:

        cw = CurrentView()
        engine.rootContext().setContextProperty("current_view", cw)
    

    It works like a charm, when I press Ctrl+W the model instantly switches.

    This SO answer helped me figure out the missing pieces. Thank you @raven-worx for pointing me in the right direction.


  • Qt Champions 2018

    QAbstractListModel as a property type or a return type should work, using QObject instead is not the correct solution.
    Have you tried calling qRegisterMetaType?


  • Moderators

    @GrecKo said

    using QObject instead is not the correct solution

    and why?
    Context properties also dont't require a type registration and still can be used as models. Since the cast / type-check anyway happens on the qobject instance.


  • Qt Champions 2018

    Because you lose the type information than can be used by the QML engine/compiler and Qt Creator.
    Also root context properties should not be used anymore, access lookup is heavy for them.



  • @GrecKo said in Is it possible to pass an instance of a derived `QAbstractListModel` from backend to QML without using `setContextProperty`?:

    Have you tried calling qRegisterMetaType?

    No, I have not. I tried

    qmlRegisterType(QAbstractListModel, "com.videoModel", 1, 0, "VideoModel")
    


  • @GrecKo Could you elaborate on how to use qRegisterMetaType? And on what you mean by

    root context properties should not be used anymore, access lookup is heavy for them.


Log in to reply