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.
I have a QML (Felgo) application and I would like to pass parameter to the previous page of the stack in navigationStack when popping. I would like to populate "myVar" property of Page2.qml (which is '0' by default) with the value '1' passed from Page3 when I call pop() in the Page3.qml. The example code is shown below. I tried that code but it doesn't work. What am I doing wrong? Thanks for all.
main.qml
Page1.qml
Page2.qml
Page3.qml
I created the following code snippet that has the following features:
Main.qml hosts a ListModel
Page1.qml is able to use the ListModel in a ListView since it sees because Page1.qml was pushed into Main.qml's StackView and has inherits access to everything in Main.qml
Page2.qml does nothing except provide navigation to either Page1.qml or Page3.qml
Page3.qml provides navigation back to Page2.qml as well as emitting a signal to Main.qml receives to update the ListModel
Page1.qml, Page2.qml and Page3.qml all emit signals which are handled in Main.qml
SVG graphics are supplied to polish the example
Here's the code snippet:
import QtQuick 2.15
import QtQuick.Controls 2.15
Page {
StackView {
id: stackView
anchors.fill: parent
initialItem: comp1
}
ListModel {
id: listModel
}
Component {
id: comp1
Page1 {
onGoForward: stackView.push(comp2)
}
}
Component {
id: comp2
Page2 {
onGoBack: stackView.pop()
onGoForward: stackView.push(comp3)
}
}
Component {
id: comp3
Page3 {
onGoBack: stackView.pop()
onNewNumber: number => listModel.append( { number } )
}
}
}
//Page1.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Page {
signal goForward()
header: Frame {
background: Rectangle { color: "#ccc" }
RowLayout {
width: parent.width
AppIconButton {
icon.source: "shapes-32.svg"
}
Text {
Layout.fillWidth: true
text: "Page1"
}
}
}
ListView {
anchors.fill: parent
model: listModel
delegate: Frame {
width: ListView.view.width
Text { text: number }
}
}
footer: Frame {
background: Rectangle { color: "#ccc" }
RowLayout {
width: parent.width
Button {
text: qsTr("Go Forward")
onClicked: goForward()
}
}
}
}
//Page2.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Page {
signal goForward()
signal goBack()
header: Frame {
background: Rectangle { color: "#ccc" }
RowLayout {
width: parent.width
AppIconButton {
icon.source: "chevron-left-32.svg"
onClicked: goBack()
}
AppIconButton {
icon.source: "shapes-32.svg"
}
Text {
Layout.fillWidth: true
text: "Page2"
}
}
}
footer: Frame {
background: Rectangle { color: "#ccc" }
RowLayout {
width: parent.width
Button {
text: qsTr("Go Forward")
onClicked: goForward()
}
}
}
}
//Page3.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Page {
signal newNumber(var number)
signal goBack()
header: Frame {
background: Rectangle { color: "#ccc" }
RowLayout {
width: parent.width
AppIconButton {
icon.source: "chevron-left-32.svg"
onClicked: goBack()
}
AppIconButton {
icon.source: "shapes-32.svg"
}
Text {
Layout.fillWidth: true
text: "Page3"
}
}
}
Text {
id: resultText
anchors.centerIn: parent
}
footer: Frame {
background: Rectangle { color: "#ccc" }
RowLayout {
width: parent.width
Button {
text: qsTr("New Number")
onClicked: {
let number = Math.random();
resultText.text = number;
newNumber(number);
}
}
}
}
}
//AppIconButton.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Item {
property alias icon: btn.icon
Layout.preferredWidth: 32
Layout.preferredHeight: 32
clip: true
signal clicked()
Button {
id: btn
anchors.centerIn: parent
background: Item { }
icon.width: parent.width
icon.height: parent.height
onClicked: parent.clicked()
}
}
//shapes-32.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M8.45 18.367l-.564.855A8.795 8.795 0 1 1 18.832 7h-1.143A7.795 7.795 0 1 0 8.45 18.367zM14 8v6.429l1 1.53V9h14v14h-9.399l.654 1H30V8zm8.982 22h-21l10.544-16zm-10.46-14.177L3.838 29h17.295z"/><path fill="none" d="M0 0h32v32H0z"/></svg>
//chevron-left-32.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M18.793 25l-9-9 9-9h1.414l-9 9 9 9z"/><path fill="none" d="M0 0h32v32H0z"/></svg>
You can Try it Online!
I have a QML page with a GridLayout that contains the page title, ListView and close button:
GridLayout {
columns: 1
rows: 5
anchors.fill: parent
<page title item>....
ListView
{
id: list
spacing: 15
model: logModel
Layout.fillWidth: true
Layout.fillHeight: true
delegate: Item {
implicitWidth: parent.width
implicitHeight: grid.height
RowLayout
{
id: grid
spacing: 0
width: parent.width
height: commentLabel.implicitHeight
<icon>....
Label {
id: commentLabel
Layout.fillWidth: true
text: comment
wrapMode: Label.Wrap
}
}
}
ScrollIndicator.vertical: ScrollIndicator { }
}
<close button>...
}
Component.onCompleted:
{
console.log("list.contentHeight = ", list.contentHeight, "list.height = ", list.height)
}
when I bind ListView to a model (logModel) that contains items with comment property that contains relatively long multiline text and check contentHeight property of ListView from Component.onCompleted I get the value 161 that is obviously too small (actually it should be greater than 500, because ListView height property value is 461 and it is not enough for all the items).
So I cannot figure out what this 161 is. The only guess is that it is something close to the content height with single-line items (not multiline), but it is not clear what is the logic behind that.
My QT version is 5.13.2.
EDIT1:
The full source code:
ChangeLogPage.qml:
import QtQuick 2.7
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import QtQuick.Controls.Styles 1.4
Page
{
id: root
property alias model: list.model
background: Rectangle {
color: "transparent"
}
function close()
{
stack.pop()
}
SystemPalette {
id: palette
}
GroupBox
{
id: box
background: Rectangle {
color: palette.base
}
anchors.margins: 15
anchors.fill: parent
GridLayout {
columns: 1
rows: 5
anchors.fill: parent
ListView
{
id: list
Layout.fillWidth: true
Layout.fillHeight: true
//It is not clear why, but there is an enough left margin.
Layout.leftMargin: 0
Layout.rightMargin: 0
Layout.topMargin: 15
Layout.bottomMargin: 20
//Looks like it is only vertical spacing.
spacing: 15
clip: true
delegate: Item {
width: parent.width
height: grid.height
RowLayout
{
id: grid
spacing: 0
width: parent.width
//height: commentLabel.height
Label {
//id: commentLabel
Layout.fillWidth: true
text: comment //qsTr(comment)
wrapMode: Label.Wrap
}
}
}
ScrollIndicator.vertical: ScrollIndicator { }
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("Close")
onClicked: close()
}
}
}
Component.onCompleted:
{
console.log("list.contentHeight = ", list.contentHeight, "list.height = ", list.height)
}
}
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2
import LinesGame 1.0
ApplicationWindow {
id: window
visible: true
width: 360
height: 640
title: "Test"
Component {
id: listModelComponent
ListModel {
id: model
}
}
StackView
{
anchors.fill: parent
id: stack
}
function showChangeLogPage(beginIndex, endIndex)
{
if (beginIndex < endIndex)
{
//var component = Qt.createComponent("ChangeLogModel.qml")
//var logModel = component.createObject(this)
var logModel = listModelComponent.createObject(this);
for (var i = beginIndex; i < endIndex; i++)
{
//i.toString()
logModel.append({comment: qsTr("The advertising was added, but it is shown only when you start a new game or load a saved game. Support the developers, tap on the advertising!")})
}
stack.push(Qt.resolvedUrl("ChangeLogPage.qml"), {model: logModel})
}
}
Component.onCompleted:
{
showChangeLogPage(0, 5)
}
}
I am attempting a simple RowLayout of Rectangles. (See code below.) When I attempt to compile/run this in Qt Creator, I get:
qrc:/main.qml:31 Do not create objects of type Layout
when I attempt to use either Layout.minimumWidth:200 or Layout { minimumWidth:200 }
The Qt documentation for RowLayout shows the first form working. What am I missing?
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
id: window
title: "RB3Jay"
width:1280; height:960
minimumWidth:600; minimumHeight:300
visible: true
Rectangle {
id: pseudocontent
height: parent.height - (header.height + footer.height)
color:'orange'
anchors {
top:header.bottom
bottom:footer.top
left:parent.left
right:parent.right
}
}
header: RowLayout {
id: header
spacing: 0
height: 100
width: parent.width
Rectangle {
color:'red'
Layout {
minimumWidth:200; maximumWidth:200; preferredWidth:200
fillHeight:true
}
}
Rectangle {
color:'green'
Layout {
minimumWidth: 200
preferredWidth: parent.width*0.7
fillWidth:true; fillHeight:true
}
}
Rectangle {
color:'blue'
Layout {
minimumWidth: 200
preferredWidth: parent.width*0.3
fillWidth:true; fillHeight:true
}
}
}
footer: Inspector {
id: footer
height:100
}
}
While the foo { bar: 1; baz: 2 } -syntax works for grouped properties, Foo { } is reserved for creating an instance of QML type Foo. For attached properties, you must use the Foo.bar: 1 -syntax.
Layout is not a creatable type, it only provides attached properties. Therefore you must use the Foo.bar: 1 -syntax.
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
id: window
title: "RB3Jay"
width:1280; height:960
minimumWidth:600; minimumHeight:300
visible: true
Rectangle {
id: pseudocontent
height: parent.height - (header.height + footer.height)
color:'orange'
anchors {
top:header.bottom
bottom:footer.top
left:parent.left
right:parent.right
}
}
header: RowLayout {
id: header
spacing: 0
height: 100
width: parent.width
Rectangle {
color:'red'
Layout.minimumWidth:200
Layout.maximumWidth:200
Layout.preferredWidth:200
Layout.fillHeight:true
}
Rectangle {
color:'green'
Layout.minimumWidth: 200
Layout.preferredWidth: parent.width*0.7
Layout.fillWidth:true; Layout.fillHeight:true
}
Rectangle {
color:'blue'
Layout.minimumWidth: 200
Layout.preferredWidth: parent.width*0.3
Layout.fillWidth:true; Layout.fillHeight:true
}
}
footer: Inspector {
id: footer
height:100
}
}
I have a custom Footer Component which I would like to reuse in different place in my QML App:
Rectangle {
color: "gold"
height: 50
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
RowLayout {
anchors.fill: parent
anchors.margins: 10
Button {
text: "quit"
}
}
}
The use of this is easy:
Window {
visible: true
Footer {
}
}
But now I would like to add a "ButtonA" to the RowLayout of my Footer in one view and a "ButtonB" in another view.
How can I achieve that?
See this answer.
You have to declare a default property in Footer.qml:
import QtQuick 2.0
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
Rectangle {
color: "gold"
height: 50
default property alias content: rowLayout.children
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.margins: 10
Button {
text: "quit"
}
}
}
This ensures that any items declared as children of Footer instances will be added to its RowLayout.
main.qml:
import QtQuick 2.4
import QtQuick.Controls 1.3
ApplicationWindow {
width: 640
height: 480
visible: true
StackView {
id: stackView
anchors.fill: parent
initialItem: viewAComponent
}
Component {
id: viewAComponent
Rectangle {
id: viewA
color: "salmon"
Footer {
id: footerA
Button {
text: "Go to next view"
onClicked: stackView.push(viewBComponent)
}
}
}
}
Component {
id: viewBComponent
Rectangle {
id: viewB
color: "lightblue"
Footer {
id: footerB
Button {
text: "Go to previous view"
onClicked: stackView.pop()
}
}
}
}
}
I used StackView as a convenient way of navigating between the views.