我有一个这样的表:
id parent_id name
1 1 Root
2 1 Car
3 1 Plane
4 2 BMW
5 4 CLK
如何在 Delphi 中动态创建包含所有子项的弹出菜单?
它应该是这样的:
最佳答案
假设根元素的 Parent_ID 为 NULL,您可以发出请求
Select ID, Parent_ID, Name from all_my_menus
order by Parent_ID nulls first, ID
where Menu_ID = :MenuIDParameter
1 <NULL> Root
8 <NULL> another root
2 1 Car
4 1 Plane
3 2 BMW
5 4 CLK
您还可以缓存内存中创建的菜单项:var MI_by_id: TDictionary<integer, TMenuItem>;
遍历结果看起来像
var MI: TMenuItem;
MI_by_id: TDictionary<integer, TMenuItem>;
begin
MI_by_id := TDictionary<integer, TMenuItem>.Create;
try
While not Query.EOF do begin
MI := TMenuItem.Create(Self);
MI.Caption := Query.Fields[2].AsString;
MI.Tag := Query.Fields[0].AsInteger; // ID, would be helpful for OnClick event
MI.OnClick := ...some click handler
if Query.Fields[1].IsNull {no parent}
then MainMenu.Items.Add(MI)
else MI_by_id.Items[Query.Fields[1].AsInteger].Add(MI);
MI_by_id.Add(MI.Tag, MI); //save shortcut to potential parent for future searching
Query.Next;
end;
finally
MI_by_id.Free;
end;
end;
实际上,由于我们对查询中的 Parent_ID 进行了排序,给定父级的所有子级都会生成单个连续列表,因此在填充最后一个子级后(即在parent_ID获得新值之后),最好从字典中删除填充的父级并将先前找到的父项缓存在另一个局部变量中(而不是通过字典进行另一次搜索)。 然而,以人为本的菜单的合理大小应该远不值得这样做。但您必须了解这种方法很可能会扩展为 O(n*n),因此随着表的增长,速度会非常快。
- http://docwiki.embarcadero.com/Libraries/XE3/en/Vcl.Menus.TMenuItem.Add
- http://docwiki.embarcadero.com/CodeExamples/XE2/en/Generics.Collections.TDictionary_(Delphi )
注意:这还要求每个非根元素 ID > ParentID(在表上放置 CHECK CONSTRAINT)
1 <NULL> Root
8 <NULL> another root
7 1 Plane
3 4 BMW
4 7 CLK
5 8 Car
这将导致 BMW 在其母公司 CLK 创建之前绑定(bind)创建。 违反该条件可以通过以下几种方式克服:
- 递归加载:
select <items> where Parent_id is null
,然后对于每个添加的菜单项执行select <items> where Parent_id = :current_memuitem_id
等等。这就像 VirtualTreeView 可以工作 - 要求 SQL 服务器对树进行排序和展平 - 这通常称为自递归 SQL 选择,并且与服务器相关。
- 再引入一个集合变量 - 没有父项的菜单项。将每个新项目添加到菜单后,应搜索此集合是否有待处理的子项可从中提取并移动到新创建的父项中。
关于sql - Delphi中从sql server表动态创建弹出菜单树,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14356418/