QML异常问题:删除ListModel节点后报pthModel未定义错误
问题原因与修复方案
错误原因
调用pthModel.remove(modelData.index, 1)后,对应的Repeater委托实例会立即被销毁,此时委托上下文已不存在,后续尝试访问ms、cnvManual甚至pthModel都会触发引用错误,因为当前执行环境已经失效。
修复后的代码
import QtQuick import QtQuick.Shapes 2.15 Rectangle { id: mRct implicitHeight: 600 implicitWidth: 800 color: "transparent" property bool moving: false ListModel { id: pthModel onDataChanged: { cnvManual.requestPaint() } // 监听元素移除事件,触发重绘 onRemoved: { cnvManual.requestPaint() console.log("点已移除") } } MouseArea { anchors.fill: parent id: mouse onClicked: { if (moving) return pthModel.append({ "x": mouseX, "y": mouseY }) cnvManual.requestPaint() } } Repeater { model: pthModel z: 21 delegate: Rectangle { required property int index // 直接使用index属性,比modelData.index更可靠 id: rctParent color: "white" width: 12 height: 12 radius: 6 x: model.x - 6 y: model.y - 6 DragHandler { acceptedModifiers: Qt.ControlModifier target: parent onActiveChanged: { if (!active) { // 使用ListModel的set方法更新数据,确保触发dataChanged pthModel.set(index, { x: rctParent.x + 6, y: rctParent.y + 6 }) } } } MouseArea { anchors.fill: parent acceptedButtons: Qt.AllButtons onClicked: mouse => { if (mouse.button === Qt.RightButton) { // 仅执行移除操作,后续逻辑交给model的onRemoved信号处理 pthModel.remove(index, 1) } } } } } Canvas { id: cnvManual anchors.fill: parent onPaint: { if (pthModel.count === 0) return var ctx = getContext("2d") ctx.clearRect(0, 0, width, height) ctx.strokeStyle = "blue" ctx.lineWidth = 2 ctx.beginPath() var firstPoint = pthModel.get(0) ctx.moveTo(firstPoint.x, firstPoint.y) for (var i = 1; i < pthModel.count; i++) { var pnt = pthModel.get(i) ctx.lineTo(pnt.x, pnt.y) } if (pthModel.count > 2) { ctx.lineTo(firstPoint.x, firstPoint.y) } ctx.stroke() } } }
关键修复点
- 移除委托内删除后的冗余代码,将重绘逻辑交给
pthModel.onRemoved信号处理 - 使用委托的
index属性替代modelData.index,避免上下文销毁导致的引用问题 - 用
pthModel.set()方法更新坐标,确保可靠触发dataChanged信号 - 移除无用的
ms.removed信号,简化逻辑
替代实现思路
1. 使用Shape组件自动绘制路径
无需手动操作Canvas,利用ShapePath绑定ListModel数据,实现自动更新:
Shape { anchors.fill: parent ShapePath { strokeColor: "blue" strokeWidth: 2 fillColor: "transparent" PathLine { x: pthModel.get(0)?.x || 0 y: pthModel.get(0)?.y || 0 } Repeater { model: pthModel.count > 1 ? pthModel.slice(1) : null delegate: PathLine { x: model.x y: model.y } } // 闭合路径 PathLine { x: pthModel.count > 2 ? pthModel.get(0).x : 0 y: pthModel.count > 2 ? pthModel.get(0).y : 0 } } }
2. 自定义PointItem管理坐标
创建独立的PointItem存储坐标,直接绑定到绘制组件,避免ListModel的上下文问题:
Rectangle { id: mRct implicitHeight: 600 implicitWidth: 800 color: "transparent" property bool moving: false default property alias points: pointContainer.children Item { id: pointContainer } Canvas { anchors.fill: parent onPaint: { if (pointContainer.children.length === 0) return var ctx = getContext("2d") ctx.clearRect(0, 0, width, height) ctx.strokeStyle = "blue" ctx.lineWidth = 2 ctx.beginPath() var firstPoint = pointContainer.children[0] ctx.moveTo(firstPoint.globalX, firstPoint.globalY) for (var i = 1; i < pointContainer.children.length; i++) { var pnt = pointContainer.children[i] ctx.lineTo(pnt.globalX, pnt.globalY) } if (pointContainer.children.length > 2) { ctx.lineTo(firstPoint.globalX, firstPoint.globalY) } ctx.stroke() } } MouseArea { anchors.fill: parent onClicked: { if (moving) return PointItem { parent: pointContainer x: mouseX - 6 y: mouseY - 6 } } } } // 外部定义PointItem Rectangle { id: pointItem width: 12 height: 12 radius: 6 color: "white" property real globalX: x + 6 property real globalY: y + 6 DragHandler { acceptedModifiers: Qt.ControlModifier target: parent } MouseArea { anchors.fill: parent onClicked: mouse => { if (mouse.button === Qt.RightButton) { parent = null } } } }
3. 使用QtQuick.Controls的ListView(或PathView)
如果点的数量较多,用ListView管理点元素,利用其内置的模型管理能力,减少手动维护成本,同时支持批量操作和性能优化。
内容的提问来源于stack exchange,提问作者Berkdan Kara
相关产品推荐
相关产品推荐

