更改模型后未正确绘制 QML 中继器

Posted

技术标签:

【中文标题】更改模型后未正确绘制 QML 中继器【英文标题】:QML Repeater is not drawn correctly after model is changed 【发布时间】:2021-01-28 07:32:14 【问题描述】:

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

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

出于调试目的,单击屏幕会将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. blockId.  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

【问题讨论】:

在 Linux 上使用 Qt 5.15.1 可以获得预期的输出。使用 Qt 5.15 获得您指出的可能是 Qt 5.15.1 中已修复的错误的输出 @eyllanesc Windows 上的 Qt 5.15 出现了问题。升级到 Qt 5.15.1 解决了这个问题。请添加答案,以便我接受。 @eyllanesc 你愿意吗? 看我的回答... 【参考方案1】:

您为什么要计算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. blockId.  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。这是有效的,因为布局引擎不会触发宽度/高度的变化,因为它应该自己设置这些,但是它会监控隐式大小:

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. blockId.  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引擎非常聪明,它甚至会在函数中的一个参数发生变化时重新评估绑定!

【讨论】:

感谢您的提示。这是我面临的问题的一个可重复的例子。在实际应用中,这些计算略有不同,ColumnLayout 将没有用处。 @karlphillip 我更新了答案以符合您的要求 太棒了!如果可以的话,我会再次投票。这个问题确实是 Qt 5.15 上的一个错误,升级到 Qt 5.15.1 解决了它。最后,我的演示没有任何问题,但我真的很喜欢你的替代解决方案。人,这个答案值得更多的支持!【参考方案2】:

(来自我的 cmets)

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

【讨论】:

以上是关于更改模型后未正确绘制 QML 中继器的主要内容,如果未能解决你的问题,请参考以下文章

QML 使用中继器和委托,显示旧列表

列数可变的 QML 中继器和 QML 网格布局

中继器内的 QML ListView

QML ListElement 传递字符串列表

QML在被MouseArea拖动时无法更改项目的位置

具有“填充”过渡的 QML 中继器