How to display a 2 dimension qvariantlist in QML - qt

I have a 2-dimension qvariantlist which I would like to display in QML. I have been trying with a Listview but it only display the dimensions that you indicate. Ex array[0] array1 ...
The following code displays only the first dimension...:
Rectangle {
id: rectangle
height: 300
anchors.top: userFields.bottom
width: parent.width
ListView {
id: listView
anchors.centerIn: parent
width: 180
height: 200
model: RdaDevicePropertyAcess.rdaDataAcquired[userFields.devicePropertyText + '#' + userFields.fieldText]
delegate: Row {
Rectangle {
id: first
width: listView.width / 3
height: 30
Text {
anchors.centerIn: parent
text: modelData[0]
font.pointSize: 20
}
}
}
}
}
I was taking a look of this POST but i didn't work for me. I would like to have the same but displaying it into a qml object.
I also have been trying to use the createObject()javascript function but it didn't work for me neither.
Does anyone has any suggestion?

I reply to myself. Finally I solved the problem with the following QML code. I think it will help many people to save time.
Grid {
id: rectangle
height: 1000
columns: _model.myQvariantList.length
anchors.top: userFields.bottom
anchors.left: parent.left
anchors.right: parent.right
width: 1000
Repeater {
model: _model.myQvariantList[0].length
Repeater {
model: _model.myQvariantList.length
id: repeater1
property int outerIndex: index
Rectangle {
width: 20; height: 20
Text
{
anchors.centerIn: parent
text: _model.myQvariantList[index][repeater1.outerIndex]
font.pointSize: 10
}
}
}
}
}

I fill the 2-dimensional qvariantlist:
case DataType::DT_FLOAT_ARRAY_2D:{
const float * array= entry->getArrayFloat2D(rowCount, columnCount);
QVariantList list;
for (unsigned long row=0; row < rowCount;row++) {
QVariantList rows;
for (unsigned long col=0; col < columnCount;col++) {
rows.append(array[(row*columnCount+col)]);
}
list.append(QVariant::fromValue(rows));
}
return list;
}
Then in the QML part I try to display the 2D qvariantlist:
delegate: Row {
spacing: 5;
Repeater {
model: qvariantListObject
Rectangle {
width: listView.width / 3
height: 30
Text {
anchors.centerIn: parent
text: modelData[index]
font.pointSize: 12
}
}
}
}
But it is only repeating the same row several times.

Related

QML: How to set coordinates inside Repeater

I have an issue with setting coordinates inside Repeater:
import QtQuick
Window {
id: mainWindow
property int wi: 640
property int he: 500
width: wi
height: he
visible: true
title: qsTr("Game")
Rectangle {
id: gameWindow
width: wi/1.6
height: he
anchors.right: parent.right
visible: true
color: "black"
clip: true
Grid {
id: gameGrid
columns: 25
spacing: 0
rows: 32
anchors.fill: parent
Repeater {
model: 600
Rectangle {
width: wi/40
height: 20
border.width: 2
color: "grey"
}
}
}
Grid {
id: sGrid
columns: gameGrid.columns
spacing: gameGrid.spacing
rows: gameGrid.rows
anchors.fill: gameGrid
Repeater {
model: 5
Rectangle {
// anchors.horizontalCenter: sGrid.horizontalCenter
// anchors.verticalCenter: sGrid.verticalCenter
// x: (wi/2) + (index * (wi/40) )
// y: he/2
width: wi/40
height: 20
border.width: 1
color: "red"
}
}
}
}
}
Whole code above, but my question is about the second Repeater with 5 Rectangles.
I have tried to solve that with many ways. Most obvious seemed to me placing coordinates inside Repeater, but now I know it does not work like this - I have to place coordinates somehow inside Rectangle. I have commented code, where are the ways I have tried to solve this.
Anchors work very well - it places the first element exactly where I am expecting.
Problem appears with the next elements. They are placing inside the same element of Grid. I do not understand why the coordinates does not working. Documentation shows I could use "index", don't know, maybe the point is that's "read only" property? I have tried to set Rectangle with prefix "delegate:" with the same result as well.
In your question, you mention you have Grid + Repeater + Rectangle. I am not sure what you want to achieve, but, it feels like you may have better luck by going GridView + Rectangle because GridView supports a model.
Since you want coordinate control of your Rectangles, it is possible to do this alone with Repeater + Rectangle. No need for Grid since the Grid will impact the coordinate system of your Rectangle.
Below illustrates how you can use a simple ListModel to control the placement of your Rectangles:
import QtQuick
import QtQuick.Controls
Page {
Repeater {
model: ListModel {
ListElement { p:100; q:100; w:50; h:50; c:"red"; o:0 }
ListElement { p:200; q:100; w:50; h:50; c:"green"; o:15 }
ListElement { p:300; q:200; w:50; h:50; c:"blue"; o:30 }
ListElement { p:300; q:300; w:50; h:50; c:"orange"; o:45 }
ListElement { p:200; q:400; w:50; h:50; c:"purple"; o:60 }
}
delegate: Rectangle {
x: p
y: q
width: w
height: h
color: c
rotation: o
}
}
}
You can Try it Online!
[EDIT]
With the following, it shows how you could use index in 10 delegates to place your rectangles using a formula:
import QtQuick
import QtQuick.Controls
Page {
Repeater {
model: 10
delegate: Rectangle {
x: 100 + (index % 4) * 100
y: 100 + Math.floor(index / 4) * 100
width: 50
height: 50
color: ["green","red","orange","blue","purple"][index%5]
rotation: index * 10
Text {
anchors.centerIn: parent
text: index
color: "white"
}
}
}
}
You can Try it Online!

QML ListView: Binding loop detected for property "height"

I have a QML ListView, and I'm trying to dynamically add elements to it. I want the background rectangle to also scale dynamically as elements are added/removed from the ListView. Right now I get a binding loop, and I understand what they are but I can't figure out where it's coming from. I played around changing the code a bit and I was able to get rid of the binding loop one time but then the ListView couldn't be scrolled. Anyone have any ideas?
import QtQuick 2.15
import QtQuick.Window 2.0
Window {
visible: true
width: 800
height: 800
Rectangle {
id: listContainer
height: childrenRect.height
width: parent.width
color: "transparent"
anchors {
top: parent.top
topMargin: 30
left: parent.left
leftMargin: 45
}
ListView {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
model: myModel
height: childrenRect.height
header:
Text {
z: 2
height: 50
text: "HEADER"
color: "black"
}
delegate: Component {
Item {
Text {
id: userName;
text: name;
color: "black";
font.pixelSize: 50
anchors {
left: parent.left
leftMargin: 20
}
}
Rectangle {
height: 1
color: 'black'
width: listContainer.width
anchors {
left: userName.left
top: userName.top
topMargin: - 12
leftMargin: -15
}
}
}
}
spacing: 80
}
}
ListModel {
id: myModel
}
/* Fill the model with default values on startup */
Component.onCompleted: {
for (var i = 0; i < 100; i++) {
myModel.append({
name: "Big Animal : " + i
})
}
}
}
EDIT: As suggested by #Aditya, the binding loop can be removed by having a static ListView height, but I don't want it to be that way. I'm using the rectangle as a background for the ListView and I want it to scale according to the ListView. For example, if I only add two elements, I want the rectangle to also scale for those two elements and not cover the entire screen. This causes a problem:
import QtQuick 2.15
import QtQuick.Window 2.0
Window {
visible: true
width: 800
height: 800
Rectangle {
id: listContainer
height: childrenRect.height
width: parent.width
color: "yellow"
anchors {
top: parent.top
topMargin: 30
left: parent.left
leftMargin: 45
}
ListView {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
model: myModel
height: 800//childrenRect.height
header:
Text {
z: 2
height: 50
text: "HEADER"
color: "black"
}
delegate: Component {
Item {
Text {
id: userName;
text: name;
color: "black";
font.pixelSize: 50
anchors {
left: parent.left
leftMargin: 20
}
}
Rectangle {
height: 1
color: 'black'
width: listContainer.width
anchors {
left: userName.left
top: userName.top
topMargin: - 12
leftMargin: -15
}
}
}
}
spacing: 80
}
}
ListModel {
id: myModel
}
/* Fill the model with default values on startup */
Component.onCompleted: {
for (var i = 0; i < 2; i++) {
myModel.append({
name: "Big Animal : " + i
})
}
}
}
I also tried separating the header from ListView into a different component and anchoring the listview below it and that worked. The only problem was it could not be scrolled with the listview. Worst case, I could make a scrolling animation for it but that seems like an inefficient solution and I'd like to know why this doesn't work.
You are probably also biting yourself with the Item as the top-level in the delegate, since that doesn't give any implicit size, which the ListView uses to calculate the scrolling needs. You can simply use Text directly as the delegate (you don't need the Component either) and put the line/rectangle inside. If doing so you can use the contentHeight property of ListView to size the background.
Furthermore, I would suggest to have the ListView as the top level and do any styling secondary, with which I mean, put the background Rectangle inside.
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
ListView {
id: listView
model: 3
anchors.fill: parent
Rectangle { //background
color: "yellow"
z: -1
width: listView.width
height: listView.contentHeight
}
delegate: Text {
text: "name" + index
color: "black";
font.pixelSize: 50
leftPadding: 20
Rectangle {
height: 1
color: 'black'
width: listView.width
y: - 12
x: -15
}
}
spacing: 80
}
}
Btw, if you are going to put the ListView in some RowLayout or something, you probably also want implicitHeight: contentHeight in the ListView.
The binding loop is originating from the ListView's height: childrenRect.height statement. It looks like the ListView needs to be a fixed height, or at least not dependent on childrenRect. It is most likely how the ListView element knows that the view should be scrollable to view elements below.
It really depends on what you're trying to achieve with setting the height to match childrenRect, but in my case, ListView height is changing based on the children (per your desire presumably). With a 100 items the height came out to be 7970. With 5 items in the model, the result was 350. You can check this by adding a debug or console.log() with onHeightChanged However, as a result of this scaling, the ListView is assumed to be big enough to view the entire data set regardless of the window parent container size.
You do not need to scale the ListView height to match the contents; that is what it is built for. It allows scrolling because the contents are too big to be shown within its limited height.
I was able to achieve get rid of the binding loop and be able to scroll by simply changing the statement to a static value, which is the parent height of 800 as an example:
Window {
visible: true
width: 800
height: 800
Rectangle {
id: listContainer
height: childrenRect.height
width: parent.width
color: "transparent"
anchors {
top: parent.top
topMargin: 30
left: parent.left
leftMargin: 45
}
ListView {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
model: myModel
height: 800//childrenRect.height
header:
Text {
z: 2
height: 50
text: "HEADER"
color: "black"
}
delegate: Component {
Item {
Text {
id: userName;
text: name;
color: "black";
font.pixelSize: 50
anchors {
left: parent.left
leftMargin: 20
}
}
Rectangle {
height: 1
color: 'black'
width: listContainer.width
anchors {
left: userName.left
top: userName.top
topMargin: - 12
leftMargin: -15
}
}
}
}
spacing: 80
}
}
ListModel {
id: myModel
}
/* Fill the model with default values on startup */
Component.onCompleted: {
for (var i = 0; i < 100; i++) {
myModel.append({
name: "Big Animal : " + i
})
}
}
}
Edit:
I feel like you're trying to just secure a background for a scalable ListView. Having a static background as a container works but not very well for modern unser interfaces - any bounce effects or such will not move the rectangle. You could achieve this by anchoring the rectangle to the ListView element but it is a very roundabout way. Instead, you could just set a rectangle to style each element of the ListView delegate instead.
delegate: Component {
Item {
Rectangle{
width: listContainer.width
height: userName.height+13
//add 13 to adjust for margin set below
anchors {
left: userName.left
top: userName.top
topMargin: - 12
leftMargin: -15
//just copying from the other rectangle below
}
gradient: Gradient {
//I am just using gradient here for a better understanding of spacing. You could use color.
GradientStop { position: 0.0; color: "aqua" }
GradientStop { position: 1.0; color: "green" }
}
}
Text {
id: userName;
text: name;
color: "black";
font.pixelSize: 50
anchors {
left: parent.left
leftMargin: 20
}
}
Rectangle {
height: 1
color: 'black'
width: listContainer.width
anchors {
left: userName.left
top: userName.top
topMargin: - 12
leftMargin: -15
}
}
}
}
This will make sure that the rectangle background behind the ListView will look like it is scrolling with the items. In reality we have broken one rectangle into multiple and just set each element with one. You can also use this type of styling to achieve alternate colors in your list for example.

How to keep last items in view when adding them to listview?

I have a ListView where I dynamically add ListElements. The ListView has a maximum of 10 items that can be in view, so I also have a ScrollBar. When I add the 11th+ item, I always want it to scroll into view.
ListView {
id: logListView
delegate: logListViewDelegate
model: logListModel
anchors.fill: parent
ScrollBar.vertical: ScrollBar {}
}
ListModel {
id: logListModel
}
Component {
id: logListViewDelegate
Item {
height: 44
width: logListView.width
Text {
id: countText
width: 18
font {
pixelSize: 16
family: variables.globalFont
}
color: colors.foregroundColor3
text: index+1
anchors {
left: parent.left
leftMargin: 7
verticalCenter: parent.verticalCenter
}
}
Text {
id: timeText
width: 96
horizontalAlignment: Text.AlignRight
font {
pixelSize: 24
family: variables.globalFont
}
color: colors.foregroundColor1
text: time
anchors {
left: countText.right
verticalCenter: parent.verticalCenter
}
}
Text {
id: unitText
width: 18
font {
pixelSize: 16
family: variables.globalFont
}
color: colors.foregroundColor3
text: unit
anchors {
left: timeText.right
leftMargin: 6
bottom: timeText.bottom
bottomMargin: 2
}
}
}
}
I have a button outside of the listview that when clicked just does:
logListModel.append({
time: myTime, unit: myUnit
})
The new item just gets added to the bottom of the list and is hidden when there are more than 10. When adding an item , I would like the list to automatically scroll to it.
In your ListView, scroll to the bottom by changing the currentIndex when onCountChanged is called (when your model has changed):
ListView {
id: logListView
delegate: logListViewDelegate
model: logListModel
anchors.fill: parent
ScrollBar.vertical: ScrollBar {}
onCountChanged: {
var newIndex = count - 1 // last index
positionViewAtEnd()
currentIndex = newIndex
}
}

How to drag an item outside a ListView in QML

I am developing a QML application which basically contains two ListView. I would like to copy a QML item from one ListView to another. I tried to handle this by setting Drag property in the delegate but the item cannot go outside the view when I drag the item, I think the Flickable container handles mouse events.
So, I want to try the following:
create a mousearea which overlaps the to ListView
create a new object by calling **createComponent() / createObject()**
reparent this object to the mousearea
handle mouse events in the mousearea till drop
This solution seems to me a little complicated, so do you have a better way to achieve this ?
This was a bad idea and too much complicated. I think I got a way to achieve this:
each delegate of the ListView has a hidden Item which can be dragged,
as my ListView are in a reusable component, I use a property to pass a higher item (a Rectangle here and NOT a **MouseArea**) which can be used as parent for dragged items,
the higher item contains the two ListView (and maybe more in the future),
when the drag begins, the item is set to visible and reparented using a **State**
So, I missed the point that set the parent should solve my problem.
Next code is just an idea, but the key is to have a MouseArea inside a delegate for the first ListView so the user can drag the items and drop them into a DropArea which belongs to the second ListView.
In this example, model is very simple, just a number. And when the item is dropped, it is removed from the first ListView:
listView.model.remove(listView.dragItemIndex)
Just remove that line of code to copy the item instead of removing.
main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
width: 600
height: 600
Rectangle {
id: root
width: 400
height: 400
ListView {
id: listView
width: parent.width / 2
height: parent.height
property int dragItemIndex: -1
model: ListModel {
Component.onCompleted: {
for (var i = 0; i < 10; ++i) {
append({value: i});
}
}
}
delegate: Item {
id: delegateItem
width: listView.width
height: 50
Rectangle {
id: dragRect
width: listView.width
height: 50
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
color: "salmon"
border.color: Qt.darker(color)
Text {
anchors.centerIn: parent
text: modelData
}
MouseArea {
id: mouseArea
anchors.fill: parent
drag.target: dragRect
drag.onActiveChanged: {
if (mouseArea.drag.active) {
listView.dragItemIndex = index;
}
dragRect.Drag.drop();
}
}
states: [
State {
when: dragRect.Drag.active
ParentChange {
target: dragRect
parent: root
}
AnchorChanges {
target: dragRect
anchors.horizontalCenter: undefined
anchors.verticalCenter: undefined
}
}
]
Drag.active: mouseArea.drag.active
Drag.hotSpot.x: dragRect.width / 2
Drag.hotSpot.y: dragRect.height / 2
}
}
}
ListView {
id: listView2
width: parent.width / 2
height: parent.height
anchors.right: parent.right
property int dragItemIndex: -1
DropArea {
id: dropArea
anchors.fill: parent
onDropped: {
listView2.model.append(listView.model.get(listView.dragItemIndex))
listView.model.remove(listView.dragItemIndex)
listView.dragItemIndex = -1;
}
}
model: ListModel {
Component.onCompleted: {
for (var i = 0; i < 1; ++i) {
append({value: i});
}
}
}
delegate: Item {
id: delegateItem2
width: listView2.width
height: 50
Rectangle {
id: dragRect2
width: listView2.width
height: 50
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
color: "salmon"
border.color: Qt.darker(color)
Text {
anchors.centerIn: parent
text: modelData
}
}
}
}
}
}

Share equally the horizontal space in a QML Row

I need to share equally the horizontal space between all "buttons" in my Row.
I use this code with a Repeater.
Component {
id: buttonComponent
Rectangle {
height: buttonRow.height
width: buttonRow.width / buttonsRepeater.count
color: "#FFDDDD"
Text {
anchors.centerIn: parent
text: model.text
}
}
}
Rectangle {
color: "#DDDDDD"
id: buttonBar
height: 30
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
Row {
id: buttonRow
anchors.fill: parent
Repeater {
id: buttonsRepeater
model: buttonsModel
delegate: buttonComponent
}
}
}
Now, I like to compute the ideal width of the Row such that all my button texts appear correctly.
How can I get this ideal width?
If you don't want to use QtQuick.Layouts as they are not really ready yet, you can use this :
Rectangle {
id: buttonBar;
color: "#DDDDDD";
height: 30;
width: (buttonColumn.width + 20 + buttonRow.spacing) * buttonsRepeater.count;
anchors {
bottom: parent.bottom;
left: parent.left;
}
Column {
id: buttonColumn;
visible: false;
Repeater {
model: buttonsModel;
delegate: Text {
text: model.text;
}
}
}
Row {
id: buttonRow;
anchors.fill: parent;
property real itemWidth : ((width + spacing) / buttonsRepeater.count) - spacing;
Repeater {
id: buttonsRepeater;
model: buttonsModel;
delegate: Component {
id: buttonDelegate;
Rectangle {
height: parent.height;
width: parent.itemWidth;
color: "#FFDDDD";
border.width: 1;
Text {
anchors.centerIn: parent;
text: model.text;
}
}
}
}
}
}
I just used a hidden Column to easily compute max width of Text elements, and added a little padding in the bar width to avoid unspaced text.
The minimum width of a button itself is the implicitWidth property of its Text element.
One solution to your problem might be to add code in the Component.onCompleted handler, i.e. code that is executed after the repeater has created its items, and then sum up these implicitWidth properties of each of the repeater's item (which you can get by using its itemAt(index) function).
These kinds of dynamic layout is a bit cumbersome in QML still, which will get much better in Qt 5.1 with the introduction of Qt Quick Layouts

Resources