Filter ObjectModel in DelegateModel in QML - qt

I wonder if it is possible to somehow use an ObjectModel as the model for multiple DelegateModel to use it's ability to create Groups.
For example, I might have an ObjectModel containing multiple Rectangle as this
ObjectModel {
id: rootModel
Rectangle { width: 50; height: 20; color: 'red' }
Rectangle { width: 80; height: 50; color: 'blue' }
Rectangle { width: 50; height: 20; color: 'green' }
Rectangle { width: 80; height: 50; color: 'orchid' }
Rectangle { width: 50; height: 20; color: 'black' }
}
My goal might be to display all Rectangle with the width: 50 in one View, while those with the width: 80 in another view.
So I create a DelegateModel
DelegateModel {
id: delMod1
model: rootModel
groups: [
DelegateModelGroup { name: 'width50' },
DelegateModelGroup { name: 'width80' }
]
}
As far as I understood the documentation I need to attach the property
item.DelegateModel.inWidth50: ....width === 50
item.DelegateModel.inWidth80: ....width === 80
or something like this, to add my elements to the groups. But this is where I fail as I don't know where and how to do this.
Though there are tons of workarounds (such as disassembling the Objects in the ObjectModel and using a ListModel, recreating the Objects in the DelegateModel delgate, which robbs a lot of the versatility as the Objects and it's children would need to have a predictable structure...) I think it would be wonderful, if it is possible to make this idea run.
Takk fyrir,
-m-

Related

How can i overwrite items in a data property

I'm trying to make some qml components that are more general but have the flexibility of defaults but for items. Here are some example components
A.qml
import QtQuick 2.15
Rectangle {
property alias inner: inside.data
color: "red"
width: 40
height: 40
Item {
id: inside
}
}
B.qml
import shared.debug 1.0 as Debug
import QtQuick 2.15
Debug.A {
inner: Rectangle {
width: 30
height: 30
color: "blue"
}
}
C.qml
import shared.debug 1.0 as Debug
import QtQuick 2.15
Debug.B {
inner: Rectangle {
width: 20
height: 20
color: "black"
}
}
qmldir
module shared.debug
A 1.0 A.qml
B 1.0 B.qml
C 1.0 C.qml
Used here
Column {
spacing: 10
Debug.A {}
Debug.B {}
Debug.C {}
}
Im looking for a way to make the Debug.C show just the red and black squares, i understand why the blue is showing but i was hoping i could make a straight forward default and then have it be removed and overwritten when used by a child element.
Ive tried to make properties that dont use alias but thats the same problem. Ive also tried just hiding the original content but that feels roundabout in that the original items are still there just not visible.
I would suggest using a Loader instead of an Item in A.qml:
import QtQuick 2.15
Rectangle {
property alias inner: inside.sourceComponent
color: "red"
width: 40
height: 40
Loader {
id: inside
}
}
Then you can replace that inside field with a Component of your choosing:
B.qml:
Debug.A {
inner: Component {
Rectangle {
width: 30
height: 30
color: "blue"
}
}
}
The data property is of type list<QtObject>. It seems like binding to it multiple times will append.
Column {
spacing: 10
A { Component.onCompleted: console.log("A", inner.length) } // 0
B { Component.onCompleted: console.log("B", inner.length) } // 1
C { Component.onCompleted: console.log("C", inner.length) } // 2
}
Similar like making the alias to the data property the default which means all child items of component A would be appended to the list.
component A: Rectangle {
default property alias inner: inside.data
color: "red"; width: 80; height: 80
Item {
id: inside
}
}
component B: A {
Rectangle { width: 40; height: 40; color: "blue" }
}
component C: B {
Rectangle { width: 20; height: 20; color: "black" }
}
To circumvent this you could make inner an Item and bind it to the data or children property.
DANGER Keep in mind that this is a "hack". You should only use it if you know what you're doing. You can't add any other item into the inner Item because the binding on data will always overwrite it.
component A: Rectangle {
property Item inner
color: "red"; width: 80; height: 80
Item {
data: [inner]
}
}
component B: A {
inner: Rectangle { width: 40; height: 40; color: "blue" }
}
component C: B {
inner: Rectangle { width: 20; height: 20; color: "black" }
}
Column {
spacing: 10
A {}
B {}
C {}
}

QML Row: How to extend qml containers (Row or Column) from other file

It seems should have a solution for sure.
Suppose I have a Test.qml file containing this:
import QtQuick 2.0
Rectangle {
color: "green"
Row {
id: row
spacing: 10
anchors.fill: parent
Rectangle {
color: "red";
width: 100;
height: 100;
}
Rectangle {
color: "red";
width: 100;
height: 100;
}
Rectangle {
color: "red";
width: 100;
height: 100;
}
}
}
Now suppose we want to use this Test.qml within another file like main.qml:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: window
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Test {
anchors.fill: parent;
// I want to be able to add new items (rects) to the row inside Test.qml
}
}
Now suppose we want to extend items to the row object in Test.qml, But we want to add from main.qml. How we can do that? is that even possible?
(FYI: The application of this feature would be to develop a placeholder form and fill the items in the other items so we can skip duplicate codes. )
You can do this without creating objects dynamically. You need to use a default property that is aliased to the contents of your Row. A default property means Items that get added to your object will actually be assigned to that property instead. In Test.qml, add this:
Rectangle {
color: "green"
default property alias contents: row.data
Row {
id: row
...
}
}
Now you can add other items to it from main.qml, like this:
Test {
anchors.fill: parent;
// Automatically gets added to 'row'
Rectangle {
color: "blue"
width: 100
height: 100
}
}
You can create objects dynamically:
MyRow.qml:
Row {
id: row
spacing: 10
anchors.fill: parent
Rectangle {
color: "red";
width: 100;
height: 100;
}
}
main.qml:
MyRow{
id: myRow
Component.onCompleted: Qt.createQmlObject('import QtQuick 2.0; Rectangle {color: "green"; width: 100; height: 100}', myRow)
}

QML How to paint different to grabToImage()

Is there any possible way to make grabToImage() buffer different from what's displayed in GUI? I'm using ChartView without legend in GUI and want to export it to PNG with legend. So I try:
chartView.legend.visible = true
chartView.update()
chartView.grabToImage(function(result) {
console.log("grabbed")
var path = filename.toString().replace(/^(file:\/{2})/,"");
console.log(path + result.saveToFile(path));
chartView.legend.visible = false
update();
});
But both those updates happen only after the control comes out of this function, so I don't get legend drawn in PNG. Also I would like the appearance of legend to be unnoticable to user. Are there any ways to do that in QML?
I am unsure, whether I got you quite right.
My suggestion is, to not grab the Item you display, but a copy of it, that you then modify as you like.
Copy here does not mean, that you have the same object twice, but that you render it twice by using a ShaderEffectSource. Before you grab this ShaderEffectSource to image, you can add anything you like.
In my example I display a simple Rectangle with a nice gradient. What I save is the same Rectangle that is extended by the Text 'I am legend'. The user won't see this text apearing in the view at any time.
Rectangle {
id: commonView
width: 200
height: 200
gradient: Gradient {
GradientStop { position: 0; color: 'steelblue' }
GradientStop { position: 1; color: 'orange' }
}
MouseArea {
anchors.fill: parent
// don't grab the rectangle itself.
onClicked: legendView.grabToImage(function(result) {
console.log(result.saveToFile("something.png"));
});
}
}
ShaderEffectSource {
id: legendView
visible: false // Does not need to be shown.
sourceItem: commonView
width: 200
height: 200
Text {
anchors {
right: parent.right
bottom: parent.bottom
}
text: 'I am legend'
}
}
You might optimize the performance by only having the ShaderEffectSource active or even created when needed.
You might use the ShaderEffectSource.live-property to disable updating of it. Then use scheduleUpdate() to trigger the update.
This might look like this:
Rectangle {
id: commonView
width: 200
height: 200
gradient: Gradient {
GradientStop { position: 0; color: 'steelblue' }
GradientStop { id: gs1; position: 1; color: 'orange' }
}
MouseArea {
anchors.fill: parent
onClicked: {
gs1.position -= 0.1
legendView.save()
}
}
}
ShaderEffectSource {
id: legendView
y: 200
visible: false // Do not render it (will only be rendered when called grapToImage()
sourceItem: commonView
width: 200
height: 200
live: false // Will only be updated, when explicitly called for
function save() {
console.log('Schedule Save')
scheduleUpdate() // explicitly update. grabToImage() will force rendering afterwards.
legendView.grabToImage(function(result) {
console.log(result.saveToFile("something" +gs1.position.toFixed(1) + ".png"));
})
}
// Put additional stuff on it.
Text {
anchors {
right: parent.right
bottom: parent.bottom
}
text: 'I am legend!'
}
}

QML ListElement pass list of strings

I have a listview who's delegate has a repeater in it which is supposed to be populated by text. If the repeater's model property is hard coded this way:
model: ['String 1', 'String 2', 'String 3'];
It works perfectly by showing 3 items in the repeater's region.
However, I want to send such a list using a ListElement and this is what I tried:
ListElement{
shape: "Circle"; colors: ['Red', 'Blue'];
}
Unfortunately this approach doesn't work and throws an error:
ListElement: cannot use script for property value
How many I achieve this? TIA
You can't:
Values must be simple constants; either strings (quoted and optionally within a call to QT_TR_NOOP), boolean values (true, false), numbers, or enumeration values (such as AlignText.AlignHCenter).
The most powerful way to expose data to views is by creating a C++ model.
However, if you really don't want to go to C++, you could store the colours in a comma-separated string, and then split them:
import QtQuick 2.6
import QtQuick.Window 2.0
Window {
visible: true
width: 200
height: 200
ListView {
width: 32
height: 64
anchors.centerIn: parent
model: ListModel {
ListElement{
shape: "Circle"
colors: "red, blue"
}
ListElement{
shape: "Square"
colors: "green,yellow"
}
}
delegate: Rectangle {
width: 32
height: 32
radius: shape === "Circle" ? width / 2 : 0
property var colorArray: colors.split(",")
gradient: Gradient {
GradientStop {
position: 0
color: colorArray[0]
}
GradientStop {
position: 1
color: colorArray[1]
}
}
}
}
}
An alternative to #Mitch's answer would be to ditch the ListModel and use a plain Javascript array of objects as the model.
With that solution, you'll lose the dynamic features of the ListModel (adding, removing, inserting, ...). You also won't be able to use sections in your view or use a proxymodel on this model.
import QtQuick 2.6
import QtQuick.Window 2.0
Window {
visible: true
width: 200
height: 200
ListView {
width: 32
height: 64
anchors.centerIn: parent
model: [
{
shape: "Circle",
colors: ["red", "blue"]
},
{
shape: "Square",
colors: ["green", "yellow"]
}
]
delegate: Rectangle {
width: 32
height: 32
radius: modelData.shape === "Circle" ? width / 2 : 0
gradient: Gradient {
GradientStop {
position: 0
color: modelData.colors[0]
}
GradientStop {
position: 1
color: modelData.colors[1]
}
}
}
}
}

Better way to reparent visual items in QML

It would seem that in the design of QML user reparent was not really "envisioned", because even though it is possible, it involves creating and changing states, which is just not convenient to add to each and every item.
import QtQuick 1.0
Item {
width: 200; height: 100
Rectangle {
id: redRect
width: 100; height: 100
color: "red"
}
Rectangle {
id: blueRect
x: redRect.width
width: 50; height: 50
color: "blue"
states: State {
name: "reparented"
ParentChange { target: blueRect; parent: redRect; x: 10; y: 10 }
}
MouseArea { anchors.fill: parent; onClicked: blueRect.state = "reparented" }
}
}
I was wondering if there is a more elegant way to reparent items without polluting items with unnecessary states?
not certain if you need to use QtQuick 1.0, but with 2.0 this also works and is imo more straight forward.
import QtQuick 2.0
Item {
width: 200; height: 100
Rectangle {
id: redRect
width: 100; height: 100
color: "red"
}
Rectangle {
id: blueRect
x: redRect.width
width: 50; height: 50
color: "blue"
MouseArea { anchors.fill: parent; onClicked:
{ blueRect.parent = redRect; blueRect.x = 10; blueRect.y = 10 }
}
}
}

Resources