wsys
wsys

Reputation: 385

Qt5 QML - Set component of Loader as an argument problem

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.

Upvotes: 0

Views: 191

Answers (2)

Stephen Quan
Stephen Quan

Reputation: 26309

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!

Upvotes: 1

wsys
wsys

Reputation: 385

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.

Upvotes: 0

Related Questions