【问题标题】:VirtualStringTree - Correct way to add/handle subnodes/childnodes when using objects?VirtualStringTree - 使用对象时添加/处理子节点/子节点的正确方法?
【发布时间】:2011-12-21 18:30:13
【问题描述】:

我正在使用 Delphi2010 并尝试使用 VirtualStringTree。

我一直在尝试让它与对象一起工作,但一直没有成功,直到我遵循了 Philipp Frenzel 的 Virtual TreeView 教程,该教程是我在 soft-gems.net 网站上找到的。到目前为止我想出的东西有效,但我认为我没有正确处理子节点(即子节点)。

我唯一能够开始工作的就是为每个孩子再次链接整个对象,然后只显示我需要的字段 - 但感觉很不对。

非常感谢您的建议/反馈。


我有我正在尝试与 VirtualStringTree 连接的对象列表,我正在尝试实现这样的事情,其中​​一个字段将充当父级的标签,其余字段显示为子节点.

  • 罗伯特·莱恩
    • 35
    • 洛杉矶
    • 黑发
  • 简·多伊
    • 女性
    • 19
    • 丹佛
    • 红发女郎

这就是我的班级的设置方式。

type
  PTreeData = ^TTreeData;
  TTreeData = record
    FObject : TObject;
  end;

  TCustomerNode = class(TObject)
  private
    fName: string;
    fSex: string;
    fAge: integer;
    fHair: string;
    //...
  public
    property Name: string read fName write fName;
    //...
  end;

一旦我填充了对象,我就将它们添加到基于下面提到的 TList 的另一个类 (CustomerObjectList) 中。

这里是我将 VirtualStringTree 与我的对象列表连接起来的地方

procedure TfrmMain.btnLoadDataClick(Sender: TObject);
var
  i, j : integer;
  CustomerDataObject: TCustomerNode;
  RootXNode, XNode: PVirtualNode;
  Data: PTreeData;
begin
  vstree.NodeDataSize := SizeOf( TTreeData );

  vstree.BeginUpdate;
  for i := 0 to CustomerObjectList.Count - 1 do
  begin
    CustomerDataObject := CustomerObjectList[i];

    //load data for parent node
    RootXNode := vstree.AddChild(nil);
    Data  := vstree.GetNodeData(RootXNode);
    Data^.FObject:= PINodeSource;

    //now add children for rest of fields
    //Isn't there a better way to do this?
    for j := 1 to NUMBERofFIELDS -1 do  //first field is label for parent so -1
    begin
      XNode := vstree.AddChild(RootXNode);
      Data  := vstree.GetNodeData(XNode);
      Data^.FObject:= PINodeSource;
    end;

  end;
  vstree.EndUpdate; 
end;    

procedure TfrmMain.vstreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
 Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
  Data : PTreeData;
begin
  Data := vstObjects.GetNodeData(Node);
  ////if Node.ChildCount  = 0 then //edit - oops typo!
  if Node.ChildCount  > 0 then
  begin
    CellText := TCustomerNode(Data.FObject).Name;
    exit;
  end;

  //handle childnodes
  case Node.Index of
    0:  CellText := TCustomerNode(Data.FObject).Sex;
    1:  CellText := IntToStr(TCustomerNode(Data.FObject).Age);
    2:  CellText := TCustomerNode(Data.FObject).Hair;
    3:  CellText := TCustomerNode(Data.FObject).City;
  end;  
end;

【问题讨论】:

  • 如果您已经有一个包含所有对象的对象列表,那么为什么不只需要在 TreeData 记录中的对象列表中保存索引呢?或者在多个列表的情况下,列表和索引。
  • 抱歉,我可能只是需要更多的咖啡,但我没听懂你的意思。在 TreeData 记录的对象列表中保存索引?您能否提供更多信息?谢谢
  • 这是我提到的 Philipp Frenzel 的“Treeview_Tutorial.pdf”的链接soft-gems.net/supplement/download.php?ID=79

标签: delphi delphi-2010 virtualtreeview tvirtualstringtree


【解决方案1】:

我唯一能够开始工作的就是将整个链接 再次为每个孩子输入对象,然后只显示我需要的字段 - 但感觉很不对劲......非常感谢您的建议/反馈。

你必须这样做,因为你在混合关卡。

您的树被设置为期望每个级别的对象实例。但是您的实例列表只有主级别的对象和子级别的对象属性。

从所有意图和目的来看,这并没有错。

如果你真的想避免它,你将不得不使用复合模式,其中一个对象拥有一个属性列表,这些属性本身又是对象。会工作,但也会让你的班级工作变得更加混乱。尽管您始终可以通过使用索引属性来提供对拥有属性的访问。

TSomeObject = class(TObject)
private
  MyObjects: TList<TSomeObject>;
protected
  function GetStringProperty(const aIndex: Integer): string;
public
  property Name: string index 1 read GetStringProperty;
end;

这称为实体属性值模型。 (The EAV/CR Model of Data Representation) 它提供了灵活性,但也有缺点,特别是如果您要以类似的方式构建数据库。

【讨论】:

  • 感谢实体属性值模型的信息 - 稍后会更详细地检查它,但至少现在我认为它对我需要的东西来说太过分了。
【解决方案2】:

您不需要将所有数据加载到树中。您可以使用树的“虚拟性”。以下是我的做法。

将树的RootNodeCount设置为CustomerObjectList中的记录数:

vstree.RootNodeCount := CustomerObjectList.Count;

然后,在 OnInitChildren 事件中,将 0 级节点的子节点数设置为要显示的属性数:

procedure TfrmMain.vstreeInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
begin
  if Sender.GetNodeLevel(Node) = 0 then
  begin
    Sender.ChildCount[Node] := 4;

    // Comment this out if you don't want the nodes to be initially expanded
    Sender.Expanded[Node] := TRUE;
  end;
end;

现在,只需在 OnGetText 事件中检索正确的数据。

procedure TfrmMain.vstreeGetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: string);
begin
  if Column <= 0 then
  begin
    if Sender.GetNodeLevel(Node) = 0 then
      CellText := CustomerObjectList[Node.Index].Name else
    if Sender.GetNodeLevel(Node) = 1 then
    begin
      case Node.Index of
        0: CellText := CustomerObjectList[Parent.Node.Index].Sex;
        1: CellText := CustomerObjectList[Parent.Node.Index].Age;
        2: CellText := CustomerObjectList[Parent.Node.Index].Hair;
        3: CellText := CustomerObjectList[Parent.Node.Index].City;
      end; // of case
     end;
end;

您可能需要添加更多范围检查,以防万一。

希望这会有所帮助。

【讨论】:

  • 感谢您提供的重要信息。我检查了它,但我无法填充子节点 - vstreeInitChildren() 似乎没有触发。
  • 这就是你不测试的结果:) 我更新了代码并测试了它(将 InitChildren 更改为 InitNode 并更改了事件代码)。立即查找作品。
  • 我想出了一个不同的解决方案 - 但您的解决方案更简单、更优雅!在任何一种情况下,我仍然必须转换我的对象才能检索数据,如 >> CellText := TCustomerNode(CustomerObjectList[Node.Index]).Name
  • (编辑我的文本时编辑框超时)如果没记错的话,我在使用记录时不必强制转换,而是使用对象。感谢您花时间拓宽我的视野;)
  • 如果您不想转换对象,请使用 Generics.Collections 中的 TObjectlist。例如。 MyList := TObjectList.Create。根据经验,如果您已经在其他地方拥有数据,则很少需要在树记录中存储任何内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多