VirtualStringTree - 无法使 parent-child 个节点正常工作
VirtualStringTree - Can't get parent-child nodes to work correctly
我需要在 TVirtualStringTree 中呈现 Main-Menu - 每个菜单项都有一个类别。类别将构成树的根节点,并且在每个类别根节点下,将是菜单项。
类别和菜单项的数据集字段如下所示:
我在 OnInitNode 中的代码滚动浏览类别数据集的记录,并将每个类别的菜单项加载为子节点。但是我出了点问题(见图),类别节点都是相同的文本——这意味着数据集没有滚动到下一条记录。
似乎是 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;
这是正在发生的事情。每个根节点(父节点)应该不同,但事实并非如此。此外,第二根节点的子节点应该不同,但它似乎卡在第一根节点的最后一个子节点上:
提前致谢!
尝试一些替代方法,例如在单独的过程中创建节点,例如:
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;
只要你想加载整棵树,就调用这个新过程。
初始化 parent(类别)节点时,设置 SQL、打开查询并分配 child 节点的数量。您假设这些节点将立即被初始化,因此将立即遍历查询。
这不是树控件的工作方式。 Children 是按需初始化的,而不是按任何定义的顺序。有可能所有 parent 节点都在任何 children 之前初始化,这解释了为什么许多 children 具有相同的文本——您丢弃了其中两个查询和第三个查询,所以剩下的 children 继续重复使用最近查询的最后一个有效结果。
几年前,有一个 data-aware 版本的树控件;可能仍然会有一些这样的控制。它们可能更适合您的目的。
否则,您应该做的是使用AddNode
将节点添加到树中。遍历查询结果并在遇到每条记录时为其添加一个节点。
出于类似的原因,类别节点可能都具有相同的标题。让您的 query-processing 代码驱动节点的添加,而不是相反。
我需要在 TVirtualStringTree 中呈现 Main-Menu - 每个菜单项都有一个类别。类别将构成树的根节点,并且在每个类别根节点下,将是菜单项。
类别和菜单项的数据集字段如下所示:
我在 OnInitNode 中的代码滚动浏览类别数据集的记录,并将每个类别的菜单项加载为子节点。但是我出了点问题(见图),类别节点都是相同的文本——这意味着数据集没有滚动到下一条记录。
似乎是 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;
这是正在发生的事情。每个根节点(父节点)应该不同,但事实并非如此。此外,第二根节点的子节点应该不同,但它似乎卡在第一根节点的最后一个子节点上:
提前致谢!
尝试一些替代方法,例如在单独的过程中创建节点,例如:
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;
只要你想加载整棵树,就调用这个新过程。
初始化 parent(类别)节点时,设置 SQL、打开查询并分配 child 节点的数量。您假设这些节点将立即被初始化,因此将立即遍历查询。
这不是树控件的工作方式。 Children 是按需初始化的,而不是按任何定义的顺序。有可能所有 parent 节点都在任何 children 之前初始化,这解释了为什么许多 children 具有相同的文本——您丢弃了其中两个查询和第三个查询,所以剩下的 children 继续重复使用最近查询的最后一个有效结果。
几年前,有一个 data-aware 版本的树控件;可能仍然会有一些这样的控制。它们可能更适合您的目的。
否则,您应该做的是使用AddNode
将节点添加到树中。遍历查询结果并在遇到每条记录时为其添加一个节点。
出于类似的原因,类别节点可能都具有相同的标题。让您的 query-processing 代码驱动节点的添加,而不是相反。