How to Use Models with Qml

How to Use Models with QML?

To even begin to address your issue, we'd need to see what the unitGenerator method does. If you're using a custom model, it's almost certain that you're not correctly implementing the notifications. My bet at the moment would be that you're not signaling the model reset.

Below is a complete code example that shows how you can tie a QStringListModel to an editable ListView and to ComboBoxes. The second ComboBox's model is regenerated based on the selection from the first one. This presumably approximates your desired functionality.

Note the specific handling of roles done by the QStringListModel. The model treats the display and edit roles almost the same: they both are mapped to the string value in the list. Yet when you update a particular role's data, the dataChanged signal carries only the role that you've changed. This can be used to break a binding loop that might be otherwise present in the model editor item (TextInput). When you use a custom model, you may need to implement similar functionality.

The display role is used to bind the combo boxes to the model. The edit role is used to pre-populate the editor objects. The editor's onTextChanged signal handler is updating the display role, and this doesn't cause a binding loop to itself. If the handler was updating the edit role, it would cause a binding loop via the text property.

On Models in QML

There are various kinds of "models" in QML. Internally, QML will wrap almost "anything" in a model. Anything that is internally not a QObject yet can still be a model (say a QVariant), won't be notifying anyone about anything.

For example, a "model" based on QVariant that wraps an int will not issue notifications, because QVariant is not a QObject that could signal changes.

Similarly, if your "model" is tied to a property value of a class derived from QObject, but you fail to emit the property change notification signal, it also won't work.

Without knowing what your model types are, it's impossible to tell.

main.qml

import QtQuick 2.0
import QtQuick.Controls 1.0

ApplicationWindow {
width: 300; height: 300
ListView {
id: view
width: parent.width
anchors.top: parent.top
anchors.bottom: column.top
model: model1
spacing: 2
delegate: Component {
Rectangle {
width: view.width
implicitHeight: edit.implicitHeight + 10
color: "transparent"
border.color: "red"
border.width: 2
radius: 5
TextInput {
id: edit
anchors.margins: 1.5 * parent.border.width
anchors.fill: parent
text: edit // "edit" role of the model, to break the binding loop
onTextChanged: model.display = text
}
}
}
}
Column {
id: column;
anchors.bottom: parent.bottom
Text { text: "Type"; }
ComboBox {
id: box1
model: model1
textRole: "display"
onCurrentTextChanged: generator.generate(currentText)
}
Text { text: "Unit"; }
ComboBox {
id: box2
model: model2
textRole: "display"
}
}
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QStringListModel>
#include <QQmlContext>

class Generator : public QObject
{
Q_OBJECT
QStringListModel * m_model;
public:
Generator(QStringListModel * model) : m_model(model) {}
Q_INVOKABLE void generate(const QVariant & val) {
QStringList list;
for (int i = 1; i <= 3; ++i) {
list << QString("%1:%2").arg(val.toString()).arg(i);
}
m_model->setStringList(list);
}
};

int main(int argc, char *argv[])
{
QStringListModel model1, model2;
Generator generator(&model2);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;

QStringList list;
list << "one" << "two" << "three" << "four";
model1.setStringList(list);

engine.rootContext()->setContextProperty("model1", &model1);
engine.rootContext()->setContextProperty("model2", &model2);
engine.rootContext()->setContextProperty("generator", &generator);

engine.load(QUrl("qrc:/main.qml"));
QObject *topLevel = engine.rootObjects().value(0);
QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
window->show();
return app.exec();
}

#include "main.moc"

QT qml model within a model? and accesible via qml

Yes its fine.

You need to return a pointer to your sub model object wrapped in a variant,

QVariant::fromValue(&subModel) 

You probably also need to register your model pointer with Metatype system using

qRegisterMetaType<MySubModelClass*>("MySubModelClass*" );

How to change QML-model to C++ model?

First, it should be noted that I know about I need to make a class, who inherit QAbstractListModel and implement some methods in that class is false, it is not necessary to create a new class that inherits from QAbstractListModel, for example the same can be implemented based on QStandardItemModel:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QStandardItemModel>

enum CustomRoles{
NameRole = Qt::UserRole + 1000
};

int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

QGuiApplication app(argc, argv);

QStandardItemModel model;
model.setItemRoleNames({{CustomRoles::NameRole, "name"}});

for(const QString & name: {"Arthur Morgan", "Dutch van der Linde", "John Marston"}){
QStandardItem *item = new QStandardItem;
item->setData(name, CustomRoles::NameRole);
model.appendRow(item);
}

QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("lstmdl", &model);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);

return app.exec();
}
import QtQuick 2.12
import QtQuick.Window 2.12

Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle{
width: parent.width / 3
height: parent.height
ListView{
anchors.fill: parent
model: lstmdl
spacing: 9
delegate: Text {
text: name
}
}
}
}

Note: On the other hand, Qt provides documentation and examples on how to use C++ models in QML:

  • Using C++ Models with Qt Quick Views

  • Models and Views: AbstractItemModel Example, etc



  1. why these methods should be implemented specifically?

    Because like any abstract class: It has methods that only define the behavior but do not implement it.

  2. How do I know the methods that have to be implemented?

    That is clearly indicated by the QAbstractListModel docs, so I will not repeat it but will point out some clarifications. If you only want a reading model you only need to implement the data, rowCount() and roleNames() methods. If you want to be editable you can implement the setData() method. For more detail read the class documentation.

  3. What does each of these methods do?

    Same as above: Each method has indicates its usefulness and logic in the documentation.

How to display model in QML with pages?

ListView has a method called positionViewAtIndex() that will allow you to specify which item to scroll to. So for your case, you could do something like this:

Button {
id: previousBtn
onClicked: {
listView.positionViewAtIndex(listView.currentIndex - 5);
}
}
Button {
id: nextBtn
onClicked: {
listView.positionViewAtIndex(listView.currentIndex + 5);
}
}

Integration of Exposed model into ListModel QML

You should set the property on the rootContext:

int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
AnimalModel model;
model.addAnimal(Animal(1, 2,3));
model.addAnimal(Animal(2,4,5));
model.addAnimal(Animal(3,4,6));

QQuickView viewer;
viewer.setResizeMode(QQuickView::SizeRootObjectToView);
viewer.rootContext()->setContextProperty("model", &model);
viewer.setSource(QUrl("qrc:/qml/qml/qmlsurface/main.qml"));
viewer.setTitle(QStringLiteral("Egyptolict "));
viewer.show();
return app.exec();

}

The setInitialProperties function tries to set properties on the object (in this case you 'main.qml' file), had you added property var model to 'main.qml' it could have worked.

PS, if you don't want to mix up property 'height', you could use 'altitude' ;-)

QT/QML Data Model

There are several options in this case such as:

  • Create a model based on QAbstractItemModel where you provide the properties through roles.

  • Create a QObject Device that has the desired properties as qproperties and expose it through a qproperty associated with a signal from another QObject, the QObject Device list and use that list as a model.

  • Create a model as a QAbstractListModel (or QStandardItemModel) and expose the QObject through a role.

  • Create a QObject that exposes a list of QObjects Device through ListProperty.

In this case I have chosen the first option for a demo:

main.py

from dataclasses import dataclass
import sys
from typing import Callable

from PySide2.QtCore import (
Property,
QCoreApplication,
QObject,
QVariantAnimation,
Qt,
QUrl,
)
from PySide2.QtGui import QGuiApplication, QStandardItem, QStandardItemModel
from PySide2.QtQml import QQmlApplicationEngine

@dataclass
class item_property:
role: int
function: Callable = None

def __call__(self, function):
self.function = function
return self

class item_property_impl(property):
def __init__(self, role, function):
super().__init__()
self._role = role
self._function = function

def __get__(self, obj, type=None):
if obj is None:
return self
if hasattr(obj, "_initial"):
obj.setData(self._function(obj), self._role)
delattr(obj, "_initial")
return obj.data(self._role)

def __set__(self, obj, value):
obj.setData(value, self._role)

class ItemMeta(type(QStandardItem), type):
def __new__(cls, name, bases, attrs):
for key in attrs.keys():
attr = attrs[key]
if not isinstance(attr, item_property):
continue
new_prop = item_property_impl(attr.role, attr.function)
attrs[key] = new_prop
if not hasattr(cls, "attrs"):
cls._names = []
cls._names.append(key)

obj = super().__new__(cls, name, bases, attrs)
return obj

def __call__(cls, *args, **kw):
obj = super().__call__(*args, **kw)
obj._initial = True
for key in cls._names:
getattr(obj, key)
return obj

class Item(QStandardItem, metaclass=ItemMeta):
pass

keys = (b"name", b"description", b"icon", b"progress", b"source", b"details", b"log")
ROLES = (
NAME_ROLE,
DESCRIPTION_ROLE,
ICON_ROLE,
PROGRESS_ROLE,
SOURCE_ROLE,
DETAILS_ROLE,
LOG_ROLE,
) = [Qt.UserRole + i for i, _ in enumerate(keys)]

class Device(Item):
@item_property(role=NAME_ROLE)
def name(self):
return ""

@item_property(role=DESCRIPTION_ROLE)
def description(self):
return ""

@item_property(role=ICON_ROLE)
def icon(self):
return ""

@item_property(role=PROGRESS_ROLE)
def progress(self):
return 0

@item_property(role=SOURCE_ROLE)
def source(self):
return ""

@item_property(role=DETAILS_ROLE)
def details(self):
return dict()

@item_property(role=LOG_ROLE)
def log(self):
return list()

class DeviceManager(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QStandardItemModel()
self._model.setItemRoleNames(dict(zip(ROLES, keys)))

def get_model(self):
return self._model

model = Property(QObject, fget=get_model, constant=True)

def add_device(self, *, name, description, icon, progress, source, details, log):
dev = Device()
dev.name = name
dev.description = description
dev.icon = icon
dev.progress = progress
dev.source = source
dev.details = details
dev.log = log
self.model.appendRow(dev)
return dev

def main():
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()

manager = DeviceManager()
engine.rootContext().setContextProperty("device_manager", manager)

url = QUrl("main.qml")

def handle_object_created(obj, obj_url):
if obj is None and url == obj_url:
QCoreApplication.exit(-1)

engine.objectCreated.connect(handle_object_created, Qt.QueuedConnection)
engine.load(url)

processor = manager.add_device(
name="Processor",
description="Intel i7 6600k",
icon="/resources/images/chip.svg",
progress=10,
source="resources/qml/Processor.qml",
details={
"badge": "Intel® Core i5 processor",
"cache": "6144 KB",
"clock": "4200000",
},
log=[
"Starting Cpu Test",
"Detected Intel CPU",
"Performing intense calculations",
"Processing calculations still",
"Cleaning up",
"Test Passed",
],
)

memory = manager.add_device(
name="Memory",
description="Kingston 16GB DDR3",
icon="/resources/images/ram.svg",
progress=50,
source="resources/qml/Memory.qml",
details={
"device_locator_string": "ChannelB-DIMM1",
"device_set": 0,
"error_handle": 65534,
"extended_size": 0,
"form_factor": "Unknown",
},
log=[
"Starting Memory Test",
"Detected 2 x RAM modules",
"Performing intense calculations",
"Processing calculations still",
"Cleaning up",
"Test Failed",
],
)

def update_progress(value):
processor.progress = value

animation = QVariantAnimation(
startValue=processor.progress, endValue=100, duration=3 * 1000
)
animation.valueChanged.connect(update_progress)
animation.start()

ret = app.exec_()
sys.exit(ret)

if __name__ == "__main__":
main()

main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
id: root

visible: true
width: 400
height: 400

ListView {
id: view

property url currentSource: ""

model: device_manager.model
width: parent.width / 2
height: parent.height
spacing: 10
clip: true
flickableDirection: Flickable.VerticalFlick
boundsBehavior: Flickable.StopAtBounds
currentIndex: -1

ScrollBar.vertical: ScrollBar {
}

highlight: Rectangle {
color: "lightsteelblue"
radius: 5
}

delegate: Rectangle {
id: rect

color: "transparent"
border.color: ListView.isCurrentItem ? "red" : "green"
height: column.height
width: ListView.view.width

Column {
id: column

Text {
text: model.name
}

ProgressBar {
from: 0
to: 100
value: model.progress
}

Label {
text: "Log:"
font.bold: true
font.pointSize: 15
}

Text {
text: model.log.join("\n")
}

}

MouseArea {
anchors.fill: parent
onClicked: {
rect.ListView.view.currentIndex = index;
rect.ListView.view.currentSource = model.source;
}
}

}

}

Rectangle {
x: view.width
width: parent.width / 2
height: parent.height
color: "salmon"

Loader {
anchors.centerIn: parent
source: view.currentSource
}

}

}

Processor.qml

import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle{
color: "red"
width: 100
height: 40
Text{
text: "Processor"
anchors.centerIn: parent
}
}

Memory.qml

import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle{
color: "blue"
width: 100
height: 40
Text{
text: "Memory"
anchors.centerIn: parent
}
}
├── main.py
├── main.qml
└── resources
└── qml
├── Memory.qml
└── Processor.qml

Sample Image



Related Topics



Leave a reply



Submit