如何绑定到转发器外部的转发器生成项目的 属性?

How to bind to a property of a repeater-generated item outside of the repeater?

我希望能够绑定到 Repeater 生成的项目的 属性 以对其进行处理,例如显示它的坐标。为此,我使用 itemAt() 这样的:

ListModel {
    id: modelNodes

    ListElement { name: "Banana"; x: 100; y: 200 }
    ListElement { name: "Orange"; x: 150; y: 100 }
}

Repeater {
    id: foo
    model: modelNodes

    Rectangle {
        x: model.x; y: model.y
        width: textBox.implicitWidth + 20
        height: textBox.implicitHeight + 20
        color: "red"

        Drag.active: true

        Text {
            id: textBox
            anchors.centerIn: parent
            color: "white"
            text: model.name + ": " + foo.itemAt(index).x
        }

        MouseArea {
            anchors.fill: parent
            drag.target: parent
        }
    }
}

Text {
    id: moo

    Binding {
        target: moo
        property: "text"
        value: foo.itemAt(0).x + " -> " + foo.itemAt(1).x
    }
}

在委托内部这工作正常,但是当我尝试在 Repeater 外部使用它时(即将 moo 的文本绑定到它),我收到以下错误:

TypeError: Cannot read property 'x' of null

如何解决这个问题?

Binding 对象在 Repeater 之外不起作用的原因是因为在计算绑定时 Repeater 尚未构建其项目。要解决此问题,您可以将绑定移动到 Component.onCompleted 处理程序中。然后只需使用 Qt.binding() 函数从 javascript (docs).

进行绑定
Text { 
    Component.onCompleted: { 
        text = Qt.binding(function() { return foo.itemAt(0).x + ", " + foo.itemAt(1).x }) 
    }
}

你没有。
(或者更准确地说,你不应该)

代表不应该存储状态或数据,只是显示它或能够与之交互。 在您的情况下,您所追求的是存储在模型中的数据。

您的解决方案应该是在您的委托中修改您的模型,并根据需要从您的模型中获取数据。

我创建了一个小例子来说明我的意思:

import QtQuick 2.15
import QtQuick.Window 2.12
import QtQuick.Controls 2.12

Window {
    visible: true
    width: 800
    height: 640

    ListModel {
        id: modelNodes

        ListElement { name: "Banana"; x: 50; y: 50 }
        ListElement { name: "Orange"; x: 50; y: 100 }
    }

    Row {
        anchors.centerIn: parent
        spacing: 1
        Repeater {
            model: 2 // display 2 copy of the delegates for demonstration purposes

            Rectangle {
                color: "transparent"
                width: 300
                height: 300
                border.width: 1

                Repeater {
                    id: foo
                    model: modelNodes

                    Rectangle {
                        x: model.x; y: model.y
                        width: textBox.implicitWidth + 20
                        height: textBox.implicitHeight + 20
                        color: "red"

                        DragHandler {
                            dragThreshold: 0
                        }

                        onXChanged: model.x = x // modify model data when dragging
                        onYChanged: model.y = y

                        Text {
                            id: textBox
                            anchors.centerIn: parent
                            color: "white"
                            text: model.name + ": " + foo.itemAt(index).x
                        }
                    }
                }
            }
        }
    }

    Instantiator {
        model: modelNodes
        delegate: Binding { // the hacky solution to the initial problem.
            target: myText
            property: model.name.toLowerCase() + "Point"
            value: Qt.point(model.x, model.y)
        }
    }

    Text {
        id: myText
        property point bananaPoint
        property point orangePoint
        anchors.right: parent.right
        text: JSON.stringify(bananaPoint)
    }


    ListView {
        anchors.fill: parent
        model: modelNodes
        delegate: Text {
            text: `${model.name} - (${model.x} - ${model.y})`
        }
    }

}

我已经使用了一个 hacky 解决方案来解决你最初的问题 Instantiator of Bindings,我不太了解用例,所以这可能不是理想的解决方案。在这里,它为模型的每个元素创建了一个绑定,但这很奇怪。如果您只需要第一行的数据,您可能需要在 Binding 中执行 when: index === 0。我创建了第三方库以获得更清晰的代码:https://github.com/okcerg/qmlmodelhelper

这将为您的外部生成以下代码 Text(并允许您摆脱奇怪的 Instantiator + Binding 部分):

Text {
    readonly property var firstRowData: modelNodes.ModelHelper.map(0)
    text: firstRowData.x + ", " + firstRowData.y
}

请注意,我关于不在委托中存储数据(或从外部访问它们)的观点仍然代表您选择的任何解决方案。