You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 22:55:43