TreeControl
(System Library)
Description | Implements a tree control similar to that used by the Microsoft Windows Explorer folder panel. |
Returns | Reference to self(). Tree control is ready when return value is valid. |
Usage | Steady State only. |
Function Groups | Graphics |
Related to: | DropTree | GridList | ImportAPI |
Format | System.TreeControl(Tree[, GrayOutTextParm, EnableKeyboardControl, NodeCallBackScope, NodeCallBackName, LeftParm, BottomParm, RightParm, TopParm, BackColorParm, TextColorParm, RowHeight) |
Parameters |
Tree |
Required. Pointer to a structure defining the tree to be displayed. See comments for the structure definition. |
GrayOutTextParm |
Optional Boolean. True to gray-out text. Default:FALSE |
EnableKeyboardControl |
Optional Boolean. True to enable keyboard control. Default:FALSE |
NodeCallBackScope |
Optional object reference. The scope in which to find the callback module. See comments. |
NodeCallBackName |
Optional text. Name of the callback module for drawing. |
LeftParm |
Optional numeric. Will default to window dimensions |
BottomParm |
Optional numeric. Will default to window dimensions |
RightParm |
Optional numeric. Will default to window dimensions |
TopParm |
Optional numeric. Will default to window dimensions |
BackColorParm |
Optional background color. |
TextColorParm |
Optional text color. |
RowHeight |
Optional numeric to override the row height. |
Comments:
The first parameter describes the tree structure. The structure can be modified at any time, and the TreeControl will faithfully follow it. You can call Refresh at any time to invoke a full rebuild of the tree.
Structure definition:
NodeStruct Struct [ Key { The Key value is user-defined, and must be a value for which the == operator is meaningful. It identifies which node the caller refers to when calling helper subroutines. }; Label { Text value displayed. It can be any VTScada value that has a valid textual representation. }; SubTree { This is a reference to an array of subordinate nodes that are of the same format as this node. }; MapIdx { MapArray entry index. }; Flags { Various flags... definitions follow }; Icon { ICON graphic for the node. Defaults to Folder icon. }; Tooltip { Tooltip text. Defaults to blank. }; Data { Custom data managed by caller }; ];For legacy applications, you may continue to use the constants shown in the following table for the tree array. Used this way, the array must be two dimensional, with each row (first subscript) describing a node at the same level in the tree. Each field in the row describes the node further. The first six fields must be the following. Additional fields may be added as needed. In the following table, [n] refers to the row number and #TI_xxx refers to a defined constant within the TreeControl module. These constants are:
- [n][#TI_KEY]
- [n][#TI_TEXT]
- [n][#TI_SUBTREE]
- [n][#TI_FLAGS]
- [n][#TI_ICON]
- [n][#TI_TOOLTIP]
Node attributes to assign to Flags
- Constant #TIF_EXPANDED = 1 { Flag - true if expanded };
- Constant #TIF_CANEXPAND = 2 { Flag - true if able to expand };
- Constant #TIF_NOFOLDER = 4 { Flag - true if not display folder bmp };
- Constant #TIF_GREYTEXT = 8 { Flag - true if grey this option };
- Constant #TIF_HIDDENROOT = 16 { Flag - true if root is a hidden root };
- Constant #TIF_TITLEDTIP = 32 { Flag - true to use titled tooltips };
- Constant #TIF_POPUPTIP = 64 { Flag - true to NOT use in-place tooltips };
To use the control, the caller (not the parent) can provide the following subroutine modules, which will be called by TreeControl in response to specific events:
OnLeftClick(Node, X, Y, RelX, RelY) |
Called when the left mouse button is released over a tree node. Node is the tree node, while X and Y are the coordinates of the mouse. RelX and RelY are the coordinates of the mouse relative to the Line. |
OnRightClick(Node, X, Y, RelX, RelY) |
Called when the right mouse button is released over a tree node. Node is the tree node, while X and Y are the coordinates of the mouse. RelX and RelY are the coordinates of the mouse relative to the Line. |
OnDoubleClick(Node, X, Y, RelX, RelY) |
Called when the left mouse button is double-clicked over a tree node. Node is the tree node, while X and Y are the coordinates of the mouse. RelX and RelY are the coordinates of the mouse relative to the Line. This callback is always proceeded by OnLeftClick() and any node expansion is done prior to calling OnDoubleClick(), but after OnLeftClick(). |
CreateSubtree(Node) |
Called when a tree node has its #TIF_CANEXPAND flag set, but the #TI_SUBTREE member of the node has not yet been constructed. The callee is expected to construct an array of nodes and store them in the node supplied to CreateSubtree. |
ExpandTreeToNode(Key) |
A sort of superset of CreateSubtree. It is called in response to a call to SetSelected() to command the caller of TreeControl to make all the tree nodes necessary to allow the node containing the Key to be expanded. The caller of TreeControl should call ExpandNode as needed for each node. When this callback returns, the tree will be positioned at the node that contains Key. The (rough) logic of ExpandTreeToNode is: Recursively walk up the tree by recursing this subroutine until you get to the tree root. The reverse recursion path is the shortest route from the root back to the node. Unwind the recursion, creating sub-trees as necessary and calling ExpandNode() for any that are not expanded. |
OnJunctionSelect(Node) |
Called when a node is collapsed only if the currently selected node existed in Node's subtree. When this happens the collapsed node will take the selection. Node is the newly selected (now collapsed) node. Click events on the junction are handled internally, and no callback is made. |
When using callbacks on the selected node, the click detection is still done by the tree itself. You can use ExpandNode or SetSelected if you might prefer a different behavior. The selected node highlight is not done by the Tree Control and needs to be done in a callback, which is defined as follows:
CallBackScope\CallBackName( Left { }, Bottom { }, Right { }, Top { Drawing coordinates (not transformed) }, Node { Array for this Node }, Offset { Pixel offset from left coordinate where the text or image will be drawn. });
Public API
TreeControl makes the following API available to any module that calls the ImportAPI function. To do so use:
Init [ If 1 Main; [ System.ImportAPI(System.TreeControl); ]
{***** Indices into the Tree array nodes *****} [ (API) {***** Modern node structure to use instead of legacy arrays *****} NodeStruct Struct [ Key { "Key" value...see heading comment }; Label { Text value to be displayed }; SubTree { Subordinate tree below this node }; MapIdx { MapArray entry index }; Flags { Various flags...see definitions }; Icon { Icon for node. Default to Folder icon. }; Tooltip { Tooltip text. Default to Label. }; Data { Custom data managed by caller }; ]; {***** Legacy indicies for a tree node array ***** } Constant #TI_KEY = 0 { "Key" value...see heading comment }; Constant #TI_TEXT = 1 { Text value to be displayed }; Constant #TI_SUBTREE = 2 { Subordinate tree below this node }; Constant #TI_MAPARRAYIDX = 3 { MapArray entry index }; Constant #TI_FLAGS = 4 { Various flags...see definitions }; Constant #TI_ICON = 5 { ICON graphic for node..folder is default }; Constant #TI_TOOLTIP = 6 { In-place tooltip text - #TI_TEXT default }; Constant #TREE_MINNODESIZE = 7 { Minimum compulsory node size }; {***** Node attributes to assign to Flags *****} Constant #TIF_EXPANDED = 1 { Flag - true if expanded }; Constant #TIF_CANEXPAND = 2 { Flag - true if able to expand }; Constant #TIF_NOFOLDER = 4 { Flag - true if not display folder bmp }; Constant #TIF_GREYTEXT = 8 { Flag - true if grey this option }; Constant #TIF_HIDDENROOT = 16 { Flag - true if root is a hidden root }; Constant #TIF_TITLEDTIP = 32 { Flag - true to use titled tooltips }; Constant #TIF_POPUPTIP = 64 { Flag - true to NOT use in-place tooltips }; ]
Public Variables
TreeControl makes the following variables available for you to examine:
[ Height { Total number of NODES high. }; Width { Total number of PIXELS wide. }; LineHt { Height of one line of text in PIXELS }; Started = 0 { Flag - True when internal TreeWin is valid }; ]
Public Subroutines
Refresh |
No parameters. Should be called upon building the tree and upon change. |
CollapseNodes(Node) |
Differs from CollapseNode. Collapse the entire sub-tree below the specified node. This can be done at any time and will not destroy the expanded state of subordinate tree nodes, allowing a subsequent expand to restore the state of the tree as it was before the collapse. This must be called if you want to delete the nodes below a specific point and rebuild them. Failure to call this before deleting nodes will have unpredictable results. |
CollapseNode(ItemIndex) |
Differs from CollapseNodes. Collapses a MappingArray entry if not already collapsed. This operation recursively destroys any MappingArray entries for each child of the identified item. Does not affect the expanded or contracted indication in the node tree. This allows a subsequent expansion of this node to display itself in it's previous expansion state. |
DeleteNode(Node) |
Deletes the specified node and its subtree. |
ExpandNode(Node) |
Expand the indicated node if there are child nodes and if not already expanded. |
FindNode(Key, CurrentNode) |
Given the "Key" value, returns the node containing that (unique) key. Returns Invalid if none found. WARNING: This subroutine may require significant system resources or time. |
InsertNodes(Node, StartIndex, NumNewNodes) |
Node { Tree node having its subtree expanded } StartIndex { Start index in its subtree-inserts after this } NumNewNodes { Number of new nodes added } Note that Node must already be expanded. Also, you cannot add a node to an empty Node. Build the node data and call ExpandNode instead. |
SetSelected(TreeObject) |
Provide the "Key" value to programmatically select a tree item. The tree will be positioned at the new selected item, expanding if needed. |
Example of TreeControl:
{================================= System ====================================} {=============================================================================} ( System { Provides access to system library functions }; Layer { Provides access to the application layer }; ) [ Graphics Module { Contains user graphics }; Var { Value of element in tree that has been clicked }; ] Main [ Window( 0, 0 { Upper left corner }, 800, 600 { View area }, 800, 600 { Virtual area }, Graphics() { Start user graphics }, {65432109876543210} 0b00010000000110011, "Window Title", 0, 1); ] < {=================================== Graphics ================================} { This module displays the graphics for the application. } {=============================================================================} Graphics [ DrawTree Module { Draws the tree }; SelectedElement { Selected element }; LHS = 10 { Left hand side of window }; TOP = 59 { Top of window }; Trigger { Set when var changed }; Constant HelpID = 1234; XPos { Window X Location }; YPos { Window Y Location }; Width = 800 { Window width }; Height = 600 { Window height }; ] Init [ If 1 Main; [ { If a value that should be selected in the tree has been passed in, use it } IfThen(Valid(Var), SelectedElement = Var; ); ] ] Main [ { Update the window position once it has stopped moving } If TimeOut(Watch(0, VStatus(Self, 15), VStatus(Self, 16), VStatus(Self, 11), VStatus(Self, 12)), \DialogMoveTime); [ { Save the current position of the dialog (if it isn't minimized or maximized). Only do this if the dialog has moved and after a timeout. } XPos = VStatus(Self, 28) || VStatus(Self, 29) ? XPos : VStatus(Self, 15); YPos = VStatus(Self, 28) || VStatus(Self, 29) ? YPos : VStatus(Self, 16); Width = VStatus(Self, 28) || VStatus(Self, 29) ? Width : VStatus(Self, 11); Height = VStatus(Self, 28) || VStatus(Self, 29) ? Height : VStatus(Self, 12); ] { Draw the tree } Window(LHS, TOP, Width - 2*10, 0 { View area }, Width - 2*10, 0 { Virtual area }, DrawTree(), { 98765432109876543210 } 0b10000010001000001100, "Tree Window Label", "<00000000>", TRUE); { OK button } If WinButton(Width - 11*10, Height - 2*10, Width - 6*10, Height - 5*10, 0, "Ok", 1 { FID }, \_DialogFont); [ Var = SelectedElement; ] ] < {============================ Graphics\DrawTree ==============================} { Draws the TreeControl that shows the tree structure of the data. If the } { user selects a node in the tree, that node's value will be used. } {=============================================================================} DrawTree [ { Public callback modules } OnLeftClick Module { Callback for the TreeControl }; CreateSubTree Module { Callback for the TreeControl }; { Public working variables } HasFocus { Expected callback variable for TreeControl }; Ready { Is the tree control ready to go? }; I { Counter }; { Private working variables } PROTECTED TreeData { Data structure for use by Tree Ctrl }; PROTECTED TreeCtrlObj { Tree control instance }; PROTECTED NodeDict { Used to create the subtree }; { Each folder of the tree object } Children1; Children2; { Array that stores children of the root } NodeCategories; ] Init [ If 1 ShowTree; [ { Import the Tree Control API } System.ImportAPI(System.TreeControl); { Root node of tree (hidden) } TreeData = \NodeStruct(Invalid, "RootNode",Invalid, Invalid, \#TIF_EXPANDED + \#TIF_HIDDENROOT, Invalid, Invalid); { Put nodes into array } Children1 = System.MakeArray( \NodeStruct( "Child1Value", "Child1Label", Invalid, 0, \#TIF_NOFOLDER , Invalid, Invalid ), \NodeStruct( "Child2Value", "Child2Label", Invalid, 1, \#TIF_NOFOLDER, Invalid, Invalid ), ); { Put nodes into array } Children2 = System.MakeArray( \NodeStruct( "Child3Value", "Child3Label", Invalid, 0, \#TIF_NOFOLDER , Invalid, Invalid ), \NodeStruct( "Child4Value", "Child4Label", Invalid, 1, \#TIF_NOFOLDER, Invalid, Invalid ) ); { Put all the children into 1 array so we know how many children the root node should have } NodeCategories = System.MakeArray( Children1, Children2, ); { Used to make the subtree } NodeDict = System.MakeDictionary( "Folder1", Children1, "Folder2", Children2 ); CreateSubTree(TreeData); ] ] ShowTree [ Return(Self); SizeWindow(Self, width-2*10, height-10-13*10); { We're ready whenever our Tree Control is valid } Ready = Valid(TreeCtrlObj); { When the TreeCtrlObj becomes ready, expand the tree to the selected node } If Watch(0, Ready) && Ready; [ IfElse(Valid(SelectedElement), { If a value that should be selected in the tree has been passed in, use it } TreeCtrlObj.SetSelected(SelectedElement), { Example of selecting a node in the tree } TreeCtrlObj.SetSelected("Child2Value") ); TreeCtrlObj.ExpandNode(TreeData.SubTree[0]); ] { The tree control looks at our HasFocus variable to see if it has focus. We have focus if our calling module has focus. } HasFocus = ActiveWindow() == Caller(Self); TreeCtrlObj = System.TreeControl(&TreeData); { Rebuild the Tree Control's map array, anytime we build a new Tree Control } If Watch(1, Valid(TreeCtrlObj)); [ IfThen(Valid(TreeCtrlObj), TreeCtrlObj.Refresh(); ); ] ] < {====================== Graphics\DrawTree\CreateSubTree ======================} { Callback for creating a sub tree. } {=============================================================================} CreateSubTree ( Node { Node that needs a sub-tree }; ) [ Key { Key of new child node }; Folders { Folder names }; ] CreateSubTree [ If 1; [ IfThen(!Valid(Node.SubTree), Folders = ListKeys(NodeDict); Node.SubTree = New(ArraySize(NodeCategories)); I = 0; WhileLoop(I < ArraySize(Folders), Key = NodeDict[Folders[I]]; { Key for folders will start with '_' because an element in the subtree may have the same name as the folder which will cause both elements to be selected if either of them are clicked } Node.SubTree[I] = \NodeStruct(Concat("_", Folders[I]), Folders[I], Key, Invalid, \#TIF_CANEXPAND, Invalid, Invalid, Invalid); I++; ); ); Return(); ] ] { End of Graphics\DrawTree\CreateSubTree } > < {=============== Graphics\DrawTree\OnLeftClick ==============} { Callback when node is clicked } {=============================================================================} OnLeftClick ( Node { Node that was clicked }; ) OnLeftClick [ If 1; [ SelectedElement = Valid(Node.SubTree) ? "" : Node.Key; Return(); ] ] { End of Graphics\DrawTree\OnLeftClick } > { End of Graphics } > >