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 ComboBox
es. 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
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.
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.
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
Related Topics
Dead Code Identification (C++)
How to Use C++ Std::Ostream with Printf-Like Formatting
C++ Linking Error After Upgrading to MAC Os X 10.9/Xcode 5.0.1
Can't Downcast Because Class Is Not Polymorphic
Is "Std::Cout" Usable in Android-Ndk
Why Can't I Store References in a 'Std::Map' in C++
C++ Linker Error with Class Static Constexpr
C++ Range/Xrange Equivalent in Stl or Boost
Default Libraries Linked in by Gcc
How to Prevent Non-Specialized Template Instantiation
How to Initialize Static Members in the Header
What's the Best Way to Check If a File Exists in C++? (Cross Platform)
Gsl::Not_Null<T*> VS. Std::Reference_Wrapper<T> VS. T&
Lambda Expression VS Functor in C++
What Does "Typedef Void (*Something)()" Mean