Dynamically create QML ListElement and content - qt

So I am trying to dynamically create ListElements in a ListModel. This works fine until I try writing some content in the ListElements to be loaded dynamically.
I tried making an own file with the ListElement within and the hour as a property, but the model then I got an error saying that ListElements can not be nested.
The error for running the code below is:
Cannot assign to non-existent property "hour"
How can I solve this?
Code:
import QtQuick 2.0
ListModel
{
id: listModel
Component.onCompleted:
{
for (var i = 0; i < 24; i++)
{
var object = createListElement(listModel)
}
}
function createListElement(parent)
{
var object = Qt.createQmlObject('import QtQuick 2.0; ListElement { hour: "01" }', parent);
return object;
}
}
EDIT:
Change the code line in the function to:
var object = Qt.createQmlObject('import QtQuick 2.0; ListElement { property string hour: "23" }', parent);
Now I get no errors, but the elements are still not showing in the list.

I'm not sure why that doesn't work, but using plain old JavaScript objects does the job:
import QtQuick 2.4
import QtQuick.Window 2.0
Window {
width: 400
height: 400
ListView {
id: listView
anchors.fill: parent
model: listModel
delegate: Rectangle {
width: listView.width
height: listView.height / 4
Text {
text: hour
anchors.centerIn: parent
}
}
}
ListModel {
id: listModel
Component.onCompleted: {
for (var i = 0; i < 24; i++) {
append(createListElement());
}
}
function createListElement() {
return {
hour: "01"
};
}
}
}

Related

How to access data of QSqlQueryModel from a QML ListView delegate?

I want to access data by a query in SQL database and present its results as list of elements in QML. However, I'm not sure if I'm accessing this data in the right way. Or maybe QSqlQueryModel presents its data in unusual way? How can I check this?
Here's code from my QML file:
import QtQuick 2.0
import QtQuick.Controls 2.1
import QtQuick.Window 2.1
import QtQuick.Layouts 2.0
import QtQml.Models 2.3
import DbInterface 1.0
ApplicationWindow {
id: main
width: 400
height: 800
visible: true
Component {
id: appCentralListDelegate
Row {
Text {
text: signature
}
Text {
text: name
}
}
}
DbInterface { id: appCentralListModel }
DelegateModel {
id: delegateModel
property var isSearchResult: function(item) {
return true;
// not implemented yet
}
model: appCentralListModel
delegate: appCentralListDelegate
function update() {
console.log("Starting update");
if (items.count > 0) {
items.setGroups(0, items.count, "items");
}
// Step 1: Filter items
var visible = [];
for (var i = 0; i < items.count; ++i) {
var item = items.get(i);
if (isSearchResult(item.model)) {
visible.push(item);
}
}
// Step 3: Add all items to the visible group:
for (i = 0; i < visible.length; ++i) {
item = visible[i];
item.inVisible = true;
if (item.visibleIndex !== i) {
visibleItems.move(item.visibleIndex, i, 1);
}
}
}
items.onChanged: update()
onIsSearchResultChanged: update()
groups: DelegateModelGroup {
id: visibleItems
name: "visible"
includeByDefault: false
}
filterOnGroup: "visible"
}
ListView {
id: appCentralListView
anchors.fill: parent
model: delegateModel
spacing: 0
}
Row {
TextField {
id: searchField
}
}
}
And here is a file where DbInterface is presented:
from PySide6.QtSql import QSqlQueryModel
from PySide6.QtQml import QmlElement
QML_IMPORT_NAME = "DbInterface"
QML_IMPORT_MAJOR_VERSION = 1
#QmlElement
class DbInterface(QSqlQueryModel):
def __init__(self, parent=None):
super(DbInterface, self).__init__(parent)
self.setQuery(open("query.sql").read())
if self.lastError().isValid:
print(self.lastError())
else:
print("Query executed successfully.")

Dynamically bind values from qml to repeater created object

I control the position of some elements of my scene using alias properties likes this : If I have a file Foo.qml containing
Item {
property alias myprop1: id1
property alias myprop2: id2
Node {id:id1,...}
Node {id:id2,...}
On my main, I can then call
Slider{
id:myslider
}
foo{
myprop1.x: myslider.value
}
Now if my Foo.qml contains an unknow number of properties (lets say they are all called mypropX). If I have 10 properties I want to create 10 sliders, one for each property. It is possible with a repeater and loop like mentioned in last answer here
Foo{
id:myfoo
}
Column {
Repeater {
id: myrepeater
delegate: Slider {
from:0
to:400
y: 12*index
}
Component.onCompleted: {
let propArray = [];
for(var prop in myfoo){
//select only the properties I'm interested in
//a "onXXXChanged" is created on each properties so I also have to remove it
if(prop.substring(0, 6)==="myprop" && prop.substring(prop.length-7,prop.length)!=="Changed"){
propArray.push(prop)
}
}
myrepeater.model = propArray
}
}
}
The problem is now that I don't know how to bind those 10 sliders to my properties.
I tried adding to my Foo instance in main
Component.onCompleted: {
let i=0
for(var prop in myfoo){
if(prop.substring(0, 6)==="myprop" && prop.substring(prop.length-7,prop.length)!=="Changed"){
//equivalent to myprop1.x: myslider.value when there was no repeater
myfoo.prop.x = Qt.binding(function() {
return myrepeater.itemAt(i).value
})
i++
}
}
}
But it return
QQmlEngine::setContextForObject(): Object already has a QQmlContext
qrc:/main.qml:145: Error: Cannot assign to non-existent property "prop"
The problem is that in the for loop, prop is a string. I am also not sure that at the moment the onCompleted is executed, the repeater has already created all the slidders.
I could use the QML type Bindings{} which takes a target (myrepeater.itemAt(i).value) and the property name as a string, but I don't know how to call the Bindings{} type from javascript
You can use the [] operator to read the properties from myfoo and as discussed I would use a Binding object inside the delegate:
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Item {
id: myfoo
property int myprop_upper_threshold
onMyprop_upper_thresholdChanged: console.log("upper_threshold", myprop_upper_threshold)
property int myprop_lower_threshold
onMyprop_lower_thresholdChanged: console.log("lower_threshold", myprop_lower_threshold)
}
ColumnLayout {
Repeater {
id: myrepeater
delegate: Slider {
id: myslider
from: 0
to: 400
Text {
text: modelData
}
Binding {
target: myfoo
property: modelData
value: myslider.value
}
}
Component.onCompleted: {
let propArray = [];
for(var prop in myfoo)
{
//select only the properties I'm interested in
//a "onXXXChanged" is created on each properties so I also have to remove it
if(prop.substring(0, 6)==="myprop" && prop.substring(prop.length-7,prop.length)!=="Changed")
{
propArray.push(prop)
}
}
myrepeater.model = propArray
}
}
}
}

Qt.createComponent url of the library components

Below is a function from TimelinePresenter.qml which is a custom component I created.
function createMenu() {
var menuComp = Qt.createComponent("Menu.qml");
if( menuComp.status != Component.Ready )
{
if( menuComp.status == Component.Error )
console.debug("Error: " + menuComp.errorString());
return;
}
}
It gives the error:
Error: qrc:/qml/timeline/Menu.qml:-1 No such file or directory
TimelinePresenter.qml is a resource file specified in the .qrc file and its path is qml/timeline as shown in error message so qml engine is trying to find the Menu.qml there expectedly. How can I specify the path to create qt's Menu component?
Edit:
my resources.qrc file
<RCC>
<qresource prefix="/">
<file>qml/main_window.qml</file>
<file>qml/timeline/TimelineViewItem.qml</file>
<file>qml/timeline/HorizontalLine.qml</file>
<file>qml/timeline/TimelineView.qml</file>
<file>qml/timeline/VerticalLine.qml</file>
<file>qml/timeline/timeline-item/timeline_item.h</file>
<file>qml/timeline/TimelinePresenter.qml</file>
<file>qml/timeline/timeline-item/analog_timeline_item.h</file>
<file>qml/timeline/timeline-item/digital_timeline_item.h</file>
<file>qml/timeline/timeline_presenter_backend.h</file>
<file>qml/ControllableListPresenter.qml</file>
<file>qml/controllable_list_backend.h</file>
<file>qml/controllable-popup/AddControlUnitPopup.qml</file>
<file>qml/styled/CenteredPopup.qml</file>
<file>qml/styled/StyledTextField.qml</file>
</qresource>
</RCC>
You are confusing the creation of a component with the creation of an object that belongs to a component.
The Menu component already exists and is provided by Qt, what you must do is create the object using the Qt.createQmlObject() method.
Example:
var menuObj = Qt.createQmlObject('import QtQuick.Controls 2.0 ; Menu {
MenuItem { text: "Cut" }
MenuItem { text: "Copy" }
MenuItem { text: "Paste" } }', parentItem, "dynamicSnippet1");
Complete Example:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
id: parentItem
Component.onCompleted: {
var menu = Qt.createQmlObject('import QtQuick.Controls 2.0 ; Menu {
MenuItem { text: "Cut" }
MenuItem { text: "Copy" }
MenuItem { text: "Paste" }
}', parentItem,"dynamicSnippet1");
// test: open menu
menu.open()
}
}
In the case you have described in your comments, I would suggest to only create one Menu and only popup() it at the place where you have clicked, setting it in a specific context.
I prepared a small example to illustrate how the Menu could be used:
import QtQuick 2.7
import QtQuick.Window 2.0
import QtQuick.Controls 2.3 // Necessary for the "Action" I used. Create the Menu otherwise if you are bound to older versions.
import QtQml 2.0
ApplicationWindow {
id: window
visible: true
width: 600
height: 600
Repeater {
model: ListModel {
ListElement { color: 'black'; x: 400; y: 50 }
ListElement { color: 'black'; x: 100; y: 190 }
ListElement { color: 'black'; x: 70; y: 80 }
ListElement { color: 'black'; x: 30; y: 0 }
ListElement { color: 'black'; x: 340; y: 500 }
ListElement { color: 'black'; x: 210; y: 10 }
}
delegate: MouseArea {
x: model.x
y: model.y
width: 50
height: 50
property QtObject modelItem: model
onClicked: menu.openMenu(x + mouse.x, y + mouse.y, modelItem)
Rectangle {
color: model.color
anchors.fill: parent
}
}
}
Menu {
id: menu
Action { text: "green" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "blue" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "pink" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "yellow" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "orchid" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "orange" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "teal" ; onTriggered: { menu.currentContext.color = text } }
Action { text: "steelblue"; onTriggered: { menu.currentContext.color = text } }
property QtObject currentContext
function openMenu(x, y, context) {
currentContext = context
popup(x, y)
}
}
}
Though I think this answer might solve your problem, I know that it is not really the answer to the question you stated initially.
For the Component-part: I think you misunderstood what a Component is - it is not an Item. It is a prestage in the creation of QtObjects and more something like a prototype or configured factory.
So your function - if it would work - would end at the creation of a invisible thing, from which you could create objects, by calling createObject().
Creating Components is the right thing to do, if you want to create an object at a later time and you might want to create similar objects multiple times, either by JavaScript or by other QML-types that expect Components as some input (e.g. delegates).
To create Components you have multiple possibilities, e.g.:
Qt.createComponent(url)
Component { SomeItem {} }
The first expects you to know the url, which in your case, you do not. To circumvent that, the easiest solution is, to create a new File, like MyMenu.qml
that only contains the Menu {} - then you can create a Component from this.
The second does not expects you to know the url, but it is not dynamically created.
Component {
id: myCmp
Menu {
}
}
onSomeSignal: myCmp.createObject({ prop1: val1 }, this)
Here the Component is automatically created when the object in the file is instantiated. This makes that (one time) initially a bit slower, since more code has to be processed, but you don't have to do it later.
Creating objects like eyllanesc shows with Qt.createQmlObject("Write a new QML-File here") might be also used to create a Component if the top-level element is a Component. If you don't have a Component as top-level, it will also first create a component that is once used to create a QtObject and then is discarded. It is the slowest but most flexible way to dynamically create objects.

Create Objects of inner Component of Object of outer Component

Is it possible to do something like this:
import QtQuick 2.7
import QtQuick.Window 2.2
Window{
id: root_
visible: true
width: 300
height: 300
Component {
id:compouter
Column{
anchors.fill: parent
Component {
id: compinner
Rectangle {
width:parent.width
height:parent.height/2
}
}
}
}
Component.onCompleted: {
var c = compouter.createObject(this)
//var d = c.compinner.createObject(c, {"color": "green"})
//var e = c.compinner.createObject(c, {"color": "red"})
}
}
That is, I want to create multiple Objects inside the outer object (after the outer object was created). However this isn't possible, as I get the error:
TypeError: Cannot call method 'createObject' of undefined
Is there any workaround for this? Is it maybe only possible to instantiate all inner objects during the instantiation of the outer object?
The ids are out of the scope for your function call.
You can either add a function to your outer component, that creates your objects:
Component {
id:compouter
Column{
anchors.fill: parent
function createCompinner(arg) { compinner.createObject(this, arg) }
Component {
id: compinner
Rectangle {
width:parent.width
height:parent.height/2
}
}
}
}
or you expose the inner Component as a property:
Component {
id:compouter
Column{
property alias compinner: compinnerComponent
anchors.fill: parent
Component {
id: compinnerComponent
Rectangle {
width:parent.width
height:parent.height/2
}
}
}
}
and access it like that.
Ok, I found a workaround through sending a signal to the outter object:
import QtQuick 2.7
import QtQuick.Window 2.2
Window{
id: root_
visible: true
width: 300
height: 300
Component {
id:compouter
Column{
anchors.fill: parent
signal createRect(var color)
onCreateRect: {
compinner.createObject(this, {"color": color})
}
Component {
id: compinner
Rectangle {
width:parent.width
height:parent.height/2
}
}
}
}
Component.onCompleted: {
var c = compouter.createObject(this)
c.createRect("green")
c.createRect("red")
}
It's working, but maybe there is some more generic approach to this (see the accepted answer). As the poster suggested, calling a function would be semantically more clean than sending a signal

QML XmlListModel messes sorting when delegated

I'm trying to get a Canvas to draw lines in the same order as presented in this xml-file:
<root>
<doc><nopeus>80.0</nopeus><aika>40.0</aika></doc>
<doc><nopeus>110.0</nopeus><aika>80.0</aika></doc>
<doc><nopeus>120.0</nopeus><aika>120.0</aika></doc>
<doc><nopeus>190.0</nopeus><aika>160.0</aika></doc><doc><nopeus>243.0</nopeus><aika>200.0</aika></doc><doc><nopeus>260.0</nopeus><aika>240.0</aika></doc><doc><nopeus>300.0</nopeus><aika>280.0</aika></doc><doc><nopeus>350.0</nopeus><aika>320.0</aika></doc>
</root>
QML-file with XmlListModel:
import QtQuick 2.0
import Sailfish.Silica 1.0
import QtQuick.XmlListModel 2.0
Page {
id: page
property alias startx : coords.mX
property alias starty : coords.mY
Item {
id: coords
property int mX: 0
property int mY: 0
}
XmlListModel {
id: myxml
source: "/home/nemo/filename.xml"
query: "/root/doc"
XmlRole { name: "nopeus"; query: "nopeus/string()" }
XmlRole { name: "aika"; query: "aika/string()" }
}
ListView {
model: myxml
anchors.fill: page
delegate:
Item {
Chart {
xc: coords.mX;
yc: coords.mY;
xd: aika;
yd: nopeus;
}
}
}
}
Chart.qml:
import QtQuick 2.0
Rectangle {
id: myrect
width: 540
height: 960
color: "transparent"
property int xd: 0
property int yd: 0
property int xc: 0
property int yc: 0
Canvas {
id: mycanvas
width: myrect.width; height: myrect.height;
onPaint: {
var context = getContext('2d')
context.strokeStyle = "#FF0000"
context.lineWidth = 2
context.beginPath()
context.moveTo(xc,yc)
context.lineTo(xd,yd)
context.stroke()
startx = xd
starty = yd
}
}
}
The question is why is the resulting path messed up when inserted in the ListView via delegates? I have tried to sort the path items separately, via a function and another ListModel but the result is the same.
Here is a screenshot:
Delegates are created for each item in a model. Your model contains eight items (as of your input). Hence, you create eight Canvases (each one as a ListView item, i.e. at (theoretical) increasing y w.r.t. ListView origin coordinates).
Combile these problems with the (probably wrongly set) starting points...and you get a random mess! You can't see that, since the Canvases tend to overlap due to sizing/constraints set on the component.
In this case you just need one Canvas on which each myxml item is painted. Here is a (naive) adaptation of your code which correctly shows the path stored in the xml file:
// main.qml
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.XmlListModel 2.0
Window {
visible: true
width: 600
height: 600
XmlListModel {
id: myxml
source: "qrc:/filename.xml" // added to the resources
query: "/root/doc"
XmlRole { name: "nopeus"; query: "nopeus/string()" }
XmlRole { name: "aika"; query: "aika/string()" }
onStatusChanged: {
if(status === XmlListModel.Ready)
comp.mod = myxml // set the model ASA it is ready to be used
}
}
Chart {
id: comp
anchors.fill: parent
mod: myxml
}
}
// Chart.qml
import QtQuick 2.4
import QtQuick.XmlListModel 2.0
Item {
property var mod: undefined
onModChanged: {
if(mod)
mycanvas.requestPaint() // repaint only when the model is available
}
Canvas {
id: mycanvas
width: parent.width; height: parent.height;
onPaint: {
var context = getContext('2d')
context.strokeStyle = "#FF0000"
context.lineWidth = 2
context.beginPath()
context.moveTo(0,0)
// iterate over all the point to print them
for(var i = 0; i < mod.count; i++)
{
var point = mod.get(i)
context.lineTo(point.aika, point.nopeus)
}
context.stroke()
}
}
}
The resulting path rendered:

Resources