Mastering PowerBuilder

HomePrevious Lesson: New Dot Notation Syntax for DataWindow Objects
Next Lesson: Course 3:: Session 21 :: Page 230
Treeview Control

TreeView control is one of the Windows 95 controls i.e., these controls were introduced with the Windows 95 operating system. Using a TreeView control you can display hierarchical data in the window and create more user-friendly screens. The behavior of this control is similar to windows explorer. Each item in this control is called TreeViewItem. In the following picture, Receipts is a TreeViewItem, Issues is a TreeViewItem and each transaction number you see is a TreeViewItem. Each TreeViewItem has a level number. The first item from which all the TreeViewItems branch is called "RootTreeViewItem". In the following picture, "Transactions" is the "RootTreeViewItem". The level of "RootTreeViewItem" is 1. The TreeViewItem level increments by one for each branch. In the following picture, "Receipts", "Returns" and "Issues" all have level 2. All the transactions under "Receipts" are at level 3, similarly transactions under "Issues" are also at level 3.

Unlike ListBox and DropDownListBox you cannot specify the tree items at painting time. You need to write code to add tree items in the TreeView control. Create a new window and place a TreeView control on the window. Write the following code in the window open event and run the window.

tv_trans.InsertItemFirst( 0, "Transactions", 1 )

The first argument specifies the tree item handle under which you want to insert a tree item. We do not have any other tree items in this TreeView control; so, we specify 0, i.e. to display the item as the first one in the control. The second argument specifies the label, third option specifies the picture number to use before the label. Close the window and go to the TreeView control properties. Select "Pictures" tab and change the first prompt to "Run!" and run the window again. Now you will see the following picture.

At painting time, you can either specify stock pictures or custom pictures in the TreeView control properties dialog box. In the script, you can refer to these pictures by the index number (index starts from the top, in the same order that you specified in properties dialog box). Changing the above code to the code shown below will not display any icon before the label.

tv_trans.InsertItemFirst( 0, "Transactions", 0 )

Now, let us see how to add pictures to the TreeView Control at run-time. Replace the above code with the following and run the window. You will see Help icon instead of a Closed Folder icon.

Integer li_icon1
li_icon1 = tv_trans.AddPicture( "Help!")
tv_trans.InsertItemFirst( 0, "Transactions", li_icon1)

We are adding a picture to the TreeView control using AddPicture() and capturing the return code of it. We are specifying that return code in the InserItemFirst(). To insert a TreeViewItem, there are two more functions available, InsertItemLast() and InsertItemSort(). The following picture depicts the result of different functions when the TreeView control already has the following TreeViewItems.

Now, let us add few branches to the "Transactions" root item. Before you write the code, change the picture to "Custom039!" in the "Picture" tab page of the properties dialog box. Replace the above code in the window’s open event with the following code and run the window.

long ll_trans, ll_receipts, ll_issues, ll_returns
ll_trans = tv_trans.InsertItemFirst( 0, "Transactions", 1)

// The following three tree items are branches
// to "Transactions.

// This is achieved by using ll_trans as the first
// argument in the following functions.

ll_receipts = tv_trans.InsertItemFirst( ll_trans, &
"Receipts", 1 )

ll_issues = tv_trans.InsertItemFirst( ll_trans, &
"Issues", 1 )

ll_returns = tv_trans.InsertItemFirst( ll_trans, &
"Returns", 1 )

You will see all tree items in the TreeView control as children of "Transactions". We are able to do this by using the handle of "Transactions" in the InsertItemFirst(). You can observe the lines that are connecting all the TreeViewItems in the TreeView control. If you wish not to show these lines, you need to turn off "ShowLines" property in the property dialog box. By default, the lines are not shown to the root tree item. Turn on "Lines at Root" option, if you need to show lines for the root tree item also. Turn this option on and run the window.

In addition to the lines, you will see one more thing, a button before the line. The button will display + sign when the branch has sub-branches but not expanded, - sign when the branch has sub-branches and the branch is already expanded. You will not see the button if the branch has no sub-branches. At run-time, clicking on the + button will expand the branch and vice-versa. That’s why, there is no button for "Receipts", but "Transactions" has the button. If you wish, you can turn off displaying buttons before the picture by turning off "ShowButtons" property in the property dialog button. Turning off "ShowButtons" feature is useful when you are using custom icons.

You can allow the user editing the label by turning on "EditLabels" property. This option is turned off by default. Turn on and run the window. Click on the label twice (don't double-click) and you will be able to change the label. Similarly, you can also allow the user to delete the TreeViewItems by turning on "DeleteItems" property. Like any other editable window controls, you can hide the selection when the TreeView control is not in focus by turning on "Hide Selection" property. This is the default.

While coding for TreeView control, you need to learn about one more object, TreeViewItem. To access the properties of any TreeViewItem in a TreeView control, you need to use TreeViewItem. This relationship is like the one you find between DataWindow control and DataWindow object. I will give an example of using TreeViewItem in a moment. If you browse the system objects in the "Object Browser", you will find that "TreeView" is inherited from "DragObject" while TreeViewItem is inherited from "Structure" object.

This paragraph will prepare you for the next topic. Recall about the TreeViewItem handle. Whenever you insert an item in the TreeView control using any of the InsertXXXXX(), they return the handle of the inserted item. For example, we captured the handle of the "Transactions" item in the above code example and use that to insert sub-branches to "Transactions".

TreeView control has two events in relation to item selection, they are SelectionChanging and SelectionChanged. For example, say, the "Receipts" item is highlighted and now, you click "Issues" item. At first "SelectionChanging" event will be fired. If this event returns zero, then "SelectionChanged" event will be fired and "Issues" option will be highlighted. These events are similar to "CloseQuery" and "Close" events. If "SelectionChanging" returns any non zero value, the selection will NOT be changed which means, "Receipts" is still highlighted.

Say, we don’t want to allow a particular user to see the "Issues". What do we do?. The solution is simple, return 1 from the "SelectionChanging" event. How do we know which item was clicked by the user?. Well, "SelectionChanging" event has two arguments, handle for the old item and new item (item that was just clicked). Remember that handle is of long data type. We don’t know the handle of "Issues" TreeViewItem to compare. What we know is the label, "Issues". You might think, we can find out the label by accessing the "Label" property for the handle since we know the handle. Your idea is good, but that item doesn’t have any "Label" property.

// The following line will give compilation error.

If NewHandle.Label = "Issues" Then Return 1

Hey, "long" is a normal traditional data type; It doesn’t have any properties or functions like an object. Here is where you use the TreeViewItem object. Write the following code to the "SelectionChanging" event and run the window. Click on "Issues" and see if you can select that item.

TreeViewItem ltvi_item

tv_trans.GetItem( NewHandle, ltvi_item )

If ltvi_item.Label = "Issues" Then Return 1

Once we know the handle of the item, we need to call GetItem() to read all the information about that item into a variable of type TreeViewItem (ltvi_item in the above example). Now we check or change the attributes of the TreeViewItem. Changing the TreeViewItem property does't reflect in the TreeView control automatically. You need to call SetItem() to apply the changes done to the TreeViewItem. The following code will demonstrate it.

TreeViewItem ltvi_item
tv_trans.HasButtons = True
tv_trans.GetItem( 2, ltvi_item )
ltvi_item.Children = True
tv_trans.SetItem( 2, ltvi_item )
tv_trans.Expandall(1)

Place a CommandButton on the window and write the above code. Expand "Transactions". You will not find buttons before other items. Now collapse "Transactions" and click on the CommandButton. You will see "Receipts" has "expand button". The second line says, display buttons for all items that has children. If you look at the previous code, "Receipts", "Issues" and "Returns" do not have any children; Only "Transactions" has children. Then, how did "Receipts" get a button? The answer is definitely not the ExpandAll(). This function just expands and displays. The answer is, "Children" attribute. Turning on this option for any TreeViewItem makes the TreeView control to think the specified TreeViewItem has children, even though it really doesn’t have children. That’s why, "Receipts" has got that expand button. Once we turned on this property, we are applying changes to the TreeView control by calling SetItem() for that item handle.

Before we jump on to the next topic, let us have a look at the other syntax of the InsertXXXXX() functions. All these functions are overloaded. They take either a label to insert an item or a TreeViewItem as an argument. Do you remember the code we wrote to insert "Transactions" item.

tv_trans.InserItemFirst( 0, "Transactions", 1)

The above code is using the first syntax. Let us use TreeViewItem to insert an item.

TreeViewItem ltvi_item
ltvi_item.Label = "Transactions"
ltvi_item.PictureIndex = 1
tv_trans.InsertItemFirst( 0, ltvi_item )

Now, let us learn about a most useful function for the TreeView control. That is FindItem(). Using this function, you can find the current item, previous, next items, first visible TreeViewItem on the screen and so on. Place one more CommandButton and write the following code to the Clicked event. Run the window, click on different items and click on this new button and understand the FindItem() function.

TreeViewItem ltvi_root, ltvi_FirstVisibleTreeViewItem
TreeViewItem ltvi_NextVisibleTreeViewItem
TreeViewItem ltvi_NextTreeViewItem
TreeViewItem ltvi_CurrentTreeViewItem
TreeViewItem ltvi_PreviousVisibleTreeViewItem
TreeViewItem ltvi_PreviousTreeViewItem
TreeViewItem ltvi_ParentTreeViewItem
TreeViewItem ltvi_ChildTreeViewItem
long ll_root, ll_next, ll_firstVisible, ll_NextVisible
long ll_Current, ll_Child, ll_PreviousVisible
long ll_previous, ll_parent
ll_root = tv_trans.FindItem( RootTreeViewItem!, 0)
tv_trans.GetItem( ll_root, ltvi_root )
MessageBox( "Root Item", ltvi_root.Label )
ll_current = tv_trans.FindItem( CurrentTreeViewItem!, 0 )
tv_trans.GetItem( ll_current, ltvi_CurrentTreeViewItem )
MessageBox( "Current Tree Item", ltvi_CurrentTreeViewItem.Label )
// Returns the next tree item in the same branch level
ll_next = tv_trans.FindItem( NextTreeViewItem!, ll_current )
tv_trans.GetItem( ll_next, ltvi_NextTreeViewItem )
MessageBox( "Next Tree Item", ltvi_NextTreeViewItem.Label )
ll_previous = tv_trans.FindItem( PreviousTreeViewItem!, ll_current )
tv_trans.GetItem( ll_previous, ltvi_PreviousTreeViewItem )
MessageBox( "Previous Tree Item", ltvi_PreviousTreeViewItem.Label )
ll_parent = tv_trans.FindItem( ParentTreeViewItem!, ll_current )
tv_trans.GetItem( ll_parent, ltvi_ParentTreeViewItem )
MessageBox( "Parent Tree Item", ltvi_ParentTreeViewItem.Label )
// Returns the first child item from top for the specified item.
ll_child = tv_trans.FindItem( ChildTreeViewItem!, ll_current )
tv_trans.GetItem( ll_child, ltvi_ChildTreeViewItem )
MessageBox( "Child Tree Item", ltvi_ChildTreeViewItem.Label )
/* Returns the next visible tree item (It doesn't consider
the tree item level) That means, the next visible tree item
may be one of the following depending */
// on the selection.
// * Next Child of the selected item
// * Next Child of the selected parent item
// * Next item that is in the same level of the selected
//   item's parent item
ll_FirstVisible = tv_trans.FindItem( FirstVisibleTreeViewItem!, 0)
tv_trans.GetItem( ll_FirstVisible, ltvi_FirstVisibleTreeViewItem )
MessageBox( "First Visible Tree Item", &
                ltvi_FirstVisibleTreeViewItem.Label )
ll_NextVisible = tv_trans.FindItem( NextVisibleTreeViewItem!, &
		ll_FirstVisible )
tv_trans.GetItem( ll_NextVisible, ltvi_NextVisibleTreeViewItem )
MessageBox( "Next Visible Tree Item", &
        ltvi_NextVisibleTreeViewItem.Label )
ll_PreviousVisible = tv_trans.FindItem( &
	PreviousVisibleTreeViewItem!, ll_FirstVisible )
tv_trans.GetItem( ll_PreviousVisible, &
	ltvi_PreviousVisibleTreeViewItem )
MessageBox( "Previous Visible Tree Item!", &
        ltvi_PreviousVisibleTreeViewItem.Label )

One key thing you need to remember for this function is that, when ever you use NextTreeViewItem! as an argument to this function, this function returns the handle of the next item that is in the SAME LEVEL. The same is the case with the PreviousTreeViewItem!. However, the behavior of FirstVisibleTreeViewItem! and NextVisibleTreeViewItem! arguments are different. The return value is the next and pervious visible item’s handles irrespective of the LEVEL.

Similar to the picture, you can also assign another picture for each item to display the state of the item. If you see in the properties dialog box for the TreeView control, you will find two tab pages – "Pictures", "State" Pictures. We have already demonstrated about using picture. In most of the cases, this option is enough. For example, say, you are using FileFolder icon for all options. If you want to display OpenFileFolder icon when the branch is expanded, you can add that icon to the pictures list and set the PictureIndex of the TreeViewItem when the item is expanded (You can do this in the ItemExpanding or ItemExpanded events).

Treeviewitem ltvi_CurrentItem
long ll_tvi
// Get the handle for the current item.
ll_tvi = tv_trans.FindItem( CurrentTreeViewItem!, 0 )
// Read the current item information
tv_trans.GetItem( ll_tvi , ltvi_CurrentItem )
// Set the state picture to the FileClose icon
ltvi_CurrentItem.StatePictureIndex = 2
// Apply the changes to the TreeView control from the TreeViewItem
tv_trans.SetItem ( ll_tvi, ltvi_CurrentItem )

"PictureIndex" and "SelectedPictureIndex" properties refer to the pictures that you specify in the "Pictures" tab page of the TreeView control property dialog box. The picture names you specify in this tab page are listed in "PictureNames" array property. If you add pictures using AddPicture() function, they are not available in the "PictureNames" array property at run-time.

The "StatePictureIndex" property refers to the picture names specified in the "State" tab page. To add a state picture at run-time, you need to call AddStatePicture() function. Similar to the AddPicture() function, state pictures that you add using AddStatePicture() is not available in the "StatePictureName" array property of the TreeView control.

The following picture depicts the places where these pictures were used in the TreeView control. When a TreeViewItem is selected, the picture you specified in the SelectedPictureIndex property is displayed in place of the picture you specified in the PictureIndex property. When the TreeViewItem is deselected, picture of the PictureIndex will be displayed in its original place automatically. To illustrate this, add "Custom096!" as 5th and "Custom082!" as 6th item in the "Pictures" tab, "Custom049!" as the 5th item in the "State" tab page. Append the following code to the Window’s Open event and run the code. At run-time, click on "Issues" and "Receipts" one at a time and see how the code works.

TreeViewItem ltvi_receipts
tv_trans.GetItem( ll_receipts, ltvi_receipts )
ltvi_receipts.PictureIndex = 5 //Ax icon
ltvi_receipts.StatePictureIndex = 5 // Music
ltvi_receipts.SelectedPictureIndex = 6 // Pencil
tv_trans.SetItem( ll_receipts, ltvi_receipts )

You can also specify an overlay picture using SetOverlayPicture() function. This function makes use of pictures that you specify in the "Pictures" tab page, i.e., "PictureNames" array property. When you use this function, make sure that picture is of the same size as the other pictures. You can use Watcom Image Editor to create bitmaps, icons or cursors; you don’t have to purchase other costly tools for this purpose. You can specify the size of the pictures in the property dialog box. If the original picture size is 16 by 16 and if you specify 32 by 32, PowerBuilder will stretch the picture to fit into 32 by 32 pixels area.

Couple of more important events are "ItemExpanding" and "ItemExpanded". The former event is fired first. If you want to prevent the item from expanding, return 1 in the "ItemExpanding" event. A typical use of "ItemExpanded" event is changing the icon when you are using custom images and "HasButtons" property being turned off. Similar to Item expansion, there are events related to item collapse also – "ItemCollapsing", "ItemCollapsed".

You can allow the user to edit the labels of the TreeViewItems by either turning on "Edit Labels" option in the property dialog box or setting the TreeViewItem’s "EditLabels" property of the TreeView control in the script. "BeginLabelEdit" event is fired at the beginning of the label editing. If you want to disable label editing for a particular item, check for that in this event and return 1. Similarly, "EndLabelEdit" is fired at the end of label editing, when the user press "Enter" key after editing or pressing on other TreeViewItem or other Window control. Returning 1 in this event retains the original label.

Recall the Window’s Open event script, in which we are populating all the high level branches --such as "Receipts", "Issues" and "Returns". But, we didn’t populate actual transaction numbers under each category. How to populate them, where to write the script?. The best event to write this script is the "ItemPopulate" event.

PICTURE This event will fire only when the "Children" property is set to True and will not fire if the item is already populated. Replace the Window’s Open event script with the following code. This code illustrates the right way of coding.

TreeViewItem ltvi_Item
long ll_trans, ll_receipts, ll_issues, ll_returns
Integer li_receipts_icon, li_returns_icon, li_issues_icon
// Add pictures to the TreeView control.
// Find these icons in the demo that you will
// download in a moment.
li_receipts_icon = tv_trans.AddPicture( "receipt.ico" )
li_returns_icon = tv_trans.AddPicture( "return.ico" )
li_issues_icon = tv_trans.AddPicture( "issue.ico" )
// Add categories to the TreeView Control.
ltvi_Item.Label = "Transacations"
ltvi_Item.PictureIndex = 1
ltvi_Item.SelectedPictureIndex = 2
ltvi_Item.StatePictureIndex = 2
ltvi_Item.Children = TRUE
ll_trans = tv_trans.InsertItemFirst( 0, ltvi_Item )
ltvi_Item.Label = "Receipts"
ltvi_Item.PictureIndex = li_receipts_icon
// We are not setting CHILDREN property to TRUE
// since it is already set above.
ll_receipts = tv_trans.InsertItemFirst( &
                ll_trans, ltvi_Item )
ltvi_Item.Label = "Issues"
ltvi_Item.PictureIndex = li_issues_icon
ll_issues = tv_trans.InsertItemFirst( ll_trans, ltvi_Item )
ltvi_Item.Label = "Returns"
ltvi_Item.PictureIndex = li_returns_icon
ll_returns = tv_trans.InsertItemFirst( ll_trans, ltvi_Item )
// Expand only at the root level for the user
// to see all the categories
tv_trans.ExpandItem( ll_trans )
// Set the DataWindow transaction object.
// dw_trans.SetTransObject( SQLCA )

I am using "Custom039!" and "Custom066!" in the "Pictures" tab page and "Custom039!", "Custom050!" in the "States" tab page.

Observe in the above code, we are setting the "Children" property to TRUE. At the end we are expanding only the root item by calling ExpandItem(). This will list all the children of "Transaction", but not the grand children. If you want to expand grand children also, you need to call ExpandAll() function. Now, we need to write code in the "ItemPopulate" event to populate different categories of the transactions whenever user clicks on them. I painted a DataWindow with external data source and using it for demonstration.

// Window: w_treeview_demo
// Control: tv_trans
// Event: ItemPopulate
DataStore lds_trans
Long ll_TotalRows, i
String ls_tran_type
TreeViewItem ltv_item
// Read the current item information.
This.GetItem( Handle, ltv_item )
//If NOT ltv_item.ExpandedOnce Then
if upper( ltv_item.Label ) = "RECEIPTS" Then
	ls_tran_type = "R"
elseif upper( ltv_item.Label ) = "ISSUES" Then
	ls_tran_type = "I"
elseif upper( ltv_item.Label ) = "RETURNS" Then
	ls_tran_type = "T"
elseif upper( ltv_item.Label ) = "TRANSACTIONS" Then
	return 0
else
	return 0
End If
// Create and set the Datawindow object.
lds_trans = Create DataStore
lds_trans.DataObject = "d_trans_list"
lds_trans.SetTransObject( SQLCA )
// Uncomment the following one line code in real application.
// lds_trans.Retrieve( ls_tran_type )
// Get rid of the following two lines in the real application.
// It is here since it is a demo and I don't want to go
// through the database connection.
lds_trans.SetFilter( "tran_type = '" + ls_tran_type + "'")
lds_trans.Filter()
// Populate all the transaction numbers under the current item.
ll_TotalRows = lds_trans.RowCount()
For i = 1 to ll_TotalRows
    tv_trans.InsertItemLast( Handle, String( &
	    lds_trans.GetItemNumber( i, 1 )), 1)
Next
//End If

In the above code, we are setting the transaction type depending on the selection and filtering the DataWindow and adding all those items to the TreeView control under the correct category. If the user selects "Transactions" or the transaction number, we are simply ignoring it and returning back. I am sure every function used in the above code is explained earlier either in this topic or in other topics. Questions? E-mail us.

One more interesting property that you may need when you want to do some thing only when the item is expanding for the first time or only when the item is already expanded; That is "ExpandedOnce" property.

Similar to any other window control that is inherited from the DragObject, you can also implement Drag-n-Drop interface for the TreeView control. This features is explained in detail in the "Drag-n-Drop" session. However, there is one interesting point in this control, which is that you no longer have to draw drag icons, PowerBuilder will display the item that is being dragged as the icon. To get this effect, select "None" as the drag icon in the property dialog box. Do you remember that, if you do the same thing for a DataWindow control, PowerBuilder will display a square/rectangle that is equal to the size of the DataWindow control as the drag icon?

There is one important function related to the drag-n-drop feature, the SetDropHighlight() function. This highlights the TreeViewItem that is passed as argument to this function. Please note that, highlighting a TreeViewItem using this function does not fire the SelectionChanging/SelectionChanged events.

If you need, you can use TreeView control as a front-end for the data that is stored in the database. TreeView control has a property "Data". The good thing about this property is that, the value of this property is not displayed on the screen and the data type of this property is "Any" ,which means that you can store all most all types of data in it. You can use a DataStore to retrieve the data from the database and display the data in the TreeView control. Store the database key information in "Data" property. When the user drags and drops or deletes or renames items, you can use either embedded SQL or the DataStore to update the database. I assume that you can do this very easily since you know about DataWindow, DataStore and TreeView control. If you still need an example, refer the end of this topic.

Exercises:

Complete the following exercises and check your work with the following PBL.
1. Enhance the ItemPopulate event’s code to provide the following functionality.

Display the full transaction of the selected transaction in a separate DataWindow.

2. Do all the changes that are needed in the w_treeview_demo window to have the following functionality:

Display all the transaction dates as children to the "Transactions" root item. Display "Receipts", "Returns" and "Issues" as children for each transaction date. Display transactions when the user selects a particular transaction.

Further Study:

Check the following windows in the example application and go through code in all events and functions. Before you start studying, run the example application and run the following examples and see them in work, by that you will understand the code very easily.

Window Name Section/Example Name Library Name
W_employee_xfer DataStores/ TreeView Drag/Drop Pbexamw2.pbl
W_tv_dw_link DataStores/ TreeView DataWindow Linking Pbexamw3.pbl
W_data_explorer DataStores/ Data Explorer Pbexamw1.pbl
W_dir_tree Recurson / Directory Tree Pbexamw1.pbl
HomePrevious Lesson: New Dot Notation Syntax for DataWindow Objects
Next Lesson: Course 3:: Session 21 :: Page 230