[SOLVED]Dynamic Binding of QML Object and Factory created C++ object



  • Hi all first post here. Been lurking a while though.

    I have successfully wrote and registered some cpp objects to QML env:

    Product
    and
    Products -> product 'factory' or 'repository' with Product * Products::get(QVariant id) method

    I've tested these, Product creation, saving, deletion works like a charm from QML side (imperative js)

    Now I'm progressing to building the UI.
    this is my 1st component (simplified) - master_item.qml
    @
    import QtQuick 2.0
    import QtQuick.Layouts 1.1
    import QtQuick.Controls 1.1

    Item {
    anchors.fill: parent
    property alias ref: ref.text
    property alias name: name.text

    GridLayout {
        id: gridLayout
        flow: GridLayout.TopToBottom
        rows:4
    
        Label { text: qsTr("REF")   }
        Label { text: qsTr("Name")  }
    
        TextField {
            id: ref
            placeholderText: qsTr("REF")
        }
        TextField {
            id: name
            placeholderText: qsTr("Name")
            Layout.fillWidth: true
        }
    }
    

    }
    @

    then on main.qml i do

    @
    import my.app.repositories 1.0

    ApplicationWindow {
    TabView {
    id: tabview
    }

    Button {
        text: "Add Tab"
        anchors.top: tabview.bottom
        onClicked: {
            var component = Qt.createComponent("master_item.qml");
            if (component.status == Component.Ready) {
                var tab = tabview.addTab("tab " + (tabview.count + 1), component);
                var item = products.get('CR2011W'));
                tab.item.ref = Qt.binding(function() { return item.ref; });
                tab.item.name = Qt.binding(function() { return item.name; });
            }
        }
    }
    

    @

    this worked for the first time I clicked the button(just the first tab), but subsequent calls yield Type Error.
    the component loaded, but the binding does not.
    What did I do wrong?

    My 2nd try involves embedding the Product object in the component directly:
    @
    import my.app.entities 1.0

    Item {
    anchors.fill: parent
    Product {
    id: data
    }

    GridLayout {
        id: gridLayout
        flow: GridLayout.TopToBottom
        rows:4
    
        Label { text: qsTr("REF")   }
        Label { text: qsTr("Name")  }
    
        TextField {
            id: ref
            placeholderText: qsTr("REF")
            text: data.ref
        }
        TextField {
            id: name
            placeholderText: qsTr("Name")
            Layout.fillWidth: true
            text: data.name
        }
    }
    

    }
    @

    so in main.qml I thought it's gonna be more intuitive:
    @
    onClicked: {
    var component = Qt.createComponent("master_item.qml");
    if (component.status == Component.Ready) {
    var tab = tabview.addTab("tab " + (tabview.count + 1), component);
    tab.item.data = products.get('CR2011W'));
    }
    }
    }
    @

    Now the bindings worked for subsequent calls, but tab.item.data = products.get('ID') doesn't seem to override the component's internal data property. so it's just a blank component with blank Product object.

    I've tried putting the component creation in separate js file, to no avail.

    What I need basically is bind a dynamically create QML component to an (also dynamically) Factory created cpp Object.

    How do I do this correctly?

    Thanks.

    Edit - Solution
    user-accesible property of the underlying cpp object's type in component's qml
    @
    import my.app 1.0

    Item {
    property Product obj
    obj: Product {}

    TextField {
        id: name
        text: obj.name
    }
    

    }
    @
    and in main.qml
    @
    import my.app 1.0

    ApplicationWindow {
    Products { id: products }
    TabView { id: tabview }

    Button {
        text: "Add Tab"
        anchors.top: tabview.bottom
        onClicked: {
            var component = Qt.createComponent("component.qml");
            if (component.status == Component.Ready) {
                var tab = tabview.addTab("tab " + (tabview.count + 1), component);
                var obj = products.get("ID HERE");
                if (tab.status == Loader.Ready) 
                    tab.item.obj = obj;
                else 
                    tab.loaded.connect(function() {
                        if (tab.status == Loader.Ready)
                            tab.item.obj = obj;
                    });
            }
        }
    }
    

    }
    @
    according to the docs, local components should not be lazy loaded so in this case the component status check might be alright if omitted.



  • I think the problem lies in Tabview.addTab() only accepting Component.
    if it accepts instantiated object instead, I could probably do something like:
    @
    var component = Qt.createComponent("master_item.qml");
    if (component.status == Component.Ready) {
    var obj = component.createObject(tabview, {"data": Products.get("CR2001W")});
    tabview.addTab("CR2001W", obj);
    }
    @

    but no, tabview is basically a Loader...

    Any solution to this?



  • I got my 2nd approach working as follows:
    @
    import my.app.entities 1.0

    Item {
        anchors.fill: parent
        property Product obj
        obj: Product {}
     
        GridLayout {
            flow: GridLayout.TopToBottom
            rows:4
     
            Label { text: qsTr("REF")   }
            Label { text: qsTr("Name")  }
     
            TextField {
                id: ref
                text: obj.ref
            }
            TextField {
                id: name
                text: obj.name
            }
        }
    }
    

    @
    and on main.qml
    @
    onClicked: {
    var component = Qt.createComponent("master_item.qml");
    if (component.status == Component.Ready) {
    var tab = tabview.addTab("tab " + (tabview.count + 1), component);
    tab.item.obj = products.get('CR2001W');
    }
    @

    but then I'm back to SQUARE ONE. first add it works, second adding of tab yields Type Error.

    WTH am I doing wrong??



  • After a closer inspection after the call:
    var tab = tabview.addTab(...)

    tab var will not be ready if the corresponding Tab isn't on focus, that is, the Tab is lazily loaded. Now if only I could assign a callback once its status is Loader.Ready...



  • GOT IT.
    this worked:
    @
    onClicked: {
    var component = Qt.createComponent("master_item.qml");
    if (component.status == Component.Ready) {
    var tab = tabview.addTab("tab " + (tabview.count + 1), component);
    if (tab.status == Loader.Ready) {
    tab.item.obj = products.get("CR2001W");
    } else {
    tab.loaded.connect(function() {
    console.log("this will be executed when corresponding tab is on focus");
    if (tab.status == Loader.Ready) {
    tab.item.obj = products.get("CR2001W");
    }
    });
    }
    }
    }
    @
    by extension, I should be putting callbacks on component.statusChanged as well... this gets ugly pretty fast. Refactor this on different JS file? how about my Products factory object? will it be available from js? Can I even put imports in plain js?


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.