Managing TreeView items

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:

  1. Get the item, which assigns it to a TreeViewItem structure.

  2. Make the changes, by setting TreeViewItem properties.

  3. 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

Deleting 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

Renaming items

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)

Moving items using drag and drop

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

Sorting items

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