Delphi 10.4 FMX TTreeView自定义节点引发参数越界异常如何解决?
错误原因
TTreeViewItem的AddObject/Children属性是专门用来存储子树节点的,控件内部计算子节点数量、布局、展开折叠逻辑时,会默认遍历Children里的所有对象当成TTreeViewItem类型处理。你把普通的TRectangle和TEdit加到这个列表里,控件遍历的时候类型不匹配、索引计算错误,就会抛出EArgumentOutOfRangeException异常。
方案1:修复你现有代码的崩溃问题
只需要调整自定义节点中控件的挂载逻辑,不要把编辑器相关控件加到节点的Children列表里,显示时动态挂载到TreeView上即可:
- 先修改自定义节点的构造和析构逻辑:
constructor TVLinkTreeViewItem.Create(AOwner: TComponent); begin inherited; fData := ''; // Owner设为当前节点,自己管理销毁 fBackground := TRectangle.Create(Self); fBackground.Visible := false; fBackground.Stored := false; fEditor := TEdit.Create(Self); fEditor.Visible := false; fEditor.Align := TAlignLayout.Client; fEditor.Stored := false; fEditor.OnKeyDown := EditorKeyUp; self.OnDblClick := TreeViewItem1DblClick; fEditor.OnExit := EditorExit; end; destructor TVLinkTreeViewItem.Destroy; begin // 主动销毁创建的控件 fEditor.Free; fBackground.Free; inherited; end;
- 修改双击触发编辑的逻辑,动态定位编辑器位置:
procedure TVLinkTreeViewItem.TreeViewItem1DblClick(Sender: TObject); var NodePos: TPointF; begin // 计算当前节点在TreeView上的坐标 NodePos := Self.LocalToAbsolute(PointF(20, 0)); NodePos := TreeView1.AbsoluteToLocal(NodePos); // 动态把编辑器挂载到TreeView上 fBackground.Parent := TreeView1; fBackground.Visible := true; fBackground.Width := Self.Width - 20; fBackground.Height := Self.Height; fBackground.Position.X := NodePos.X; fBackground.Position.Y := NodePos.Y; fEditor.Parent := fBackground; fEditor.Enabled := true; fEditor.Visible := true; fEditor.Opacity := 1; fBackground.BringToFront; fEditor.BringToFront; fEditor.Text := Text; fEditor.SetFocus; fEditor.SelectAll; end;
方案2:更合理的FMX树节点就地编辑实现
推荐使用全局共用编辑器的方案,不会破坏树节点的内部结构,性能更高,逻辑更简单:
- 直接在窗体上放一个
TEdit,默认设为隐藏,不用自定义TTreeViewItem类 - 绑定
TTreeView的双击事件:
procedure TForm1.TreeView1DblClick(Sender: TObject); var Node: TTreeViewItem; NodePos: TPointF; begin Node := TreeView1.Selected; if Node = nil then Exit; // 计算节点位置 NodePos := Node.LocalToAbsolute(PointF(20, 0)); NodePos := TreeView1.AbsoluteToLocal(NodePos); // 定位编辑器 Edit1.Parent := TreeView1; Edit1.Position.X := NodePos.X; Edit1.Position.Y := NodePos.Y; Edit1.Width := Node.Width - 20; Edit1.Height := Node.Height; Edit1.Text := Node.Text; Edit1.Visible := True; Edit1.BringToFront; Edit1.SetFocus; Edit1.SelectAll; // 存储当前编辑的节点 Edit1.Tag := NativeInt(Node); end;
- 绑定编辑器的退出和键盘事件:
procedure TForm1.Edit1KeyUp(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); var Node: TTreeViewItem; begin if Key = vkReturn then begin Node := TTreeViewItem(Edit1.Tag); if Assigned(Node) then Node.Text := Edit1.Text; Edit1.Visible := False; end else if Key in [vkEscape, vkCancel] then Edit1.Visible := False; end; procedure TForm1.Edit1Exit(Sender: TObject); begin Edit1.Visible := False; end;
自定义数据可以直接存在TTreeViewItem的Tag、TagString属性里,也可以继承TTreeViewItem加自定义属性,只要不往Children里加非节点控件就不会出现越界异常。
内容的提问来源于stack exchange,提问作者Rick Hollerich
相关产品推荐
相关产品推荐

