Can't get parent

I need to render a Main-Menu in a TVirtualStringTree - each menu item has a Category. The Categories will make up the root nodes of the tree, and under each Category root node, will be the menu items.

The datasets fields for the Categories and MenuItems look like this: 数据集结构

My code in OnInitNode scrolls through the Category dataset's records and loads the menu items for each Category as child nodes. However I've got something wrong (see image) and the Category nodes are all the same text - which means that the dataset is not scrolling to the next record.

It seems that this line of code in the InitNode event is causing it to exit the loop and seems to be the cause of problem:

Sender.ChildCount[Node] := x;

But then what is the proper way to render the child nodes?

This is my code:

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;

Here is what is happening. Each root node (parent) should be different but it isn't. Further the child nodes for the 2nd root node should be different, but it seems to be stuck on the last child node of the 1st root node:

错误的渲染!

Thanks in advance!


Try some alternative approach like creating nodes in a separate procedure, eg:

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;

Just call this new procedure, whenever you want to load the whole tree.


When you initialize a parent (category) node, you set the SQL, open the query, and assign the number of child nodes. You're assuming that those nodes will immediately be initialized, so the query will be traversed right away.

That's not how this tree control works. Children are initialized on demand, not in any defined order. It's possible that all the parent nodes get initialized before any children, which explains why many of the children have the same text — you discarded two of the queries with the third, so the remaining children kept reusing the last valid result from the most recent query.

Years ago, there was a data-aware version of the tree control; there might still be some such controls. They might be better suited to your purpose.

Otherwise, what you should do is use AddNode to add the nodes to the tree. Iterate over the query results and add a node for each record as you encounter it.

The category nodes probably all have the same titles for a similar reason. Let your query-processing code drive the adding of nodes, not vice versa.

链接地址: http://www.djcxy.com/p/5974.html

上一篇: 如何隐藏虚拟TreeView中的展开/折叠按钮?

下一篇: 无法获得父母