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 windows 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. Thats
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 dont 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
dont 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 doesnt
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
doesnt 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 doesnt have children.
Thats 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 items 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
Windows 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
dont 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
TreeViewItems "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 Windows Open event script, in
which we are populating all the high level
branches --such as "Receipts",
"Issues"
and "Returns".
But, we didnt 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
Windows 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 events 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 |
|