Search code examples
qtqmlpyside6

Failed to Measure Children for Dynamically Created QML Object


Basically, I was trying to recreate IconLabel with some of my own classes in QML. However, it worked, in a limited sense:

import QtQuick 2.15
import QtQuick.Controls.Material 2.15
import QtQuick.Layouts 1.15

Item {
    id: root

    property int spacing: 8
    property string direction: "left"

    property string icon: ""
    property bool is_img: false
    property int icon_size: 24
    property int font_size: 14
    property string text: ""
    property string font: "Roboto"
    property color color: "black"
    property color icon_color: "black"
    property color text_color: "black"


    Component.onCompleted: {
        var cmd = `
        import QtQuick 2.15
        import QtQuick.Controls.Material 2.15
        import QtQuick.Layouts 1.15
        `;
        var isHorizontal = direction === "left" || direction === "right";
        var alignmentCmd = `Layout.alignment: ${isHorizontal ? "Qt.AlignVCenter" : "Qt.AlignHCenter"}`;

        var iconCmd = "";
        if (icon_color === "black")
            icon_color = color;
        if (text_color === "black")
            text_color = color;

        if (root.icon !== "") {
            if (is_img) {
                iconCmd = `MImage {
                    source: "${icon}"
                    Layout.alignment: ${isHorizontal ? "Qt.AlignVCenter" : "Qt.AlignHCenter"}
                    Layout.fillWidth: true
                    Layout.fillHeight: true
                    fillMode: Image.PreserveAspectCrop
                    width: ${icon_size}
                    height: ${icon_size}
                }`;
            } else {
                iconCmd = `MIcon {
                    icon: root.icon
                    color: root.icon_color
                    ${alignmentCmd}
                    width: ${icon_size}
                    height: ${icon_size}
                }`;
            }
        }
        var textCmd = `
        Text {
            text: root.text
            font.pixelSize: root.font_size
            font.family: root.font
            color: root.text_color
            ${alignmentCmd}
        }
        `;
        var componentCmd = direction === "left" || direction === "top" ? iconCmd + textCmd : textCmd + iconCmd;
        cmd += `
        ${isHorizontal ? "RowLayout" : "ColumnLayout"} {
            spacing: ${isHorizontal ? "root.spacing" : "0"}
            ${componentCmd}
        }`;

        Qt.createQmlObject(cmd, root, "dynamicComponent");
    }
}

It does do everything that I want if I used it alone, like Shown a MWE

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Dialogs
import QtQuick.Controls.Material 2.15
import "components"

ApplicationWindow {
    id: root
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    MIconLabel {
        icon_color: Material.color(Material.Red, Material.Shade500)
        direction: "right"
        icon: "home"
        text: "Home"
    }
    MIconLabel {
        icon_color: "black"
        icon: "menu"
        text: "Menu"
        y: 100
    }
}

But for some reason, it rejects to cooperate with RowLayout or anything of this nature.

Failure Case

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Dialogs
import QtQuick.Controls.Material 2.15
import "components"

ApplicationWindow {
    id: root
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    ColumnLayout {
        MIconLabel {
            icon_color: Material.color(Material.Red, Material.Shade500)
            direction: "right"
            icon: "home"
            text: "Home"
        }
        MIconLabel {
            icon_color: "black"
            icon: "menu"
            text: "Menu"
        }
   }
}

Hence, I ask if there is some way that I can make the parent detect the newly created objects (like refresh or remeasure methods?). I tried to resize the window in hope of repainting the event, but nothing good happens. I also used console.log at the end of the initialization method to print out the dimension of the newly created object, and the dimension of the root object, and the following expression evaluates to True: root.width == object.width == root.childrenRect.width and root.height == object.height == root.childrenRect.height.

Just to explain some details here: MIcon is a wrapper of Text class with the font.family pre-declared as Material Icons. And MImage is basically an wrapper around Image with rounded corners and a placeholder, if no image is provided.


Solution

  • It took sometime to read your code. My reading of your code, I get the feeling it is (1) brutal use of createQmlObject() and I would recommend finding another way to do the same thing. For example, are you aware that most components have an icon.source property with icon.color ? For instance

    ItemDelegate {
        icon.source: "..."
        icon.color: "..."
    }
    

    I tried to recreate your UI with:

    • ItemDelegate to render both text and icon
    • icon.source and icon.color to colorize an SVG
    • LayoutMirroring to implement both left-to-right and right-to-left patterns
    • Added Frame component to visually check that the dimensions of the attempt

    Here's the complete working example:

    import QtQuick
    import QtQuick.Controls
    import QtQuick.Layouts
    Page {
        ColumnLayout {
            width: parent.width
            Frame {
                ItemDelegate {
                    text: "Home"
                    icon.source: "home-32.svg"
                    icon.color: "red"
                    LayoutMirroring.enabled: true
                }
            }
            Frame {
                ItemDelegate {
                    text: "Menu"
                    icon.source: "hamburger-32.svg"
                    icon.color: "black"
                }
            }
        }
    }
    
    //home-32.svg
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M11 8.5V5H6v8.5L2.5 17H5v12h8v-8h6v8h8V17h2.5L16 3.5zM26 16v12h-6v-8h-8v8H6V16H4.914L7 13.914V6h3v4.914l6-6L27.086 16z"/><path fill="none" d="M0 0h32v32H0z"/></svg>
    
    //hamburger-32.svg
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M28 7H4V6h24zm0 9H4v1h24zm0 10H4v1h24z"/><path fill="none" d="M0 0h32v32H0z"/></svg>
    

    You can Try it Online!