I am trying to extend the QtQuick's ComboBox with icons, and I struggle to access the model's data for the currently selected item. I need to have a textRole, a valueRole and my new iconSourceRole (which defines a qrc: url to a .png file).
Using ComboBox's delegate I can access all the needed data from the model for each row by using model[comboBox.textRole] and model[comboBox.iconSourceRole], but this delegate is not used to render the currently selected item.
contentItem handles this and I cannot find any way to access all the roles from the model from inside there. comboBox.displayText is used for the display text.
My icon ComboBox will be used with different types of models, and they all seem to have a completely different api to get the data. For example Qml's ListModel has a .get() method, which only takes one parameter, Qml's FolderListModel has a .get() method, which needs 2 arguments. And I cannot find any way to access the data from a C++ QAbstractListModel class. Neither .roleNames() nor .itemData() were marked as Q_INVOKABLE, and if I want to use .data(), it seems like I need to know the numeric value of the role instead of the name.
This example code almost works for all cases but not with C++ models and the currently selected item. I am looking for a way to access model from inside contentItem:.

Main.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Layouts
import QtQuick.Window
import Qt.labs.folderlistmodel 2.4
import comboboxtests 1.0
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
GridLayout {
anchors.fill: parent
columns: 2
// IconComboBox shall support qml ListModels
IconComboBox {
Layout.preferredHeight: 64
id: listModelComboBox
textRole: 'theText'
valueRole: 'theValue'
iconSourceRole: 'theIconUrl'
model: ListModel {
ListElement { theText: 'text0'; theValue: 'value0'; theIconUrl: 'qrc:/comboboxtests/icons/movinghead.png' }
ListElement { theText: 'text1'; theValue: 'value1'; theIconUrl: 'qrc:/comboboxtests/icons/movinghead.png' }
ListElement { theText: 'text2'; theValue: 'value2'; theIconUrl: 'qrc:/comboboxtests/icons/nebelmaschine.png' }
ListElement { theText: 'text3'; theValue: 'value3'; theIconUrl: 'qrc:/comboboxtests/icons/nebelmaschine.png' }
ListElement { theText: 'text4'; theValue: 'value4'; theIconUrl: 'qrc:/comboboxtests/icons/rgbstrahler.png' }
ListElement { theText: 'text5'; theValue: 'value5'; theIconUrl: 'qrc:/comboboxtests/icons/rgbstrahler.png' }
}
}
Label {
text: qsTr('currentValue: ') + listModelComboBox.currentValue
}
// IconComboBox shall support qml FolderListModels (to let the user select which icon to use)
IconComboBox {
Layout.preferredHeight: 64
id: folderListModelComboBox
textRole: "fileBaseName"
valueRole: "fileBaseName"
iconSourceRole: "fileUrl"
model: FolderListModel {
folder: "qrc:/comboboxtests/icons/"
showDirs: false
function getUrlForIcon(name) {
let myFolder = folder;
if (myFolder.length < 1 || myFolder.charAt(myFolder.length - 1) !== '/') {
myFolder = myFolder + '/';
}
return myFolder + name + ".png"
}
}
}
Label {
text: qsTr('currentValue: ') + folderListModelComboBox.currentValue
}
// IconComboBox shall support C++ QAbstractListModels (access to our internal database)
IconComboBox {
Layout.preferredHeight: 64
id: cppModelComboBox
textRole: 'theText'
valueRole: 'theValue'
iconSourceRole: 'theIconUrl'
model: CppDefinedModel {
}
}
Label {
text: qsTr('currentValue: ') + cppModelComboBox.currentValue
}
Item {
Layout.fillHeight: true
}
}
}
IcomComboBox.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material
import Qt.labs.folderlistmodel 2.4
import comboboxtests 1.0
ComboBox {
id: comboBox
property string iconSourceRole
delegate: ItemDelegate {
height: 64
anchors.left: parent.left
anchors.right: parent.right
contentItem: IconChooserDelegateLayout {
anchors.top: parent.top
anchors.bottom: parent.bottom
text: model[comboBox.textRole]
iconSource: model[comboBox.iconSourceRole]
}
}
contentItem: IconChooserDelegateLayout {
text: comboBox.displayText
isInsideMaterialComboBox: true
iconSource: {
// console.log("QAbstractListModel", model instanceof QAbstractListModel);
// console.log("QAbstractItemModel", model instanceof QAbstractItemModel);
// console.log("FolderListModel", model instanceof FolderListModel);
// console.log("DeviceTypesModel", model instanceof CppDefinedModel);
// console.log("QtObject", model instanceof QtObject);
if (comboBox.currentIndex < 0)
return '';
if (!comboBox.model)
return '';
if (!comboBox.iconSourceRole)
return '';
// FolderListModel has a different API
if (model instanceof FolderListModel)
return model.get(comboBox.currentIndex, iconSourceRole);
// ListModel has another different API
else if ('get' in model)
{
const data = model.get(comboBox.currentIndex);
console.log(data);
return data[iconSourceRole];
}
// and I dont know how to access C++ models from QML at all
else if ('roleNames' in model || 'data' in model)
{
if (!('roleNames' in model && 'data' in model))
throw 'roleNames or data not defined!';
const roleNames = model.roleNames();
console.log('roleNames', roleNames);
const index = model.index(comboBox.currentIndex, 0);
const data = model.data(index, 99);
console.log('data', data);
throw 'getting data from model using roleNames and data is not yet implemented.';
}
else
throw 'unknown model type: ' + typeof model;
}
}
}
cppdefinedmodel.h
#pragma once
#include <QAbstractListModel>
#include <qqml.h>
class CppDefinedModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
enum {
TextRole = Qt::UserRole,
ValueRole,
IconUrlRole
};
public:
using QAbstractListModel::QAbstractListModel;
int rowCount(const QModelIndex &parent) const override
{
return 6;
}
QVariant data(const QModelIndex &index, int role) const override
{
switch (role)
{
case TextRole: return QString("name%0").arg(index.row());
case ValueRole: return QString("value%0").arg(index.row());
case IconUrlRole: return QString("qrc:/comboboxtests/icons/%0.png")
.arg(std::array<const char *,3>{{"movinghead", "nebelmaschine", "rgbstrahler"}}[index.row() / 2 % 3]);
}
return {};
}
QHash<int, QByteArray> roleNames() const override
{
return {{TextRole, "theText"}, {ValueRole, "theValue"}, {IconUrlRole, "theIconUrl"}};
}
};
I think qml needs a unified interface to access the data models. Is the logic to handle all the different model types implemented in ListView and ComboBox's C++ implementation?
I have commited this test project to GitHub
I think qml needs a unified interface to access the data models.
It absolutely does - see https://bugreports.qt.io/browse/QTBUG-111176 for work that is currently being done on this.
Is the logic to handle all the different model types implemented in ListView and ComboBox's C++ implementation?
As mentioned in the link above, a complex binding is currently required to support the various model types that users expect to be able to use:
text: control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] : model[control.textRole]) : modelData
However, in this case there's the added problem that you're not inside a delegate. There's no convenient way of accessing data from the model in this situation:
https://bugreports.qt.io/browse/QTBUG-99115
Until then, you can achieve what you're after with a less complex iconSource binding than your current one:
iconSource: comboBox.currentIndex === -1
? ""
: Array.isArray(comboBox.model)
? comboBox.model[comboBox.currentIndex][comboBox.iconSourceRole]
: (comboBox.model as ListModel)?.get(comboBox.currentIndex)[comboBox.iconSourceRole]
?? qrcPathOrUndefined((comboBox.model as FolderListModel)?.get(comboBox.currentIndex, "filePath"))
?? comboBox.model.data(comboBox.model.index(comboBox.currentIndex, 0), CppDefinedModel.IconUrlRole)
function qrcPathOrUndefined(path) {
if (path === undefined)
return undefined
return path.startsWith(":/") ? "qrc" + path : undefined
}
A few things to note:
qrcPathOrUndefined function to allow the use of nullish coalescing and also to fix the path that FolderListModel gives us. I'm not sure if it was ever designed to work with qrc paths, because it should return a path prefixed with "qrc:" for use with QML, rather than the C++-oriented ":" that it currently returns.delegate implementation would need to be modified to use the first binding mentioned (the one from QTBUG-111176).This also requires giving your model's role enum a name, making it public, and exposing it to QML:
public:
enum Roles {
TextRole = Qt::UserRole,
ValueRole,
IconUrlRole
};
Q_ENUM(Roles);
FolderListModel seems to also require this binding in ComboBox to ensure that the first item is displayed at startup:
currentIndex: model.status === FolderListModel.Ready ? 0 : -1
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With