Qt5 QML - Set component of Loader as an argument problem - qt

I'm looking for how to input for Loader inside Component.
Here is my problem.
// CustomSplitView.qml
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
Pane {
function addItem(text, item) {
/**
Here is the problem part I think.
"headerText" is correctly applied to the 'template' object,
but "content" is not.
**/
splitView.addItem(template.createObject(null, {"headerText": text, "content": item}))
}
Component {
id: template
Pane {
property alias headerText: label.text
property alias content: loader.sourceComponent
ColumnLayout { anchors.fill: parent
Label { id: label }
Loader {
id: loader
Layout.fillWidth: true; Layout.fillHeight: true
sourceComponent: Rectangle { color: "Green" }
} // Loader
} // ColumnLayout
} // Pane
} // Component
SplitView {
id: splitView
anchors.fill: parent
} // SplitView
} // Pane
// Usage
Pane {
Component {
id: redRect
Rectangle { color: "Red" }
} // Component
Column { anchors.fill: parent
Button {
text: "add"
onClicked: customSplitView.addItem("RED", redRect.createObject())
} // Button
CustomSplitView { id: customSplitView }
} // Column
} // Pane
Result: When "add" button clicked, adding item inside of the split view has "RED" text but green rectangle appears, not red.
It's not a size issue. (detailed resizing code has been omitted for code simplicity)
Any advise will helps a lot, for what I missed, or other approaches.

Whenever you want to dynamically create (and, possibly destroy) components, I would highly recommend using a model with a delegate. Because your delegate appears to be dynamic, I recommend using DelegateChooser. This will remove the pain of managing the dynamic creation and destruction of your objects.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
header: Frame {
RowLayout {
Button {
text: "add"
onClicked: customSplitView.addRandomItem()
}
Button {
text: "clear"
onClicked: customSplitView.clear()
}
}
}
CustomSplitView {
id: customSplitView
}
}
// CustomSplitView.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt.labs.qmlmodels
Pane {
anchors.fill: parent
SplitView {
anchors.fill: parent
Repeater {
id: repeater
model: ListModel {
id: listModel
}
delegate: DelegateChooser {
role: "typ"
DelegateChoice {
roleValue: "label"
delegate: ColumnLayout {
SplitView.preferredWidth: w
SplitView.preferredHeight: h
Label {
Layout.fillWidth: true
text: txt
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
Label {
Layout.fillWidth: true
Layout.fillHeight: true
text: lbl
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
}
}
DelegateChoice {
roleValue: "rect"
delegate: ColumnLayout {
SplitView.preferredWidth: w
SplitView.preferredHeight: h
Label {
Layout.fillWidth: true
text: txt
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Qt.rgba(r, g, b, 1)
}
}
}
}
}
}
function addRandomItem() {
if (Math.random() < 0.5) {
listModel.append( {
typ: "label",
txt: "Label",
lbl: "Label " + Math.random(),
w: 200,
h: 50,
r: 0,
g: 0,
b: 0
} );
} else {
listModel.append( {
typ: "rect",
txt: "Rect",
lbl: "",
w: 50,
h: 50,
r: Math.random(),
g: Math.random(),
b: Math.random()
} );
}
}
function clear() {
listModel.clear();
}
Component.onCompleted: addRandomItem()
}
You can Try it Online!
If, on the other hand, you really want fully adhoc QML components controlled by a string. You should consider using createQmlObject or Loader with data-uri. Below is a demonstration of the latter:
import QtQuick
import QtQuick.Controls
Page {
id: page
Repeater {
model: [
{ qml: "Rectangle { }",
obj: { width: 100,
height: 100,
color: "red" } },
{ qml: "Rectangle { radius: height * 0.5 }",
obj: { width: 50,
height: 50,
color: "orange" } },
{ qml: "Button { }",
obj: { text: "Click Me" } }
]
delegate: Loader {
x: Math.random() * page.width
y: Math.random() * page.height
Component.onCompleted: setSource(
`data:text/plain,import QtQuick
import QtQuick.Controls
` + modelData.qml,
modelData.obj
);
}
}
}
You can Try it Online!

Thanks for insight of Stephen Quan, I could instantiate Component type as an argument of function.
// CustomSplitView.qml
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14
Pane {
function addItem(text, component) {
listModel.append( {text, component} )
} // addItem
SplitView { id: splitView
anchors.fill: parent
Repeater {
model: ListModel { id: listModel } // ListModel
delegate: Pane {
property string _headerText: text /* input "text" */
property var _component: component /* input "component" */
ColumnLayout { anchors.fill: parent
Label { text: _headerText }
Loader { Layout.fillWidth: true; Layout.fillHeight: true
sourceComponent: _component
} // Loader
} // ColumnLayout
} // Loader
} // Repeater
} // SplitView
} // Pane
// Usage
Window {
Component { id: rectComponent
Rectangle {
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
} // Rectangle
} // Component
Component { id: labelComponent
Label {
text: "Label " + Math.random()
} // Label
} // Component
Pane { anchors.fill: parent
ColumnLayout { anchors.fill: parent
CustomSplitView { id: customSplitView
Layout.fillWidth: true; Layout.fillHeight: true
orientation: Qt.Horizontal
} // customSplitView
Button {
text: "Add"
onClicked: {
customSplitView.addItem(
"Label", Math.random() < 0.5 ? rectComponent : labelComponent)
} // onClicked
} // Button
} // ColumnLayout
} // Pane
} // Window
Now the "add" button generates random color of rectangle or random label randomly.

Related

QML multiple window with keeping the state of all windows

I want to develop a qml application that starts with the main window and three buttons are available in the main window. By pressing each button a new page(Layout) should show up, but after returning to the main window, the state of the previous page should not change. I have tried ListView but it did not work, because it destroys the Item after pop(). Here is my Code(PagesOne.qml is a sample for three pages that I want to open):
main.qml
ApplicationWindow {
id: root
visible: true
width: 8 * Screen.width / 10
height: 8 * Screen.height / 10
color: "#154c79"
// Keep the window on the given x,y on starting
Component.onCompleted: {
x: Screen.width / 10
y: Screen.height / 10
}
minimumWidth: 700
minimumHeight: 400
// Current Working Layout
Component {
id: pageOne
PageOne{}
}
// Empty Layout
Component {
id: pageTwo
PageTwo{}
}
// Empty Layout
Component {
id: pageThree
PageThree{}
}
property variant items: [pageOne.createObject(), pageTwo.createObject(), pageThree.createObject()]
StackView {
id: stack
anchors.fill: parent
// initialItem: pageOne
Component.onCompleted: {
stack.push(items[2], {"destroyOnPop": false})
stack.push(items[1], {"destroyOnPop": false})
stack.push(items[0], {"destroyOnPop": false})
}
}
// change layout on call
function load_layout(layout){
console.log("#############", stack.depth, "#############")
switch (layout){
case 'one':
stack.pop(0, {"destroyOnPop": false})
break;
case 'two':
stack.pop(1, {"destroyOnPop": false})
break;
case 'three':
stack.pop(2, {"destroyOnPop": false})
break;
default:
stack.pop(0, {"destroyOnPop": false})
}
}
}
PageOne.qml:
Item{
id: root
// anchors.fill: parent
// Empty rect
Rectangle{
color: "#03fc88"
anchors.fill: parent
TextField {
anchors.right: parent.right
anchors.top: parent.top
width: 120
height: 60
placeholderText: qsTr("Enter: ")
}
Label{
anchors.centerIn: parent
text: "Page two"
}
// return button
RoundButton {
id: btnBack
text: "\u003C"
onClicked: {
if(text === "\u003C"){
load_layout('one')
}
}
}
}
}
Is there any suggestion that helps me?
A simple example:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.0
Window {
height: 800
width: 600
visible: true
title: qsTr("Stack test")
ColumnLayout {
anchors.fill: parent
spacing: 0
StackView {
id: stack
Layout.fillHeight: true
Layout.fillWidth: true
initialItem: screen1
Rectangle {
id: screen1
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
}
Rectangle {
id: screen2
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
}
Rectangle {
id: screen3
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
}
}
RowLayout {
id: bar
Layout.fillWidth: true
Layout.preferredHeight: 50
spacing: 0
Button {
Layout.preferredWidth: 300
Layout.preferredHeight: 50
text: "Change color"
onClicked: {
stack.currentItem.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1);
}
}
Button {
Layout.preferredWidth: 300
Layout.preferredHeight: 50
text: "Next"
onClicked: {
switch(stack.currentItem)
{
case screen1:
stack.replace(screen2)
break;
case screen2:
stack.replace(screen3)
break;
case screen3:
stack.replace(screen1)
break;
}
}
}
}
}
}
Because, in your main page, you use Component, by definition, QML can create and destroy as many instances of the page corresponding to push/pull on the stack. So, the feature you described is as per design.
If you want to maintain an instance that is persistent, do not use Component. Instead, declare an instance of the pages but keep them invisible.
In the following example, I declare Pages each with a different TextField. As you switch between the pages, you can resume editing the TextField on that page exactly where you left it.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
StackView {
id: stackView
anchors.fill: parent
initialItem: "MenuPage.qml"
}
PageOne {
id: pageOne
visible: false
}
PageTwo {
id: pageTwo
visible: false
}
PageThree {
id: pageThree
visible: false
}
}
// MenuPage.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
header: Label { text: "MainPage" }
ColumnLayout {
Button {
text: qsTr("Page One")
onClicked: stackView.push(pageOne)
}
Button {
text: qsTr("Page Two")
onClicked: stackView.push(pageTwo)
}
Button {
text: qsTr("Page Three")
onClicked: stackView.push(pageThree)
}
}
}
// PageOne.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
header: Label { text: "Page One" }
ColumnLayout {
TextField {
text: "sheep"
}
Button {
text: qsTr("Back")
onClicked: stackView.pop()
}
}
}
// PageTwo.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
header: Label { text: "Page Two" }
ColumnLayout {
TextField {
text: "magic"
}
Button {
text: qsTr("Back")
onClicked: stackView.pop()
}
}
}
// PageThree.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
header: Label { text: "Page Three" }
ColumnLayout {
TextField {
text: "xyzzy"
}
Button {
text: qsTr("Back")
onClicked: stackView.pop()
}
}
}
You can Try it Online!

Qml split Gridview along a row

I am trying to create a QML gridview of rectangles(images) such that when I click on an element, the view split right below that element. I would lie to display some text in this split section that will then appear.
I would do that with GridLayout instead of GridView since it allows columns/rows spanning, for example:
import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Window 2.10
import QtQuick.Layouts 1.12
Window {
visible: true
width: 400
height: 600
title: qsTr("test")
Component {
id: commonItem
Rectangle {
anchors.fill: parent
color: "#DEDEDE"
Text { anchors.centerIn: parent; text: "Click me" }
}
}
Component {
id: selectedItem
Rectangle {
anchors.fill: parent
color: "#999"
Text { anchors.centerIn: parent; text: "I'm selected item" }
}
}
GridLayout {
anchors.centerIn: parent
columns: 3
columnSpacing: 2
rowSpacing: 2
Repeater {
model: 6
delegate: Item {
id: item
property int w: 100
property int loaderColumns: 1
property var component: undefined
Layout.columnSpan: loaderColumns
Layout.preferredWidth: w
Layout.preferredHeight: 100
state: "collapsed"
Loader {
id: loader
anchors.fill: parent
sourceComponent: item.component
}
states: [
State {
name: "collapsed"
PropertyChanges { target: item; component: commonItem }
},
State {
name: "expanded"
PropertyChanges { target: item; component: selectedItem; }
PropertyChanges { target: item; loaderColumns: 3; }
PropertyChanges { target: item; w: 304; }
}
]
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
item.state = (item.state == "collapsed") ? "expanded" : "collapsed"
}
}
Behavior on w {
NumberAnimation {
duration: 1000
easing {
type: Easing.OutElastic
amplitude: 1.0
period: 0.5
}
}
}
}
}
}
}

QML layout does not have correct height

I have a qml layout and the ApplicationWindow does not stretch to the correct height for scrolling to work.
Here is my code:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.LocalStorage 2.0
import QtQuick.Window 2.0
import "db.js" as DB
ApplicationWindow {
id: root
visible: true
width: Screen.width
title: "Nákupy"
menuBar: MenuBar {
Menu {
title: "Smazat"
MenuItem {
text: "&Smazat seznam"
onTriggered: {
console.log("Open action triggered");
}
}
}
}
Flickable {
id: flickable
anchors.fill: parent
contentHeight: column.height
contentWidth: root.width
Column {
anchors.fill: parent
id: column
Label {
id: welcometext
text: "Test"
horizontalAlignment: Text.AlignHCenter
y: 10
anchors.margins: 10
width: root.width
}
Repeater {
y: welcometext.top + welcometext.height + 10
id: repeater
model: lmodel
Button {
id: b
text: m_text
x: 20
width: root.width - 40
height: 70
onClicked: {
visible = false;
}
}
}
Button {
y: repeater.top + repeater.height + 30
width: root.width
text: "Přidat položku"
onClicked: {
console.log("clicked")
}
}
}
}
ListModel {
id: lmodel
}
Component.onCompleted: {
DB.open().transaction(function(tx){
tx.executeSql("CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, name TEXT, done INTEGER)");
});
console.log(column.height);
for(var i = 0; i < 10; i++) {
lmodel.append({m_text: "Test "+i});
}
console.log(column.height);
console.log(welcometext.height);
}
}
Column reports height 0.
Try using implicitHeight:
Defines the natural width or height of the Item if no width or height is specified.
The default implicit size for most items is 0x0, however some items have an inherent implicit size which cannot be overridden [...]
Setting the implicit size is useful for defining components that have a preferred size based on their content [...]

How to access the property of a GridView - or ListView - element

Here is the code, I create 4 buttons. When one is clicked I wanna that its color changes to red and the color of all the others change to black.
But looks like I could not access the color property.
Rectangle {
id: root
width: 200; height: 100
DelegateModel {
id: visualModel
model: ListModel {
ListElement { my_color: "red" }
ListElement { my_color: "black" }
ListElement { my_color: "black" }
ListElement { my_color: "black" }
}
groups: [
DelegateModelGroup { name: "selected" }
]
delegate: Rectangle {
id: item
height: 25
width: 200
color:my_color
MouseArea {
anchors.fill: parent
onClicked: {
console.log(visualModel.items.get(index).color)
for (var i = 0; i < root.count; i++){
if(index == i)
visualModel.items.get(i).color = "red";
else
visualModel.items.get(i).color = "black";
}
}
}
}
}
ListView {
anchors.fill: parent
model: visualModel
}
}
I advice you to use ExclusiveGroup from QML controls. Usually it is used for Action but it's possible to use it for any other Item. From the Qt docs:
It is possible to add support for ExclusiveGroup for an object or
control. It should have a checked property, and either a
checkedChanged, toggled(), or toggled(bool) signal.
So all we need is to add suitable property. Small example:
import QtQuick 2.5
import QtQuick.Window 2.0
import QtQuick.Controls 1.4
Window {
width: 200
height: 400
ExclusiveGroup { id: exclusiveGroup }
ListView {
anchors.fill: parent
anchors.margins: 5
spacing: 2
model: 10
delegate: Rectangle {
id: myItem
property bool checked: false // <-- this is necessary
height: 30
width: parent.width
color: myItem.checked ? "lightblue" : "#DEDEDE"
border { width: 1; color: "#999" }
radius: 5
Text { text: "item" + (index + 1); anchors.centerIn: parent}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: myItem.checked = !myItem.checked;
}
Component.onCompleted: {
exclusiveGroup.bindCheckable(myItem);
}
}
}
}

QT 5.3 / QML: Resizable StackView depending on currentItem

I'm working on a QML StackView that starts with a list of items to select from. Once selected I want to transition _.push(...) to a input form which has larger dimensions than the initialItem.
The only way I have trial-and-errored my way into a situation that works is by making the form Item a nested borderless window.
Q1. A nested window can't be the right type of concept to use for this... right ? there must be another way to do it. What is the right way ?
Q2. My goal after this is to have a transition animation that grows or shrinks between stacks of different sizes. Advice that doesn't preclude that would be best.
code
Main.qml :
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
ApplicationWindow {
property int itemHeight: 30
property int cornerRadius : 5
visible: true
color: "transparent"
flags: Qt.FramelessWindowHint
ListModel {
id: searchFacets
ListElement {
title: "Topics"
page: "content/TopicSearch.qml"
}
// ListElement {
// title: "Domains"
// }
}
StackView {
id: stackView
focus: true
initialItem: SearchFacets {
id: facets
}
delegate: StackViewDelegate {
pushTransition: StackViewTransition {
PropertyAnimation {
target: enterItem
property: "opacity"
from: 0
to: 1
}
}
}
}
}
Initial Item:
import QtQuick 2.3
Item {
height : listView.count * itemHeight
ListView {
id: listView
model: searchFacets
anchors.fill: parent
focus: true
highlightFollowsCurrentItem: false
highlight: Rectangle {
width: parent.width
height: itemHeight
radius : cornerRadius
color: "green"
opacity: 0.5
z:2
x: listView.currentItem.x;
y: listView.currentItem.y
Behavior on y {
SpringAnimation {
spring: 60
damping: 1.0
}
}
}
delegate: Component {
Item {
width: parent.width
height : itemHeight
Rectangle {
anchors.fill: parent
color: "#212126"
radius: cornerRadius
z:0
border.width: 2
border.color : "white"
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
console.log("clicked index: " + index)
listView.currentIndex = index
// listView.forceActiveFocus()
stackView.push(Qt.resolvedUrl(page))
}
}
Text {
text: title
font.pixelSize: 24
font.bold: true
z:1
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
color: "white"
antialiasing: true
}
}
}
}
}
Input Form:
import QtQuick 2.3
import QtQuick.Window 2.0
Item {
Window {
width: 400
height: 400
visible: true
flags: Qt.FramelessWindowHint
Rectangle {
anchors.fill: parent
visible: true
color: "red"
}
}
}
One possible solution is to update the size of the dimensions of the StackView in the click handler that causes the transition. I do not know if that causes any problems with animating the transition.
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
console.log("clicked index: " + index)
listView.currentIndex = index
var component = Qt.createComponent(page)
var res = component.createObject(stackView)
stackView.height = res.height
stackView.width = res.width
stackView.push(res)
}
}

Resources