Title: Using treeview as a menu
Question: You can use TTreeview as a structured menu in addition to standard menu. It's very easy and requires no coding when you already have a few functions.
Answer:
{
The idea is as follows:
You create TActions for every menu item, then assign these actions to menu items. Then you add items to your TreeView component on your main form. TMenu items are associated with TreeView items by comparing their captions, e.g. if you have a menu item with a caption 'Customers' and you have a TreeView item with the same text 'Customers' it is considered that clicking either menu item or TreeView item should invoke the same TAction. You just need to associate proper action with tree view items. Note that you cannot have two menu items having same captions but assigned different actions.
}
{ returns TTreeNode identified by node text ACaption by 'digging' down the tree recursively
}
function GetNodeByCaption(ACurrNode: TTreeNode; const ACaption: String): TTreeNode;
var
ANode: TTreeNode;
begin
Result:=nil;
ANode:=ACurrNode;
while ANodenil do begin
if AnsiSameCaption(ACaption, ANode.Text) then
begin
Result:=ANode;
exit;
end
else
begin
ANode := ANode.getFirstChild;
while ANodenil do begin
Result:=GetNodeByCaption(ANode, ACaption);
if Resultnil then
Exit
else
ANode:=ANode.GetNextChild(ANode);
end;
end;
end;
end;
{ returns a TTreeNode which text is ACaption }
function GetTVNodeByCaption(tv: TTreeView; const ACaption: String): TTreeNode;
var
Node: TTreeNode;
begin
Result:=nil;
Node:=tv.Items.GetFirstNode;
while Nodenil do begin
Result:=GetNodeByCaption(Node, ACaption);
if Resultnil then
exit;
Node:=Node.GetNextSibling;//GetNext doesn't return children??
end;
end;
{ in your main form you need to have a procedure, which associates menu items with tree view nodes:
}
procedure TfrmZKMain.AssociateTVMenu;
var
i: Integer;
ANode: TTreeNode;
begin
//aclMenu is the TActionList component where you put all your menu actions
for i:=0 to aclMenu.ActionCount-1 do
begin
if (aclMenu.Actions[i] is TCustomAction) then
begin
if (aclMenu.Actions[i] as TCustomAction).Caption='-' then
Continue;
//search for a TTreeNode in the tree view tvMenu which text is equal to the caption of the current TAction
ANode:=GetTVNodeByCaption(tvMenu, (aclMenu.Actions[i] as TCustomAction).Caption);
if ANodenil then
begin
//if menu item is invisible, remove treenode with the same caption as invisible menu item
if not (aclMenu.Actions[i] as TCustomAction).Visible then
begin
ANode.Delete;
end
else
begin
if (aclMenu.Actions[i] as TCustomAction).ImageIndex-1 then
begin
//assign the same image to tree node as menu item
ANode.ImageIndex:=(aclMenu.Actions[i] as TCustomAction).ImageIndex;
ANode.SelectedIndex:=(aclMenu.Actions[i] as TCustomAction).ImageIndex;
end;
//assign action to tree node, so when it's clicked it will execute an action
ANode.Data:=aclMenu.Actions[i];
end;
end;
end;//if
end;//for
end;
//OnExecute method of the TActionList which holds your menu actions
procedure TfrmZKMain.aclMenuExecute(Action: TBasicAction;
var Handled: Boolean);
var
Node: TTreeNode;
begin
if ActionacExit then
//if action is not the one which closes your app, select tree node
if (Action is TCustomAction) then
if ((tvMenu.Selectednil) and not AnsiSameCaption(tvMenu.Selected.Text, (Action as TCustomAction).Caption)) then
begin
Node:=GetTVNodeByCaption(tvMenu, (Action as TCustomAction).Caption);
if (Nodenil) and (NodeFActiveNode) then
begin
Node.Selected:=True;
end
end;
end;
{ write tvMenu OnChange event handler
FActive node is a private variable which holds currently selected TreeNode. don't forget to assign FActiveNode:=nil in OnFormCreate event.
}
procedure TfrmZKMain.tvMenuChange(Sender: TObject; Node: TTreeNode);
begin
if (Nodenil) and (NodeFActiveNode) then
if Assigned(Node.Data) and Assigned(TCustomAction(Node.Data).OnExecute) then
TCustomAction(Node.Data).Execute;
FActiveNode:=Node;
end;
//OnFormCreate event handler
procedure TfrmZKMain.FormCreate(Sender: TObject);
begin
FActiveNode:=nil;
.......
AssociateTVMenu;
//select default tree view item if needed
tvMenu.Items.GetFirstNode.Selected:=true;
end;