Getting started

First of all you will need a form with a control of the type FormTreeControl. Set the Autodeclaration property to yes. In this guide we assume, that the control is named “tree”.

Form tree items

A tree consists of a number of FormTreeItems. Each FormTreeItem has these visual attributes:

        <StateCheckedIcon><StateImage><Image><Text> 

StateCheckedIcon

This icon can be either a checkmark or an empty box. Set or get the value with the following method:

Boolean FormTreeItem.stateChecked([Boolean]) 

To use these icons the checkbox property, must be set to yes. It is only possible to use these two icons, they cannot be changed. If you need other icons, use stateImage or image. (see below).

StateImage

This icon can be anything you desire. All you need to do is create an imagelist (a class that extends ImagelistAppl).

During initialization you must call:

tree.setStateImagelist(imagelist.imagelist());

Assuming that your imagelist is named imagelist.

Use this method to set/get the image: 

int FormTreeItem.stateImage([imagelist.image(#myImage)])

Where #myImage is a macro defining the image number. These can be seen using the form: Tutorial_resource.

Hint: ImageListAppl_Checkbox is a useful imagelist. It contains all sorts of checkmarks – including the grayed one.

Image

This icon can be anything you desire. All you need to do is create an imagelist (a class that extends ImagelistAppl).

During initialization you must call: 

tree.setImagelist(imagelist.imagelist());

Assuming that your imagelist is named imagelist.

Use this method to set/get the image: 

int FormTreeItem.Image([imagelist.image(#myImage)]);

Where #myImage is a macro defining the image number. These can be seen using the form: Tutorial_resource.
When the item is selected in the tree, the image displayed will be the one set with:

int FormTreeItem.SelectedImage([imagelist.image(#myImage)]);

Typically image and selectedImage will be set to the same.

The difference between image and stateimage, is that image allows overlays on the icon. An overlay is a small extra icon like the padlock in the AOT. They can be set with:

int FormTreeItem.OverlayImage([imagelist.image(#myImage)]);

Text

The text is the text displayed in the tree. Set or get it with this method:

str FormTreeItem.text([str)]); 

It has a maximum length at 250 characters defined by Windows. If the user should be able to rename the text, set the editlabels property to yes.

Building the tree

Now we are ready to start building the tree. Assuming the data needed to build the tree is stored in the temporary table TmpTree, the source for creating the tree could look like this:

void buildTree(int parentKey, TreeItemIdx parentIdx) {     FormTreeItem         formTreeItem;     TmpTree              tmpTreeTemp;     TreeItemIdx          idx;    ;    tmpTreeTemp.setTmpData(tmpTree);     while select tmpTreeTemp         where tmpTreeTemp.parent == parentKey     {         formTreeItem = new FormTreeItem();         formTreeItem.text(tmpTreeTemp.name);         formTreeItem.image(imageList.image(#ImageCheckAll));         formTreeItem.selectedImage(formTreeItem.image());         formTreeItem.data(tmpTreeTemp.id);         idx = tree.addItem(parentIdx, 0, formTreeItem);         this.buildTree(tmpTreeTemp.id, idx);     } }

This small recursive method will do it all for you – but it requires that you have all your data stored and ready.

The most annoying part of building trees is that not all values can be set during build. You will have to use getItem and setItem (see below) to set state values such as stateImage and stateBold.

If you have a huge tree, this approach is not optimal. Instead the tree could be build as the user expands the branches. If this performance approach is used, you will need to specify if the formTreeItem has children manually and you will need to override the expanding method on the tree:

Children

As default the FormTreeItem can be expanded, if it contains children. If you want it to be expandable before building the children, you can use the method:

int FormTreeItem.children([int]);

Calling this method with -1 will set the value back to auto.

Expanding

To build the tree as the user expands his way, the expanding method must be overriden on the tree.

public boolean expanding(TreeItemIdx _idx, FormTreeExpand _action, anytype _data) {     boolean ret;     FormTreeItem item = this.getItem(_idx);     if (!item.stateExpandedOnce())     {         element.buildTree(item.data(), _idx);                ret = super(_idx, _action, _data);     }     return ret; }

Notice the use of the StateExpandedOnce method. It will ensure that the same branch not is build more than once. To use this approach the buildTree method must also be modified to be non-recursive.

In this example the data method is used to store context information.

Storing context information

Often the tree is a picture of some physical information. To get from the tree and back to physical information the data method can be used.

The data method can hold any basic data type (int, string, enum) and classes. In the example above the data() holds the unique identifier of the record in the TmpTree table.

If more than one basic data types are needed, several approaches can be used:

  1. Use a map for each of them, where idx is the key.
  2. Use a struct. (AFC)
  3. Create a new class working as a struct – if you want the compiler to verify your code (best practice)
  4. Use a temporary table - this will give you the best performance.

Using the temporary table will speed up performance, because you do not need any method calls to access the data. However, since the data method is incapable of holding records, a small workaround must be used:

Setting:

Map map = new Map(Types::Integer, Types::Record); map.insert(0, table); formTreeItem.data(map);

Getting:

Table = item.data().lookup(0);

This approach gave 5 times performance compared to using a class as a struct in the security system in version 3.0. Mostly due to the fact the context information was needed each time the tree was redrawn.

Navigating the tree

The tree is very easy to navigate using these methods on the tree:

FormTreeItem GetItem(TreeItemIdx idx)

Returns the FormTreeItem given an idx.

TreeItemIdx SetItem(FormTreeItem)

Set the item again. To be used when states has been changed. Must be used in conjunction with get.

TreeItemIdx GetChild(TreeItemIdx idx)

Returns the first child under a branch.

TreeItemIdx GetNextSibling(TreeItemIdx idx)

Returns the next child given its sibling (brother/sister).

TreeItemIdx GetParent(TreeItemIdx idx)

Returns the parent of a branch.

TreeItemIdx GetSelection()

Returns the selected item. If the SingleSelection property is set to yes.

TreeItemIdx GetFirstSelection()

Returns the first selected item. If the SingleSelection property is set to no.

TreeItemIdx GetNextSelection(idx)

Returns the next selected item, given the previous.

To loop over the selected items, this code can be used:

TreeItemIdx idx = tree.getFirstSelected(); while (idx) { //do something idx = tree.getNextSelected(idx); }

Controlling user clicks

You can get very precise information on where the user clicks in the tree, if you override the mouseDown method:

int mouseDown(int x, int y, int button, boolean ctrl, boolean shift) {     TreeItemIdx idx; int f;     FormTreeItem FormTreeItem;     #FormTreeControl     [idx,f]      = this.hitTest(x,y);     FormTreeItem = this.getItem(idx);     if (FormTreeItem)     {         if (bittest(f,#FTCHT_ONITEMICON))                {             //Do something             return 1;         }     }     return super(x, y, button, ctrl, shift); }

In the #FormTreeControl macro bit patterns matching where the user can click are defined. The example above will catch if he clicks on the image.

Ready to get started

Now you should have all the tools to get started. This guide has only covered a small part of forest. With Axapta form trees you do exactly the same as seen in other Windows applications. For more examples see the tutorial_form_treecontrol form and the SysFormTreeItem class.