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?