【问题标题】:Cannot select the root node in Virtual Tree View无法在虚拟树视图中选择根节点
【发布时间】:2019-09-23 04:00:44
【问题描述】:

我正在使用带有虚拟树视图的 Delphi XE3。

我的代码如下:

type
  TMyData = record
    Caption: string;
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  RootNode: PVirtualNode;
  PData: ^TMyData;
begin
  RootNode := tvItems.AddChild(nil);
  PData := tvItems.GetNodeData(RootNode);
  PData^.Caption := 'This is a test node';
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  tvItems.NodeDataSize := SizeOf(TMyData);
end;

procedure TForm1.tvItemsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
  PData: ^TMyData;
begin
  if Assigned(Node) then
  begin
    PData := tvItems.GetNodeData(Node);

    if Assigned(PData) then
      Celltext := PData^.Caption;
  end;
end;

当我单击“Button1”时,将创建根节点。但是,当我的鼠标点击节点文本时,它不会被选中。

我的一些发现:

  1. 必须单击节点文本的开头才能选择节点。如果单击节点文本的中间或末尾,则不会选择节点。

  2. 如果我把 tvItemsGetText 改成下面,那么问题就消失了:

    procedure TForm1.tvItemsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
    var
      PData: ^TMyData;
    begin
      CellText := 'This is a test node';
    end;

我在 tvItemsGetText 中设置了一个断点,发现它会被调用多次。在前几次,PData 将为 nil,这使 CellText 为空。在最终调用时,PData 将变为有效,并且 CellText 将设置为“这是一个测试节点”。

似乎允许鼠标单击并选择节点的范围是由节点的初始文本确定的。如果初始文本为空字符串,则必须单击节点的最开始处才能选择它。

这是虚拟树视图的错误吗?

【问题讨论】:

  • the range that allow mouse click and select the node is determined by the initial texts - 是的。使用tvItems.BeginUpdate -> 更改树结构 -> tvItems.EndUpdate。或者 - 使用 AddChild 第二个参数(用户数据:指针)
  • @kami,非常感谢。我有两个问题: 1. 我应该为我添加的每个节点使用 BeginUpdate 和 EndUpdate 吗?节点不是批量添加的。 2、如何使用UserData参数?我检查了文件,它没有提到它。并且示例代码也不使用它。

标签: delphi select virtualtreeview


【解决方案1】:

有几种方法可以通过用户数据来初始化新节点。

1。使用OnInitNode事件:

procedure TForm5.Button1Click(Sender: TObject);
begin
  vt1.InsertNode(nil, amAddChildLast); // internal calls vt1InitNode
end;

procedure TForm5.vt1InitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
  var InitialStates: TVirtualNodeInitStates);
var
  PData: ^TMyData;
begin
  PData := Sender.GetNodeData(Node);
  PData^.Caption := 'This is a test node';
end;

2 使用UserData 参数

变体 1. 动态数据

不要忘记删除InitNode 事件并且不要设置NodeDataSize 属性

type
  TMyData = record
    Caption: string;
  end;
  PMyData = ^TMyData;

procedure TForm5.Button1Click(Sender: TObject);
var
  p: PMyData;
begin
  New(p);
  p.Caption:='This is a test node'; 
  vt1.InsertNode(nil, amAddChildLast, p); // create node with initialized user data
  // by default VirtualTree use NodeDataSize = SizeOf(pointer), 
  // so there is no reason to use GetNodeDataSize event 
end;


procedure TForm5.vt1GetText(Sender: TBaseVirtualTree; Node: PVirtualNode; 
  Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: string);
var
  PData: PMyData;
begin
  if Assigned(Node) then
  begin
    PData := PMyData(Sender.GetNodeData(Node)^); // little modification
    // for correct access to dynamic node data

    if Assigned(PData) then
      CellText := PData.Caption;
  end;
end;

procedure TForm5.vt1FreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  p: PMyData;
begin
  p:=PMyData(Sender.GetNodeData(Node)^);
  Dispose(p); // as you allocate memory for user data - you should free it to avoid memory leaks
end;

变体 2. 对象

在表单中添加新的私有函数:

  private
    { Private declarations }
    function GetObjectByNode<T: class>(Node: PVirtualNode): T; 
    // do not forget to include System.Generics.Collections to `uses`

实现:

function TForm5.GetObjectByNode<T>(Node: PVirtualNode): T;
var
  NodeData: Pointer;
  tmpObject: TObject;
begin
  Result := nil;
  if not Assigned(Node) then
    exit;
  NodeData := vt1.GetNodeData(Node);
  if Assigned(NodeData) then
    tmpObject := TObject(NodeData^);

  if tmpObject is T then
    Result := T(tmpObject)
  else
    Result := nil;
end;

以及主要代码(几乎与变体1相同):

procedure TForm5.Button1Click(Sender: TObject);
var
  d: TMyData;
begin
  d := TMyData.Create;
  d.Caption := 'This is a test node';
  vt1.InsertNode(nil, amAddChildLast, d);
end;

procedure TForm5.vt1FreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  d: TMyData;
begin
  d := GetObjectByNode<TMyData>(Node);
  d.Free;
end;

procedure TForm5.vt1GetText(Sender: TBaseVirtualTree; Node: PVirtualNode; 
  Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: string);
var
  d: TMyData;
begin
  d := GetObjectByNode<TMyData>(Node);
  if Assigned(d) then
    CellText := d.Caption;
end;

【讨论】:

  • 非常感谢。如果我理解正确,我的错误是我尝试在创建节点之后为其设置数据,因此在创建它时,它的文本设置为空字符串,这使得它只能在节点的开头选择.对吗?
  • @alancc ...是的。更准确地说 - 单元格文本设置为 VirtualTreeView.DefaultText 属性,因为在未分配 PData 时,您没有在 OnGetText 事件中将 CellText 设置为空字符串
  • 谢谢。我在 VirtualTreeView 帮助文档中阅读了常见问题解答,它说在节点数据更新后应该调用 InvalidateNode。那么这是解决问题的第三种方法吗?
  • @alancc。是的,但是......当您使用 InvalidateNode 重新计算刚刚创建的节点时 - 您做了“错误的操作”然后“解决了问题”。恕我直言,InvalidateNode / InvalidateToBottom / 等仅在您更改现有节点的数据时才应使用。对于创建节点更可取的方式 - 使用 UserData。
猜你喜欢
  • 1970-01-01
  • 2014-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多