1

我需要在 TVirtualStringTree 中呈现主菜单 - 每个菜单项都有一个类别。类别将构成树的根节点,在每个类别根节点下,将是菜单项。

类别和菜单项的数据集字段如下所示: 数据集结构

我在 OnInitNode 中的代码滚动浏览 Category 数据集的记录,并将每个 Category 的菜单项加载为子节点。但是我出了点问题(见图),类别节点都是相同的文本——这意味着数据集没有滚动到下一条记录。

似乎InitNode事件中的这行代码导致它退出循环,并且似乎是问题的原因:

Sender.ChildCount[Node] := x;

但是,渲染子节点的正确方法是什么?

这是我的代码:

type
  TTreeCategoryData = record
    ID: Integer;
    DispText: String;
  end;

  PTreeCategoryData = ^TTreeCategoryData;

  TTreeMenuItemData = record
    ID: Integer;
    CategoryID: Integer;
    DispText: String;
    ClassName: String;
  end;

  PTreeMenuItemData = ^TTreeMenuItemData;

  Tvstmainmenu_CategoryNodeData = record
    TreeCategoryData: PTreeCategoryData;
  end;

  Pvstmainmenu_CategoryNodeData = ^Tvstmainmenu_CategoryNodeData;

  Tvstmainmenu_MenuItemNodeNodeData = record
    TreeMenuItemData: PTreeMenuItemData;
  end;

  Pvstmainmenu_MenuItemNodeNodeData = ^Tvstmainmenu_MenuItemNodeNodeData;


procedure TfmMain.FormShow(Sender: TObject);
var
  x: Integer;
begin
  datamod.uspspmenucatgy_S.PrepareSQL(True);
  datamod.uspspmenucatgy_S.Open;

  x := datamod.uspspmenucatgy_S.RecordCount;
  vstmainmenu.RootNodeCount := x;
end;

procedure TfmMain.vstmainmenuFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  CategoryNodeData: Pvstmainmenu_CategoryNodeData;
  MenuItemNodeNodeData: Pvstmainmenu_MenuItemNodeNodeData;
begin
  if (Sender.GetNodeLevel(Node) = 0) then
  begin
    CategoryNodeData := Sender.GetNodeData(Node);

    if Assigned(CategoryNodeData) and Assigned(CategoryNodeData.TreeCategoryData) then
    begin
      Dispose(CategoryNodeData.TreeCategoryData);
    end;
  end
  else if (Sender.GetNodeLevel(Node) = 1) then
  begin
    MenuItemNodeNodeData := Sender.GetNodeData(Node);

    if Assigned(MenuItemNodeNodeData) and Assigned(MenuItemNodeNodeData.TreeMenuItemData) then
    begin
      Dispose(MenuItemNodeNodeData.TreeMenuItemData);
    end;
  end;

end;

procedure TfmMain.vstmainmenuGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
  TextType: TVSTTextType; var CellText: string);
var
  CategoryNodeData: Pvstmainmenu_CategoryNodeData;
  MenuItemNodeNodeData: Pvstmainmenu_MenuItemNodeNodeData;

  TreeCategoryData: PTreeCategoryData;
  TreeMenuItemData: PTreeMenuItemData;
begin

  if (Sender.GetNodeLevel(Node) = 0) then
  begin
    CategoryNodeData := Sender.GetNodeData(Node);

    if Assigned(CategoryNodeData) and Assigned(CategoryNodeData.TreeCategoryData) then
    begin
      TreeCategoryData := CategoryNodeData.TreeCategoryData;

      CellText := TreeCategoryData^.DispText;
    end;
  end
  else if (Sender.GetNodeLevel(Node) = 1) then
  begin
    MenuItemNodeNodeData := Sender.GetNodeData(Node);

    if Assigned(MenuItemNodeNodeData) and Assigned(MenuItemNodeNodeData.TreeMenuItemData) then
    begin
      TreeMenuItemData := MenuItemNodeNodeData.TreeMenuItemData;

      CellText := TreeMenuItemData^.DispText;
    end;
  end;

end;

procedure TfmMain.vstmainmenuInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
  var InitialStates: TVirtualNodeInitStates);
var
  CategoryNodeData: Pvstmainmenu_CategoryNodeData;
  MenuItemNodeNodeData: Pvstmainmenu_MenuItemNodeNodeData;

  x: Integer;
begin

  if (Sender.GetNodeLevel(Node) = 0) then
  begin
    CategoryNodeData := Sender.GetNodeData(Node);
    CategoryNodeData.TreeCategoryData := New(PTreeCategoryData);

    with CategoryNodeData.TreeCategoryData^ do
    begin
      ID := datamod.uspspmenucatgy_Srow_id.AsInteger;
      DispText := datamod.uspspmenucatgy_Scategory.AsString;
    end;

    // :Pcategory_id
    datamod.uspspmenu_S.ParamByName('Pcategory_id').AsInteger := datamod.uspspmenucatgy_Srow_id.AsInteger;
    datamod.uspspmenu_S.PrepareSQL(True);
    if (datamod.uspspmenu_S.State = dsBrowse) then
      datamod.uspspmenu_S.Refresh
    else
      datamod.uspspmenu_S.Open;

    x := datamod.uspspmenu_S.RecordCount;

    Sender.ChildCount[Node] := x;

    datamod.uspspmenucatgy_S.Next;
  end
  else if (Sender.GetNodeLevel(Node) = 1) then
  begin
    MenuItemNodeNodeData := Sender.GetNodeData(Node);
    MenuItemNodeNodeData.TreeMenuItemData := New(PTreeMenuItemData);

    with MenuItemNodeNodeData.TreeMenuItemData^ do
    begin
      ID := datamod.uspspmenu_Srow_id.AsInteger;
      CategoryID := datamod.uspspmenucatgy_Srow_id.AsInteger;
      DispText := datamod.uspspmenu_Smenuitem.AsString;
      ClassName := datamod.uspspmenu_Stframeclass.AsString;
    end;

    datamod.uspspmenu_S.Next;
  end;

end;

这是正在发生的事情。每个根节点(父节点)应该不同,但事实并非如此。此外,第二个根节点的子节点应该不同,但它似乎卡在第一个根节点的最后一个子节点上:

渲染错误!

提前致谢!

4

2 回答 2

1

初始化父(类别)节点时,您设置 SQL、打开查询并分配子节点的数量。您假设这些节点将立即被初始化,因此将立即遍历查询。

这不是这个树控件的工作方式。孩子是按需初始化的,而不是按照任何定义的顺序。有可能所有父节点都在任何子节点之前初始化,这解释了为什么许多子节点具有相同的文本 - 您丢弃了第三个查询中的两个查询,因此剩余的子节点继续重用最近查询的最后一个有效结果.

几年前,有一个数据感知版本的树控件。可能仍然有一些这样的控制。它们可能更适合您的目的。

否则,您应该做的是使用AddNode将节点添加到树中。遍历查询结果并在遇到每条记录时为其添加一个节点。

出于类似的原因,类别节点可能都具有相同的标题。让您的查询处理代码驱动节点的添加,反之亦然。

于 2015-04-01T05:35:09.100 回答
1

尝试一些替代方法,例如在单独的过程中创建节点,例如:

procedure TfrmMain.LoadTree;
var
  LTreeCategoryData: PTreeCategoryData;
  LCategoryNode: PVirtualNode;
begin
  datamod.uspspmenucatgy_S.PrepareSQL(True);
  datamod.uspspmenucatgy_S.Open;
  while not datamod.uspspmenucatgy_S.Eof do
  begin
    // 1. create parent node itself
    LTreeCategoryData := New(PTreeCategoryData);
    with LTreeCategoryData^ do
    begin
      ID := datamod.uspspmenucatgy_Srow_id.AsInteger;
      DispText := datamod.uspspmenucatgy_Scategory.AsString;
    end;
    LCategoryNode := vstmainmenu.AddChild(vstmainmenu.RootNode, LTreeCategoryData);

    // 2. create child nodes
    datamod.uspspmenu_S.ParamByName('Pcategory_id').AsInteger := datamod.uspspmenucatgy_Srow_id.AsInteger;
    datamod.uspspmenu_S.PrepareSQL(True);
    datamod.uspspmenu_S.Open;
    while not datamod.uspspmenu_S.Eof do
    begin
      LTreeMenuItemData := New(PTreeMenuItemData);

      with LTreeMenuItemData^ do
      begin
        ID := datamod.uspspmenu_Srow_id.AsInteger;
        CategoryID := datamod.uspspmenucatgy_Srow_id.AsInteger;
        DispText := datamod.uspspmenu_Smenuitem.AsString;
        ClassName := datamod.uspspmenu_Stframeclass.AsString;
      end;

      vstmainmenu.AddChild(LCategoryNode, LTreeMenuItemData);

      datamod.uspspmenu_S.Next;
    end;
    datamod.uspspmenu_S.Close;

    datamod.uspspmenucatgy_S.Next;
  end;
  datamod.uspspmenucatgy_S.Close;
end;

只要你想加载整个树,就调用这个新过程。

于 2015-03-31T16:10:52.557 回答