Splitting qml file into several files

Following https://stackoverflow.com/questions/45652525/qml-filling-menu-with-model-items I made a menu that behaves similarly to standard Makie menu. To make my project more manageable I am trying to split my files into smaller files. Following Breaking up QML into different files. [solved] | Qt Forum I put my menu into a seperate qml file and imported the directory. While I am able to import the separate qml file into my main file and use the component, the imported menu does not behave as the one written out in the main.qml file - more precisely the new menu is empty, in the sense that it has no menu items. The text on the button does appear correctly though.

Any thoughts?

File structure:

β”œβ”€β”€ main.jl
β”œβ”€β”€ main.qml
└── qml_components
    └── SensorMenu.qml

Main julia file, main.jl:

#main.jl
using QML, Observables
mutable struct menu_item
    text::String
end

items = ["x", "y", "z"]
items = [menu_item(item) for item in items]
item_model = JuliaItemModel(items)

current_item = Observable("x")

loadqml(
    "main.qml",
    observables=JuliaPropertyMap(
        "current_item" =>  current_item
    ),
    item_model = item_model
)

exec()

main qml file, main.qml:

//main.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import jlqml
import "qml_components" as Comp

ApplicationWindow {
    id: root
    title: "menus"
    visible: true
    width: 640
    height: 488
    onClosing: Qt.quit()

    RowLayout {
        // menu which works as expected, when called in the main qml file
        Button {
            id: fileButton
            text: observables.current_item
            onClicked: menu.open()

            Menu {
                id: menu
                y: fileButton.height
                visible: true
                Instantiator {
                    id: item_instatiator
                    model: item_model
                    delegate: MenuItem {
                        text: model.text
                        onTriggered: {
                            observables.current_item = model.text;
                        }
                    }

                    onObjectAdded: menu.insertItem(index, object)
                    onObjectRemoved: menu.removeItem(object)
                }
            }
        }

        // same menu but imported from another qml-file
        // expected to work as the first one, though is empty
        Comp.SensorMenu {
            text: observables.current_item
            lm: item_model
        }
    }
}

extra qml for holding the custom menu, SensorMenu.qml:

//SensorMenu.qml
import QtQuick
import QtQuick.Controls
import jlqml

Button {
    id: root
    property ListModel lm

    onClicked: menu.open()

    Menu {
        id: menu
        y: parent.height
        visible: true
        Instantiator {
            id: menu_instantiator
            model: root.lm
            delegate: MenuItem {
                text: model.text
                onTriggered: {
                    root.text = model.text;
                }
            }

            onObjectAdded: menu.insertItem(index, object)
            onObjectRemoved: menu.removeItem(object)
        }
    }
}

1 Like

So the example ends up looking as shown in the screencapture below:

After inspecting the Qt docs QML Object Attributes | Qt Qml | Qt 6.10.2 i managed to solve part of the issue by using a property alias in the SensorMenu definition as seen below.

//SensorMenu
import QtQuick
import QtQuick.Controls

Button {
    id: root
    property alias model: menu_instantiator.model

    onClicked: menu.open()

    Menu {
        id: menu
        y: parent.height
        visible: true
        Instantiator {
            id: menu_instantiator
            //model: root.lm
            delegate: MenuItem {
                text: model.text
                onTriggered: {
                    root.text = model.text;
                }
            }

            onObjectAdded: menu.insertItem(index, object)
            onObjectRemoved: menu.removeItem(object)
        }
    }
}

Now since i have renamed the property to model instead of lm this has to be changed in the main.qml file as well.

Now, while the menu does populate as expected, the text update seems to disconnect the text from the observable. See the video below, where the button texts are synced while changing only the left button, but using the right button seems to disconnect the two.

Digging a bit more around the Qt docs, Signal and Handler Event System | Qt Qml | Qt 6.10.2, I did get the text update to update the observable as well using signals.

//SensorMenu
import QtQuick
import QtQuick.Controls

Button {
    id: root
    property string current_text
    property alias model: menu_instantiator.model

    signal changed(text: string)
    text: current_text
    onClicked: menu.open()

    Menu {
        id: menu
        y: parent.height
        visible: true
        Instantiator {
            id: menu_instantiator
            //model: root.lm
            delegate: MenuItem {
                text: model.text
                onTriggered: {
                    current_text = model.text;
                    root.changed(current_text);
                }
            }

            onObjectAdded: menu.insertItem(index, object)
            onObjectRemoved: menu.removeItem(object)
        }
    }
}

//main.qml
...
        Comp.SensorMenu {
            current_text: observables.current_item
            model: item_model

            onChanged: current_text => {
                observables.current_item = current_text;
            }
        }
 ...

If anyone is interested I’ve made a quick repository to host the final state of the code in this post.

2 Likes

Was there any problem in QML.jl that made this behave in an unexpected way, or was this a generic QML issue?

I don’t think I’m experienced enough with qml or QML.jl to correctly answer this question.

1 Like

Okay… My troubles are not over yet, as I am still struggling in how to split components, that act on components from julia, in to smaller files. Is it possible to also have the logic in the smaller files? ie. updating observables or juliaItemModels with logic in the smaller qml files instead of writing signals, that is handled in the main.qml file.

Perhaps, what I am asking. Could I implement the button above in a seperate qml file. Then import it and simply pass my observable to property, and have the button update the observable, without further logic in the main.qml file?

From my experience, what works well is to design the interaction with Julia so that the same QML application can run in mockup mode and with a Julia backend. I have found that a generic App.qml component, driven by a prototype model like Prototype.qml or, once the UI is fixed, by Bridge.qml, works best from a code organisation perspective.

Nevertheless, I have only made one QML application and am therefore not an expert on the matter, so take my ramblings with a grain of salt. Perhaps you may develop a better approach.

2 Likes