Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I access the model's data in QtQuick (for icons in a ComboBox)

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:.

Example application showing comboBoxes with different types of models

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

like image 270
feedc0de Avatar asked Dec 06 '25 15:12

feedc0de


1 Answers

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:

  • We use as-casts, optional chaining, and nullish coalescing to make the binding shorter.
  • We define the 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.
  • This won't work with every model type. Specifically, I'm not sure how you'd make this work with models like integers in a generic way.
  • It does support arrays, but your 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
like image 170
Mitch Avatar answered Dec 09 '25 15:12

Mitch



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!