更改模型后未正确绘制 QML 中继器
Posted
技术标签:
【中文标题】更改模型后未正确绘制 QML 中继器【英文标题】:QML Repeater is not drawn correctly after model is changed 【发布时间】:2021-01-28 07:32:14 【问题描述】:我正在编写一个 QML 应用程序来在 blue 矩形内绘制 brown 块。应用程序使用ColumnLayout
和Repeater
执行此任务以绘制任意数量的块(默认为4):
当用户单击屏幕以强制 UI 绘制不同数量的块时,我正在尝试动态更改 Repeater
的 model
。每当通过blockCount
更改所需的块数时,都会触发重新计算每个块的高度blockHeight
,这样更少量的块可以在屏幕上占据更多空间。至少理论上是这样的!
出于调试目的,单击屏幕会将blockCount
设置为2。
这是一个示例图像,左侧是预期结果,右侧是当前结果:
如上图所示,当点击发生并执行 rectId.blockCount = 2
时,它似乎触发了一系列调用:
blockHeight
之前最终更改了 Repeater
的模型;
或者由于某些奇怪的原因,ColumnLayout
的 anchors
被重置;
或者发生了其他事情;
我正在尝试了解导致此行为的原因,并正在寻找一种方法,该方法允许应用程序动态更改块数,同时能够正确绘制它们!
我错过了什么?
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】:您为什么要计算blockHeight
和blockWidth
而您可以利用ColumnLayout
的强大功能?
使用Layout.fillWidth
和Layout.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
计算
如果您坚持保留计算,因为它在现实世界中更加困难(很公平),我建议使用implicitHeight
和implicitWidth
。这是有效的,因为布局引擎不会触发宽度/高度的变化,因为它应该自己设置这些,但是它会监控隐式大小:
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 中继器的主要内容,如果未能解决你的问题,请参考以下文章