【问题标题】:How to define a "template" with child placeholders in QML?如何在 QML 中定义带有子占位符的“模板”?
【发布时间】:2012-09-10 17:47:55
【问题描述】:

我真的很喜欢 QML。我喜欢如何定义组件(类似于类)及其属性,并从其他地方实例化它们(类似于对象)。

我可以定义,比如说,一个按钮,有一些外观和感觉,以及它上面的标签文本。例如,可以使用此组件定义 (Button.qml):

Item {
    id: button
    property string label

    anchors.fill: parent

    Rectangle {
        anchors.fill: parent
        radius: 10
        color: "gray"

        Text {
            anchors.centerIn: parent
            font.pixelSize: 20
            text: button.label
            color: "white"
        }
    }
}

并在这个主文件中实例化 (main.qml):

Rectangle {
    width: 300
    height: 200

    Button {
        anchors.centerIn: parent
        anchors.margins: 50
        label: "Hello button!"
    }
}

但我看到了以下限制:我只能定义一个按钮模板带有一些属性,而不是带有一些占位符。实例中定义的所有子代都将是直接子代,至少在默认情况下,我想更改此行为。

假设我想在按钮中放置一个项目(假设是一个图像,但我不想告诉Button 的定义它将是一个图像)。我想像这样:

Item {
    id: button
    property Item contents   <-- the client can set the placeholder content here

    anchors.fill: parent

    Rectangle {
        anchors.fill: parent
        radius: 10
        color: "gray"

        Item {
            id: placeholder     <-- where the placeholder should be inserted
        }
    }

    Component.onCompleted: {
        // move the contents into the placeholder...
    }
}

我怎样才能做到这一点?我不知道使用Component.onCompleted 是否正确。但是请注意,在我的情况下,内容之后永远不会改变(至少在我当前的应用程序设计中......)。

另外,我希望锚定在占位符内工作。例如,如果我将内容定义为 Text 元素,以它的父元素为中心(首先是模板本身)。然后我的代码将此 Text 实例移动到占位符中,然后父锚点应该是占位符项的那些,而不是模板项。

【问题讨论】:

    标签: qt qml qt-quick


    【解决方案1】:

    实际上,我所听到的正确答案是使用QML Loader 来完成您想要的。

    [话虽这么说;我实际上还没有尝试过,但它在我的近期尝试列表中,看起来相当简单]

    此外,请在 stackoverflow 中搜索其他 "QML Loader" 问题,因为有一个数字可以帮助您入门。

    【讨论】:

    • 谢谢,不知道 QML Loader 是什么以及它在这种情况下的帮助。我去看看。
    • 就像 Misch 的回答一样,您的解决方案不适用于锚定。示例:Loader { sourceComponent: Rectangle { color: "red"; anchors.fill: parent } }。 Rectangle 应该填充 Loader 的父级,但它没有。 (除非我明确设置宽度和高度,否则它是不可见的。)
    【解决方案2】:

    您可以使用以下代码移动项目(如果您想在占位符中支持多个项目):

    property list<Item> contents
    
    Component.onCompleted: {
        var contentItems = [];
        for(var i = 0; i < contents.length; ++i)
            contentItems.push(contents[i]);
        placeholder.children = contentItems;
    }
    

    请注意,您不必为 content 属性提供项目列表,因为 QML 引擎也将接受单个值用于列表属性。

    【讨论】:

    • 是的,这似乎是移动项目的正确代码 sn-p。它有效,但它不尊重锚定。 (例如,如果我提供带有anchors.centerIn: parent 的文本,结果看起来好像我没有设置锚属性。)
    • 我不知道如何“更新”锚属性(也许内容项仍然认为它们的父项是模板?)但是您可以调整它们的大小以填充内容区域。只需将它们的宽度和高度设置为占位符的大小即可。
    【解决方案3】:

    简而言之(展示想法):

    import QtQuick 1.1
    
    Item {
        width: 200
        height: 100
    
        //<-- the client can set the placeholder content here
        property Item contents: Rectangle {
            anchors.fill: parent
            anchors.margins: 25
            color: "red"
        }
    
        Rectangle {
            id: container
    
            anchors.fill: parent
            radius: 10
            color: "gray"
    
            //<-- where the placeholder should be inserted
        }
    
        Component.onCompleted: {
            contents.parent = container
        }
    }
    

    稍长的版本(支持内容重新分配):

    import QtQuick 1.1
    
    Item {
        width: 200
        height: 100
    
        //<-- the client can set the placeholder content here
        property Item contents: Rectangle {
            //anchors can be "presupplied", or set within the insertion code
            //anchors.fill: parent
            //anchors.margins: 25
            color: "red"
        }
    
        Rectangle {
            id: container
    
            anchors.fill: parent
            radius: 10
            color: "gray"
    
            //<-- where the placeholder should be inserted
            //Item {
            //    id: placeholder
            //}
        }
    
        //"__" means private by QML convention
        function __insertContents() {
            // move the contents into the placeholder...
            contents.parent = container
            contents.anchors.fill = container
            contents.anchors.margins = 25
        }
    
        onContentsChanged: {
            if (contents !== null)
                __insertContents()
        }
    
        Component.onCompleted: {
            __insertContents()
        }
    }
    

    希望这会有所帮助:)

    【讨论】:

    • 如果您有任何问题或其他要求,请随时提出(或要求更改):)
    • 感谢您提供此解决方案,但我自己添加了另一个答案,引用了我几天前发现的 Qt Developer Days 演示文稿中非常好的内容。
    • 是的,这是另一个技巧,但请注意,使用它 1)您只能静态添加项目(除非您直接操作 children 属性,这会破坏别名的初始目的)和 2 ) (一件小事)不能以这种方式添加继承 QtObject 的实例(如计时器)(因为 children 是 Item 的列表,而不是普通的 QtObjects)。此外,使用重设方法,您可以任意放置添加的项目(将每个项目放在自己的容器中,等等)。
    • 对于问题 2) 我认为有属性 resources 或类似名称。我不知道问题 1),感谢您的提示。但是在 QML 中,我们只有极少数情况下我们想要带走孩子,我猜,至少在我描述的特定情况下(当我们想让组件的“标签”变得更加动态而不仅仅是一个字符串时)
    • @leems 顺便说一句,孩子们的别名技巧很好地真正引导孩子们并以某种方式以编程方式设置它们(一个典型的例子是屏幕堆栈:您以声明方式设置屏幕 - 作为孩子,并且item 正确设置它们并开始管理它们(如果屏幕实际上将页脚/页眉/等部分公开为 QML 属性,并且简单的重新父级是不够的)。
    【解决方案4】:

    我在 2011 年 Qt 开发者日"Qt Quick Best Practices and Design Patterns" 的演示文稿中找到了这个问题的更好答案。

    他们使用default property alias ... 将子项别名到任何项的任何属性。如果您不想为 children 设置别名,而是为 alias 属性命名,只需删除 default。 (文字子项是每个 QML 定义的默认属性的值。)

    Item {
        id: button
        default property alias contents: placeholder.children
    
        anchors.fill: parent
    
        Rectangle {
            anchors.fill: parent
            radius: 10
            color: "gray"
    
            Item {
                id: placeholder     <-- where the placeholder should be inserted
            }
        }
    }
    

    【讨论】:

    • 顺便说一句,如果您仍然想知道谈话中的 QML 范围难题,请参阅:stackoverflow.com/questions/11339719/qml-component-scope-puzzle
    • 太棒了,感谢您提供此链接! ;) 顺便说一下,“d-pointer”(也在本次演讲中)可以用来更好地隐藏私有成员(也是 JS 函数)。我发现它比双下划线更好。我什至将其命名为“priv”,以确保没有人会意外访问此子对象的成员。
    • 有趣,不记得那个 (d-ptr),所以这是某种 QML/js pimpl?
    • 以这种方式将 Connections QML 对象添加到模板会给您一个错误:“无法将对象分配给列表属性“内容” - 如何处理?
    • @MarcVanDaele 谢谢-是的,我最终发现并添加了答案:) 未来的谷歌员工:请参阅下面的答案
    【解决方案5】:

    Necro 会回答,以防其他人像我一样来到这里。

    在 Qt5 的某个地方,默认属性变为“data”而不是“children”。 这使得添加“项目”以外的其他对象类型成为可能。 例如也可以添加连接(在上面回答我自己的问题)

    所以在 Qt5 中你应该这样做:

    Item {
        id: button
        default property alias contents: placeholder.data
    
        anchors.fill: parent
    
        Rectangle {
            anchors.fill: parent
            radius: 10
            color: "gray"
    
            Item {
                id: placeholder     <-- where the placeholder should be inserted
            }
        }
    }
    

    注意: placeholder.data 而不是 placeholder.children 另请注意,您不必使用别名contents - 这可以是您喜欢的任何名称。一个例子:

    Item {
        id: button
        default property alias foo: placeholder.data
        ...
    
    }
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-14
    • 1970-01-01
    • 2018-07-18
    • 1970-01-01
    • 2021-01-12
    • 1970-01-01
    • 2020-09-18
    • 2013-10-18
    相关资源
    最近更新 更多