更改模型后 QML Repeater 未正确绘制

QML Repeater is not drawn correctly after model is changed

我正在编写一个 QML 应用程序以在 blue 矩形内绘制 brown 块。该应用程序使用 ColumnLayoutRepeater 来执行此任务以绘制任意数量的块(默认为 4 个):

我正在尝试在用户单击屏幕以强制 UI 绘制不同数量的块时动态更改 Repeatermodel。每当通过 blockCount 更改所需的块数时,它会触发重新计算 blockHeight,每个块的高度,以便较小数量的块可以在屏幕上占据更多 space .至少理论上是这样!

出于调试目的,单击屏幕将 blockCount 设置为 2

这是一张示例图片,左侧是预期结果,右侧是当前结果

正如您在上图中所见,当点击发生并执行 rectId.blockCount = 2 时,它似乎触发了一系列调用:

  • 在重新计算 blockHeight 之前更改了 Repeater 的模型;
  • 或者 ColumnLayoutanchors 被重置,出于某种奇怪的原因;
  • 或者其他事情正在发生;

我试图了解是什么导致了这种行为,并且还在寻找一种方法,允许应用程序动态更改块的数量,同时能够正确绘制它们!

我错过了什么?

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.15

Window {
    id: wndId
    property int wndWidth: 200
    property int wndHeight: 300

    visible: true
    width: wndWidth
    height: wndHeight
    title: qsTr("Testing ColumnLayout")

    Rectangle {
        id: rectId

        property int borderWidth: 5  // width of the blue frame surrounding the window
        property int blockCount: 4   // number of blocks to be drawn using Repeater
        property int blocksSpace: 8  // minimum space between the blocks

        width: wndId.wndWidth
        height: wndId.wndHeight
        border.color: "blue"
        border.width: borderWidth

        // size of each inner rectangle is computed dinamically: changing blockCount should update blockHeight
        property int blockWidth: rectId.width - (rectId.borderWidth * 4)
        property int blockHeight: updateBlockHeight()

        function updateBlockHeight(numBlocks)
        {
            if (numBlocks === undefined)
                numBlocks = rectId.blockCount;

            var newHeight = (rectId.height - ((rectId.borderWidth + rectId.blocksSpace)*2) - (rectId.blocksSpace * (numBlocks-1))) / numBlocks;
            print("updateBlockHeight: newHeight=", newHeight);
            return newHeight;
        }

        Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)

        // draw blocks on top of each other with some space between them
        ColumnLayout {
            spacing: rectId.blocksSpace
            Layout.alignment: Qt.AlignBottom
            anchors.bottom: parent.bottom
            anchors.bottomMargin: rectId.borderWidth + rectId.blocksSpace
            anchors.left: rectId.left
            anchors.leftMargin: rectId.borderWidth*2

            Repeater {
                id: repId
                model: rectId.blockCount

                // each block size is calculated dinamically
                Rectangle {
                    id: blockId
                    color: "brown"
                    width: rectId.blockWidth
                    height: rectId.blockHeight

                    // Debug:
                    Component.onCompleted: {
                        print("Inner Rectangle")
                        print("  blockCount=" + rectId.blockCount);
                        print("  blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
                        print("  blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
                    }

                    Component.onDestruction: print("~Inner Rectangle")

                } // inner Rectangle

                Component.onCompleted: print("Repeater")
                Component.onDestruction: print("~Repeater")
            } // Repeater

        } // ColumnLayout

        MouseArea {
            anchors.fill: parent
            onClicked: {
                print("Mouse clicked!");

                // since repId uses blockCount as the model, any change to it should automatically recreate the elements of the Repeater
                // here we force blockHeight to be recalculated before the model is changed
                rectId.blockHeight = rectId.updateBlockHeight(2)

                // and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
                rectId.blockCount = 2;
                print("blockHeight= " + rectId.blockHeight);
            }
        }

    } // outter Rectangle

} // Window

你为什么要计算 blockHeightblockWidth 而你可以利用 ColumnLayout 的力量?

使用 Layout.fillWidthLayout.fillHeight 属性向 ColumnLayout 发出信号,即块应填满整个宽度和高度,均匀分布。然后将正确的大小设置为 ColumnLayout,它将执行您尝试自行编程的计算。

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.3

Window {
    id: wndId
    property int wndWidth: 200
    property int wndHeight: 300

    visible: true
    width: wndWidth
    height: wndHeight
    title: qsTr("Testing ColumnLayout")

    Rectangle {
        id: rectId

        property int borderWidth: 5  // width of the blue frame surrounding the window
        property int blockCount: 4   // number of blocks to be drawn using Repeater
        property int blocksSpace: 8  // minimum space between the blocks

        width: wndId.wndWidth
        height: wndId.wndHeight
        border.color: "blue"
        border.width: borderWidth

        Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)

        // draw blocks on top of each other with some space between them
        ColumnLayout {
            spacing: rectId.blocksSpace
            Layout.alignment: Qt.AlignBottom

            anchors.fill: parent
            anchors.margins: rectId.borderWidth * 2

            Repeater {
                id: repId
                model: rectId.blockCount

                // each block size is calculated dinamically
                Rectangle {
                    id: blockId
                    color: "brown"
                    Layout.fillWidth: true
                    Layout.fillHeight: true

                    // Debug:
                    Component.onCompleted: {
                        print("Inner Rectangle", index)
                        print("  blockCount=" + rectId.blockCount);
                        print("  blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
                        print("  blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
                    }

                    Component.onDestruction: print("~Inner Rectangle")

                } // inner Rectangle

                Component.onCompleted: print("Repeater")
                Component.onDestruction: print("~Repeater")
            } // Repeater

        } // ColumnLayout

        MouseArea {
            anchors.fill: parent
            onClicked: {
                print("Mouse clicked!");

                // and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
                rectId.blockCount = 2;
                print("blockHeight= " + rectId.blockHeight);
            }
        }

    } // outter Rectangle

} // Window

EDIT 用于保持 blockHeight 计算

如果你坚持保留计算,因为它在现实世界中更难(公平),我建议使用 implicitHeightimplicitWidth。这是有效的,因为布局引擎不会触发 width/height 上的更改,因为它应该自己设置这些,但是它会监视隐式大小:

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.3

Window {
    id: wndId
    property int wndWidth: 200
    property int wndHeight: 300

    visible: true
    width: wndWidth
    height: wndHeight
    title: qsTr("Testing ColumnLayout")

    Rectangle {
        id: rectId

        property int borderWidth: 5  // width of the blue frame surrounding the window
        property int blockCount: 4   // number of blocks to be drawn using Repeater
        property int blocksSpace: 8  // minimum space between the blocks

        width: wndId.wndWidth
        height: wndId.wndHeight
        border.color: "blue"
        border.width: borderWidth

        // size of each inner rectangle is computed dinamically: changing blockCount should update blockHeight
        property int blockWidth: rectId.width - (rectId.borderWidth * 4)
        property int blockHeight: updateBlockHeight()

        function updateBlockHeight(numBlocks)
        {
            var newHeight = (rectId.height - ((rectId.borderWidth + rectId.blocksSpace)*2) - (rectId.blocksSpace * (rectId.blockCount-1))) / rectId.blockCount;
            print("updateBlockHeight: newHeight=", newHeight);
            return newHeight;
        }

        Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)

        // draw blocks on top of each other with some space between them
        ColumnLayout {
            spacing: rectId.blocksSpace
            Layout.alignment: Qt.AlignBottom
            anchors.fill: parent
            anchors.margins: rectId.borderWidth * 2

            Repeater {
                id: repId
                model: rectId.blockCount

                // each block size is calculated dinamically
                Rectangle {
                    id: blockId
                    color: "brown"
                    implicitWidth: rectId.blockWidth
                    implicitHeight: rectId.blockHeight
                    onXChanged: print("x[",index,"]=", x)
                    onYChanged: print("y[",index,"]=", y)

                    // Debug:
                    Component.onCompleted: {
                        print("Inner Rectangle", index)
                        print("  blockCount=" + rectId.blockCount);
                        print("  blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
                        print("  blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
                    }

                    Component.onDestruction: print("~Inner Rectangle")

                } // inner Rectangle

                Component.onCompleted: print("Repeater")
                Component.onDestruction: print("~Repeater")
            } // Repeater

        } // ColumnLayout

        MouseArea {
            anchors.fill: parent
            onClicked: {
                print("Mouse clicked!");

                // and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
                rectId.blockCount = 2;
                print("blockHeight= " + rectId.blockHeight);
            }
        }

    } // outter Rectangle

} // Window

此外,我重构了 updateBlockHeight 函数,不需要显式设置它,QML 引擎非常智能,它甚至会在函数中的一个参数发生变化时重新计算绑定!

(来自我的评论)

在 Linux 上使用 Qt 5.15.1 获得预期的输出。使用 Qt 5.15 获取您指出的输出可能是 Qt 5.15.1 中已修复的错误。