An item in a TreeView is a TreeViewItem structure. The preceding section described how to set the item's properties in the structure and then insert it into the TreeView.
This code declares a TreeViewItem structure and sets several properties:
TreeViewItem tvi_defined tvi_defined.Label = "Symphony No. 3 Eroica" tvi_defined.StatePictureIndex = 0 tvi_defined.PictureIndex = 3 tvi_defined.SelectedPictureIndex = 4 tvi_defined.OverlayPictureIndex = 0 tvi_defined.Children = TRUE
For information about Picture properties, see Managing TreeView pictures.
When you insert an item, the inserting function returns a handle to that item. The TreeViewItem structure is copied to the TreeView control, and you no longer have access to the item's properties:
itemhandle = This.InsertItemLast(parenthandle, & tvi_defined)
Procedure for items: get, change, and set
If you want to change the properties of an item in the TreeView, you:
-
Get the item, which assigns it to a TreeViewItem structure.
-
Make the changes, by setting TreeViewItem properties.
-
Set the item, which copies it back into the TreeView.
When you work with items that have been inserted in the TreeView, you work with item handles. Most TreeView events pass one or two handles as arguments. The handles identify the items the user is interacting with.
This code for the Clicked event uses the handle of the clicked item to copy it into a TreeViewItem structure whose property values you can change:
treeviewitem tvi This.GetItem(handle, tvi) tvi.OverlayPictureIndex = 1 This.SetItem(handle, tvi)
Important
Remember to call the SetItem function after you change an item's property value. Otherwise, nothing happens in the TreeView.
Items and the hierarchy
You can use item handles with the FindItem function to navigate the TreeView and uncover its structure. The item's properties tell you what its level is, but not which item is its parent. The FindItem function does:
long h_parent h_parent = This.FindItem(ParentTreeItem!, handle)
You can use FindItem to find the children of an item or to navigate through visible items regardless of level.
For more information, see the section called “FindItem” in PowerScript Reference.
Enabling TreeView functionality in scripts
By setting TreeView properties, you can enable or disable user actions like deleting or renaming items without writing any scripts. You can also enable these actions by calling functions. You can:
-
Delete items
-
Rename items
-
Move items using drag and drop
-
Sort items
To allow the user to delete items, enable the TreeView's DeleteItems property. When the user presses the Delete key, the selected item is deleted and the DeleteItem event is triggered. Any children are deleted too.
If you want more control over deleting, such as allowing deleting of detail items only, you can call the DeleteItem function instead of setting the property. The function also triggers the DeleteItem event.
Example
This script is for a TreeView user event. Its event ID is pbm_keydown and it is triggered by key presses when the TreeView has focus. The script checks whether the Delete key is pressed and whether the selected item is at the detail level. If both are TRUE, it deletes the item.
The value of the TreeView's DeleteItems property is FALSE. Otherwise, the user could delete any item, despite this code:
TreeViewItem tvi long h_item IF KeyDown(KeyDelete!) = TRUE THEN h_item = This.FindItem(CurrentTreeItem!, 0) This.GetItem(h_item, tvi) IF tvi.Level = 3 THEN This.DeleteItem(h_item ) END IF END IF RETURN 0
If you enable the TreeView's EditLabels property, the user can edit an item label by clicking twice on the text.
Events
There are two events associated with editing labels.
The BeginLabelEdit event occurs after the second click when the EditLabels property is set or when the EditLabel function is called. You can disallow editing with a return value of 1.
This script for BeginLabelEdit prevents changes to labels of level 2 items:
TreeViewItem tvi This.GetItem(handle, tvi) IF tvi.Level = 2 THEN RETURN 1 ELSE RETURN 0 END IF
The EndLabelEdit event occurs when the user finishes editing by pressing enter, clicking on another item, or clicking in the text entry area of another control. A script you write for the EndLabelEdit event might validate the user's changes for example, it could invoke a spelling checker.
EditLabel function
For control over label editing, the BeginLabelEdit event can prohibit editing of a label, as shown above. Or you can set the EditLabels property to FALSE and call the EditLabel function when you want to allow a label to be edited.
When you call the EditLabel function, the BeginLabelEdit event occurs when editing begins and the EndLabelEdit event occurs when the user presses enter or the user clicks another item.
This code for a CommandButton puts the current item into editing mode:
long h_tvi h_tvi = tv_1.findItem(CurrentTreeItem!, 0) tv_1.EditLabel(h_tvi)
At the window level, PowerBuilder provides functions and properties for dragging controls onto other controls. Within the TreeView, you can also let the user drag items onto other items. Users might drag items to sort them, move them to another branch, or put child items under a parent.
When you implement drag and drop as a way to move items, you decide whether the dragged item becomes a sibling or child of the target, whether the dragged item is moved or copied, and whether its children get moved with it.
There are several properties and events that you need to coordinate to implement drag and drop for items, as shown in the following table.
Property or event |
Setting or purpose |
---|---|
DragAuto property |
TRUE or FALSE If FALSE, you must call the Drag function in the BeginDrag event. |
DisableDragDrop property |
FALSE |
DragIcon property |
An appropriate icon or None!, which means the user drags an image of the item |
BeginDrag event |
Script for saving the handle of the dragged item and optionally preventing particular items from being dragged |
DragWithin event |
Script for highlighting drop targets |
DragDrop event |
Script for implementing the result of the drag operation |
Example
The key to a successful drag-and-drop implementation is in the details. This section illustrates one way of moving items. In the example, the dragged item becomes a sibling of the drop target, inserted after it. All children of the item are moved with it and the original item is deleted.
A function called recursively moves the children, regardless of the number of levels. To prevent an endless loop, an item cannot become a child of itself. This means a drop target that is a child of the dragged item is not allowed.
BeginDrag event
The script saves the handle of the dragged item in an instance variable:
ll_dragged_tvi_handle = handle
If you want to prevent some items from being dragged -- such as items at a particular level -- that code goes here too:
TreeViewItem tvi This.GetItem(handle, tvi) IF tvi.Level = 3 THEN This.Drag(Cancel!)
DragWithin event
The script highlights the item under the cursor so the user can see each potential drop target. If only some items are drop targets, your script should check an item's characteristics before highlighting it. In this example, you could check whether an item is a parent of the dragged item and highlight it only if it is not:
TreeViewItem tvi This.GetItem(handle, tvi) tvi.DropHighlighted = TRUE This.SetItem(handle, tvi)
DragDrop event
This script does all the work. It checks whether the item can be inserted at the selected location and inserts the dragged item in its new position a sibling after the drop target. Then it calls a function that moves the children of the dragged item too:
TreeViewItem tvi_src, tvi_child long h_parent, h_gparent, h_moved, h_child integer rtn // Get TreeViewItem for dragged item This.GetItem(ll_dragged_tvi_handle, tvi_src) // Don't allow moving an item into its own branch, // that is, a child of itself h_gparent = This.FindItem(ParentTreeItem!, handle) DO WHILE h_gparent <> -1 IF h_gparent = ll_dragged_tvi_handle THEN MessageBox("No Drag", & "Can't make an item a child of itself.") RETURN 0 END IF h_gparent=This.FindItem(ParentTreeItem!, h_gparent) LOOP // Get item parent for inserting h_parent = This.FindItem(ParentTreeItem!, handle) // Use 0 if no parent because target is at level 1 IF h_parent = -1 THEN h_parent = 0 // Insert item after drop target h_moved = This.InsertItem(h_parent, handle, tvi_src) IF h_moved = -1 THEN MessageBox("No Dragging", "Could not move item.") RETURN 0 ELSE // Args: old parent, new parent rtn = uf_movechildren(ll_dragged_tvi_handle, & h_moved) / If all children are successfully moved, // delete original item IF rtn = 0 THEN This.DeleteItem(ll_dragged_tvi_handle) END IF END IF
The DragDrop event script shown above calls the function uf_movechildren. The function calls itself recursively so that all the levels of children below the dragged item are moved:
// Function: uf_movechildren // Arguments: // oldparent - Handle of item whose children are // being moved. Initially, the dragged item in its // original position // // newparent - Handle of item to whom children are // being moved. Initially, the dragged item in its // new position. long h_child, h_movedchild TreeViewItem tvi // Return -1 if any Insert action fails // Are there any children? h_child = tv_2.FindItem(ChildTreeItem!, oldparent) IF h_child <> -1 THEN tv_2.GetItem(h_child, tvi) h_movedchild = tv_2.InsertItemLast(newparent, tvi) IF h_movedchild = -1 THEN RETURN -1 // Move the children of the child that was found uf_movechildren(h_child, h_movedchild) // Check for more children at the original level h_child = tv_2.FindItem(NextTreeItem!, h_child) DO WHILE h_child <> -1 tv_2.GetItem(h_child, tvi) h_movedchild= tv_2.InsertItemLast(newparent,tvi) IF h_movedchild = -1 THEN RETURN -1 uf_movechildren(h_child, h_movedchild) // Any more children at original level? h_child = tv_2.FindItem(NextTreeItem!, h_child) LOOP END IF RETURN 0 // Success, all children moved
A TreeView can sort items automatically, or you can control sorting manually. Manual sorting can be alphabetic by label text, or you can implement a user-defined sort to define your own criteria. The SortType property controls the way items are sorted. Its values are of the enumerated datatype grSortType.
Automatic alphabetic sorting
To enable sorting by the text label, set the SortType property to Ascending! or Descending!. Inserted items are sorted automatically.
Manual alphabetic sorting
For more control over sorting, you can set SortType to Unsorted! and sort by calling the functions in the following table.
Use this function |
To do this |
---|---|
InsertItemSort |
Insert an item at the correct alphabetic position, if possible |
Sort |
Sort the immediate children of an item |
SortAll |
Sort the whole branch below an item |
If users will drag items to organize the list, you should disable sorting.
Sorting by other criteria
To sort items by criteria other than their labels, implement a user-defined sort by setting the SortType property to UserDefinedSort! and writing a script for the Sort event. The script specifies how to sort items.
PowerBuilder triggers the Sort event for each pair of items it tries to reorder. The Sort script returns a value reporting which item is greater than the other. The script can have different rules for sorting based on the type of item. For example, level 2 items can be sorted differently from level 3. The TreeView is sorted whenever you insert an item.
Example of Sort event
This sample script for the Sort event sorts the first level by the value of the Data property and other levels alphabetically by their labels. The first level displays composers chronologically, and the Data property contains an integer identifying a composer's century:
//Return values //-1 Handle1 is less than handle2 // 0 Handle1 is equal to handle2 // 1 Handle1 is greater than handle2 TreeViewItem tvi1, tvi2 This.GetItem(handle1, tvi1) This.GetItem(handle2, tvi2) IF tvi1.Level = 1 THEN // Compare century values stored in Data property IF tvi1.data > tvi2.Data THEN RETURN 1 ELSEIF tvi1.data = tvi2.Data THEN RETURN 0 ELSE RETURN -1 END IF ELSE // Sort other levels in alpha order IF tvi1.Label > tvi2.Label THEN RETURN 1 ELSEIF tvi1.Label = tvi2.Label THEN RETURN 0 ELSE RETURN -1 END IF END IF