"Dynamically" add QML menu items based on C++?
-
I’d like my QML to display a group of menu items, where the number of items and the name of each item is specified in accompanying C++ code. Currently my QML displays a statically-defined set of items, like this:
Menu { title: "Color table" Action {checkable: true; checked: true; text: qsTr("Haxby"); Action {checkable: true; text: qsTr("BrightRainbow"); Action {checkable: true; text: qsTr("MutedRainbow"); Action {checkable: true; text: qsTr("Grayscale");
Is it possible for the QML to retrieve the item names and number from the C++?
And can the QML “dynamically” add each item to the menu when it doesn’t a priori know how many items there are? From my reading it seems it would look something like this, assuming my C++ object has been registered as a singleton:Menu { title: "TEST" Component.onCompleted: { for (var i = 0; i < myObject.nItems; i++) { howToInsertItem named myObject.name[i] into menu??? } }
Thanks!
-
The documentation has a section on dynamic content, using Menu.addAction() or one of a set of similar functions.
https://doc.qt.io/qt-6/qml-qtquick-controls-menu.html#dynamically-generating-menu-items
-
@jeremy_k Thanks- looking at the link you provided, but I cannot find any examples of how Menu.addAction() (and other methods) are actually used in QML code. Do you know of an example? In my case, menu item names are stored in string array cmaps, and my QML looks like:
Menu { title: "TEST" id: testMenu Component.onCompleted: { // Insert menu items with names // from cmaps array for (var i = 0; i < cmaps.length; i++) { console.log("map: ", cmaps[i]) testMenu.addItem({"text":cmaps[i]}); } }
I can see the loop being executed, but items do not appear in the menu - do you know what I am missing? Thanks!
-
I solved this with the following implementation, using Qt.createQmlObject() to create an Action and add it to the menu with Menu.addAction():
Menu { title: "TEST" id: testMenu // Create and add Actions to menu Component.onCompleted: { // Insert menu items here, with number of items // and item names as specified in the cmaps[] array that was // retrieved from C++ for (var i = 0; i < cmaps.length; i++) { // Build QML string that specifies menu Action to add var qmlStr = "import QtQuick.Controls 2.3; Action {id: myAction; text: \"" + cmaps[i] + "\"}"; // Create the menu Action var obj = Qt.createQmlObject(qmlStr, testMenu, "dynamicAction"); // Add created Action to the menu testMenu.addAction(obj); } }
Thanks to @jeremy_k for the suggestion!
-
@Tom-asso said in "Dynamically" add QML menu items based on C++?:
@jeremy_k Thanks- looking at the link you provided, but I cannot find any examples of how Menu.addAction() (and other methods) are actually used in QML code. Do you know of an example? In my case, menu item names are stored in string array cmaps, and my QML looks like:
Menu { title: "TEST" id: testMenu Component.onCompleted: { // Insert menu items with names // from cmaps array for (var i = 0; i < cmaps.length; i++) { console.log("map: ", cmaps[i]) testMenu.addItem({"text":cmaps[i]}); } }
I can see the loop being executed, but items do not appear in the menu - do you know what I am missing? Thanks!
I see that you solved the problem in the following post. Perhaps you figured out the issue in the version above. If not:
The reason it fails is that addItem() takes a Quick Item, but the code is passing a javascript dictionary object.
-
@Tom-asso I know you have already got a solution, but I wonder if the approach suggested in this link might work for you. The idea is to define your dynamic menu entries by means of a model, and instantiate the items using a repeater. In your case you could expose the model from C++.
From the link:
Menu { id:contextMenu visible: true Repeater { model: menuItems MenuItem { text: modelData } } }
The example instantiates
MenuItem
though, rather thanAction
, andRepeater
is documented as only working withItem
-based elements, whichAction
isn't. I don't know if there is a way around this, but if there were some way to get it to work, it would be a more "QML" way of doing it. -
@Bob64 - Thanks. In my application, the qml doesn't "know" the list of menu item names, which is provided by C++. But the C++ does not change that name list while the application is running, so it seems a bit more complex to use model rather than just a string array of names.
-
@Tom-asso said in "Dynamically" add QML menu items based on C++?:
@Bob64 - Thanks. In my application, the qml doesn't "know" the list of menu item names, which is provided by C++. But the C++ does not change that name list while the application is running, so it seems a bit more complex to use model rather than just a string array of names.
A list is a valid model for Quick items that take a model.
import QtQuick import QtQuick.Controls import QtQml.Models Window { width: 100 height: 50 visible: true Menu { id: menu Instantiator { model: ["first", "second"] delegate: MenuItem { text: modelData } onObjectAdded: (index, object) => menu.insertItem(index, object) } Component.onCompleted: popup() } }
-
An Instantiator is not needed, Menu can handle dynamically added MenuItem by a Repeater.
Use the code provided by bob64.
And like jeremy_k said you don't have to pass it a full fledged QAbstractItemModel, a string list is fine. -
@GrecKo said in "Dynamically" add QML menu items based on C++?:
An Instantiator is not needed, Menu can handle dynamically added MenuItem by a Repeater.
Except that as @Bob64 points out, Repeater cannot handle non-Item delegates. It will work for MenuItem, but not Menu or Action.
-
@jeremy_k not working with Action is not really an issue since you are in charge of chosing the delegate type, so you can create a MenuItem instead (that's what the Menu does anyway when adding an Action).
Instantiator is indeed needed for nested Menus.