Recently, I've been interested about how to create a simple general model with submodels and pass its data to .qml page. I got help and did it. But now I've got another problem. Now I can use roles, but I can't use signals or methods.
Here how my code looked like before
basemodel.h
class BaseModel : public QObject
{
Q_OBJECT
Q_PROPERTY(ExtraModel* extra READ extraModel CONSTANT)
public:
explicit BaseModel(QObject *parent = nullptr);
ExtraModel* extraModel() const { return extraModel_; }
private:
ExtraModel* extraModel_ = nullptr;
};
basemodel.cpp
BaseModel::BaseModel(QObject *parent)
: QObject(parent),
extraModel_(new ExtraModel(this))
{
}
And here how my .qml page was before I changed it to BaseModel
Rectangle {
signal selectionChanged(int value, string pageTitle, string itemName)
SilicaListView {
id: list
anchors.fill: parent
model: ExtraModel {
id: _extraModel
onSelectedChanged: {
selectionChanged(selected, name, itemName)
}
}
delegate: Rectangle {
MouseArea {
anchors.fill: parent
onClicked: _extraModel.activate(index)
}
}
}
}
And thats how I want it to be (or something like that)
Rectangle {
signal selectionChanged(int value, string pageTitle, string itemName)
BaseModel {
id: _baseModel
}
SilicaListView {
id: list
anchors.fill: parent
model: _baseModel.extra {
id: _extraModel
onSelectedChanged: {
selectionChanged(selected, name, itemName)
}
}
delegate: Rectangle {
MouseArea {
anchors.fill: parent
onClicked: _extraModel.activate(index)
}
}
}
}
But _baseModel.extra doesn't work as component so I asked how to use signals from _baseModel.extra and got the answer: Connections object. So, I searched and found what connections object is. So, I've tried to use it, but I only figured out I can't access my signal from ExtraModel or probably doing something wrong.
That's how I tried to use connection object
SilicaListView {
id: _list
anchors.fill: parent
model: _baseModel.extra
Connections {
id: _extraModel
target: _baseModel.extra
onSelectedChanged: {
selectionChanged(selected, name, itemName)
}
}
...
}
So, the question is how to access my signals and methods from ExtraModel using BaseModel?
To connect to signals using a Connections object, your code should look like this:
model: _baseModel.extra
Connections {
target: _baseModel.extra
onSelectedChanged: {
selectionChanged(selected, name, itemName)
}
}
EDIT:
To call invokable functions, you should just be able to do this:
MouseArea {
anchors.fill: parent
onClicked: _baseModel.extra.activate(index)
}
Related
Background
I have a tree-like QStandardItemModel, whose items I would like to access from QML.
Here is how the model is defined on the C++ side:
backend.h
class Backend : public QObject
{
Q_OBJECT
Q_PROPERTY(QStandardItemModel *model READ model CONSTANT)
public:
explicit Backend(QObject *parent = nullptr);
QStandardItemModel *model() const;
private:
QStandardItemModel *m_model;
};
backend.cpp
Backend::Backend(QObject *parent) :
QObject(parent),
m_model(new QStandardItemModel(this))
{
auto *itemFirst = new QStandardItem(tr("First"));
auto *itemSecond = new QStandardItem(tr("Second"));
auto *subItem = new QStandardItem(tr("First_02"));
subItem->appendRow(new QStandardItem("First_02_01"));
itemFirst->appendRow(new QStandardItem(tr("First_01")));
itemFirst->appendRow(subItem);
itemFirst->appendRow(new QStandardItem(tr("First_03")));
itemSecond->appendRow(new QStandardItem(tr("Second_00")));
itemSecond->appendRow(new QStandardItem(tr("Second_01")));
m_model->appendRow(itemFirst);
m_model->appendRow(itemSecond);
}
QStandardItemModel *Backend::model() const
{
return m_model;
}
The model is exported to QML in main.cpp like this:
Backend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("backend", &backend);
qmlRegisterUncreatableType<QStandardItemModel>("QStandardItemModel", 1, 0, "QStandardItemModel", "The model should be created in C++");
Using TreeView from QtQuick.Controls 1.4 in main.qml like this:
TreeView {
anchors.fill: parent
model: backend.model
TableViewColumn {
title: "Name"
role: "display"
}
}
I get the desired results, i.e. all items nested correctly:
Problem
When I try to manually iterate over the nested items using Repeater and DelegateModel like this:
ColumnLayout {
anchors.fill: parent
Repeater {
model: backend.model
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
Text {
color: "blue"
text: model.display
}
Repeater {
model: DelegateModel {
model: backend.model
rootIndex: modelIndex(index)
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
Text {
color: "green"
text: model.display
}
Repeater {
model: DelegateModel {
model: backend.model
rootIndex: modelIndex(index)
Text {
color: "red"
text: model.display
}
}
}
}
}
}
}
}
}
}
}
the main branches (marked with blue) and the items on the first nesting level (marked with green) are the right ones, but I get the wrong items on the second nesting level (marked with red):
How to fix the code to correctly iterate over the QStandardItemModel's items on each nesting level?
The problem is in these two lines: rootIndex: modelIndex(index).
index is the index of the 'parent' model, but modelIndex(...) is the method of the current model.
I've tried it with this (slightly modified) piece of code and it worked:
Repeater {
model: DelegateModel {
id: model1
model: backend.model
delegate: ColumnLayout{
Text {
text: "Data: " + display
}
Repeater {
model: DelegateModel {
id: model2
model: backend.model
// 'index' comes from 'model1', so use the 'modelIndex' method from 'model1'
rootIndex: model1.modelIndex(index)
delegate: ColumnLayout{
Text {
text: "- Data: " + display
}
Repeater {
model: DelegateModel {
id: model3
model: backend.model
// 'index' comes from 'model2', so use the 'modelIndex' method from 'model2'
rootIndex: model2.modelIndex(index)
delegate: Text {
text: "-- Data: " + display
}
}
}
}
}
}
}
}
}
I have a tree model derived from a QAbstractItemModel. And I can display the data in a tree like way.
What I want is to display teh data by the layers. To display only one level of a layer at a time AND put each layer on a stack and navigate backwards by poping the layer from the stack.
I guess I have to implement a custom delegate? Any advice would be highly appreciated. Thank you.
I recently implemented something similar, based on a QFileSystemModel, set as a qml contextProperty, named treeModel in the example below.
The idea was to keep track of the current QModelIndex, and to use the data() & rowCount() functions of the QAbstractItemModel to get the actual model data, and to use a recursive stack view for the navigation
General layout
ApplicationWindow {
id: main
visible: true
width: 640
height: 480
ColumnLayout
{
anchors.fill: parent
// Breadcrumb
SEE BELOW
// View
StackView
{
id: stackView
Layout.fillHeight: true
Layout.fillWidth: true
initialItem: TreeSlide {}
}
}
}
TreeSlide
The view itself is pretty simple. I didn't used anything fancy here, and it displays only one role, but you could extend it without trouble. Note that the view's model is NOT your treeModel, but instead just the rowCount for the rootIndex.
ListView
{
Layout.fillHeight: true
Layout.fillWidth: true
model: treeModel.rowCount(rootIndex)
clip: true
snapMode: ListView.SnapToItem
property var rootIndex
// I used a QFileSytemModel in my example, so I had to manually
// fetch data when the rootIndex changed. You may not need this though.
onRootIndexChanged: {
if(treeModel.canFetchMore(rootIndex))
treeModel.fetchMore(rootIndex)
}
Connections {
target: treeModel
onRowsInserted: {
rootIndexChanged()
}
}
delegate: ItemDelegate {
property var modelIndex: treeModel.index(index,0, rootIndex)
property bool hasChildren: treeModel.hasChildren(modelIndex)
width: parent.width
text: treeModel.data(modelIndex)
onClicked: {
if(hasChildren)
{
// Recursively add another TreeSlide, with a new rootIndex
stackView.push("TreeSlide.qml", {rootIndex: modelIndex})
}
}
}
}
Breadcrumb
To navigate the model, instead of a simple back button, I used a kind of dynamic breadcrumb
// Breadcrumb
RowLayout
{
Repeater
{
id: repeat
model: {
var res = []
var temp = stackView.currentItem.rootIndex
while(treeModel.data(temp) != undefined)
{
res.unshift(treeModel.data(temp))
temp = temp.parent
}
res.unshift('.')
return res
}
ItemDelegate
{
text : modelData
onClicked: {
goUp(repeat.count - index-1)
}
}
}
}
the goUp function simply goes up the stack by poping items
function goUp(n)
{
for(var i=0; i<n; i++)
stackView.pop()
}
To to do it completely by guides we should use DelegateModel and DelegateModel.rootIndex
DelegateModel {
id: delegateSupportPropConfigModel
model: supportModel
delegate: SupportPropConfigListItem {
id: currentItem
width: scrollRect2.width - 60
fieldName: model.fieldName
fieldValue: model.value
onClick:{
delegateSupportPropConfigModel.rootIndex = supportPropConfigModel.index(0, 0, supportPropConfigModel)
}
}
}
Column {
id: columnSettings
spacing: 2
Repeater {
model: delegateSupportPropConfigModel
}
}
If I have a WebEngineView in a ListView delegate, how can I call loadHtml when the delegate is loaded? For example:
ListView
{
model: myModel
delegate: Component
{
Item:
{
WebEngineView
{
id: myWebView
text: myWebView.loadHtml(model.modelData.htmlText)
}
}
}
}
The above shows the idea of what I would like to do. Is there a signal I could hook into for each delegate Item where I could call myWebView.loadHtml()?
I do not know about a text-property of the WebEngineView but I have never used it sofar.
I think, what you want is the Component.onCompleted-handler like this:
ListView {
model: myModel
delegate: Component {
Item {
WebEngineView {
id: myWebView
Component.onCompleted: loadHtml(model.modelData.htmlText, baseURL)
}
}
}
}
I don't know about your usecase, but the loadHtml-method has a second argument for the baseURL to look for ressources such as CSS or Images, which might be needed by you.
In QML, I have a custom object type (a separate QML file) and I would like a way of accessing and/or modifying each instance of this type. For a very simple example:
MyText.qml:
Text {
height: 100
width: 100
color: "red"
function logStuff() {
console.log("This is MyText")
}
}
SomePage.qml:
MyText {
id: text1
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: "foo"
}
MyText {
id: text2
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: "bar"
}
When an event occurs (e.g. a button on SomePage.qml being clicked or a signal being emitted), I would like to be able to change all instances of MyText to have the same value for one property, or call each MyText's logStuff() function.
Note: in my actual use case, there are actually several dozen of these instances, and there will be other Text elements which are not instances of MyText.
I have a little experience with jQuery and was hoping there would be something similar to jQuery selectors, but I have been unable to find anything remotely similar. Either a QML or a C++ (or mixed) solution would be fine.
I would like to suggest a method using C++.
Since there is no way to get base class of QML object you can base the searching on objectName property witch is accessible in C++.
Suppose we have base QML object:
import QtQuick 2.3
Item {
id: base
objectName: "BaseItem"
property int someValue: 0
onSomeValueChanged: {
console.log("'someValue' was changed to " + someValue + " for " + base);
}
}
And another QML file, where these objects are used:
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
visible: true
width: 300
height: 300
id: mainWindow
Base {
id: derived1
}
Base {
id: derived2
}
Base {
id: derived3
}
}
The idea is that all derived object inherit the same objectName from the base class.
So now you can simply find all of the objects in C++:
QObject *root = engine.rootObjects().first();
QList<QObject *> list = root->findChildren<QObject *>("BaseItem");
qsrand(QTime::currentTime().msec());
foreach(QObject *item,list) {
item->setProperty("someValue",qrand());
}
Here I just change the property but you also can call some method with QMetaObject::invokeMethod() etc.
I'm not sure to understand your need but I would just use a Connections element like so :
SomePage.qml:
MyText {
id: text1
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: "foo"
Connections {
target: senderItemId
onFooChanged: logStuff()
}
}
MyText {
id: text2
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: "bar"
Connections {
target: senderItemId
onFooChanged: logStuff()
}
}
There's no API call in Qt to do this (that I could find), but you can find all the QML QObject's by a type using QObject::children() and QMetaObject in C++. Below is an implementation example.
QList<QObject *> MySingleton::findObjectsOfComponent(QObject *parent, QQmlComponent *component) const
{
auto object = component->create(QQmlEngine::contextForObject(parent));
auto foundObjects = findObjectsOfObject(parent, object);
delete object;
return foundObjects;
}
QList<QObject *> MySingleton::findObjectsOfObject(QObject *parent, const QObject *object) const
{
auto className = object->metaObject()->className();
QList<QObject*> items;
for(auto child : parent->children()) {
if(child->metaObject()->className() == className) {
items.append(child);
}
items.append(findObjectsOfObject(child, object));
}
return items;
}
Then you should register MySingleton as a QML singleton and use a Component to find the type.
Component {
id: errorLabelComponentId
ErrorLabel {}
}
Button {
onClicked: {
//Un-ignore all error labels
var errorLabels = MySingleton.findObjectsOfComponent(pageId, errorLabelComponentId)
for(var i in errorLabels) {
//ignoreError is a property of ErrorLabel {}
errorLabels[i].ignoreError = false
}
}
}
Can I create my own attached for everything property like a Component?
Item{
Component.onCompleted : {} // Component is attached to everyone Items
}
Yes, for example:
#include <QQmlEngine>
#include <QTimer>
class TestAttached : public QObject
{
Q_OBJECT
// Declare property with setter and getter
Q_PROPERTY(int val READ getVal WRITE setVal)
public:
TestAttached() Q_DECL_EQ_DEFAULT;
explicit TestAttached(QObject *parent):
QObject{parent},
m_val{100500}
{
Q_ASSERT(parent);
qDebug() << "* We just have created the object of attached properties for" << parent->metaObject()->className();
}
~TestAttached() Q_DECL_EQ_DEFAULT;
// define static function qmlAttachedProperties(QObject *object)
// which returns pointer to instance of TestAttached class
static TestAttached *qmlAttachedProperties(QObject *object)
{
TestAttached* testAttached { new TestAttached{object} };
QTimer* timer { new QTimer{testAttached} };
connect(timer, &QTimer::timeout,
[testAttached] {
testAttached->setVal(testAttached->getVal()+1);
emit testAttached->testDone(testAttached->getVal());
});
timer->start(3000);
return testAttached;
}
inline int getVal() const
{
return m_val;
}
inline void setVal(int val)
{
m_val = val;
}
signals:
void testDone(int val);
private:
int m_val;
};
// Makes the type TestAttached known to QMetaType (for using with QVariant)
QML_DECLARE_TYPE(TestAttached)
// declares that the TestAttached supports attached properties.
QML_DECLARE_TYPEINFO(TestAttached, QML_HAS_ATTACHED_PROPERTIES)
Register attached type in QML system (e.g. at main.cpp):
qmlRegisterUncreatableType<TestAttached>("my.test", 1, 0, "Test", QObject::tr("Test is an abstract type that is only available as an attached property."));
And try it in main.qml:
import QtQuick 2.15
import QtQuick.Window 2.15
//import our module
import my.test 1.0
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World of Attached properties")
Rectangle {
color: focus ? Qt.lighter("red") : "red"
width: parent.width/2; height: parent.height
anchors.left: parent.left
Component.onCompleted: {
console.log("Rectangle is completed", Test.val)
}
Keys.onReturnPressed: {
console.log("Rect 1: Attachet value is", Test.val)
}
Test.val: 20000
Test.onTestDone: function(num) {
console.log("We received", num, "from attached object")
}
MouseArea {
anchors.fill: parent
onClicked: parent.focus = true
}
}
Rectangle {
color: focus ? Qt.lighter("blue") : "blue"
width: parent.width/2; height: parent.height
anchors.right: parent.right
focus: true
Keys.onReturnPressed: {
// The attached object will created after return pressed
console.log("Rect 2: Attachet value is", Test.val)
}
MouseArea {
anchors.fill: parent
onClicked: parent.focus = true
}
}
}
You may not be able to attach properties to Items or Components you did not create. But why would you want to do that anyway?
Instead you could consider using signals and global properties.
For global properties that you can access from anywhere you can set the context property of the root context of the declarative view.
i.e,
QmlApplicationViewer viewer;
MyPropertyClass myProp;
viewer->rootContext()->setContextProperty("MyPropClass", &myProp);
Now, in your QML file you can access the properties of this class as
Rectangle {
Text {
text: MyPropClass.getMyPropText()
}
MouseArea {
onClicked: { MyPropClass.text = "Clicked" }
}
}
This will invoke the Q_INVOKABLE method getMyPropText() from the MyPropertyClass. and Q_PROPERTY 'text' can be set when some signal is emitted.
Would this suit you needs?