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 }
>
>