Paradox Community
Search:

 Welcome |  What is Paradox |  Paradox Folk |  Paradox Solutions |
 Interactive Paradox |  Paradox Programming |  Internet/Intranet Development |
 Support Options |  Classified Ads |  Wish List |  Submissions 


Paradox Programming Articles  |  Beyond Help Articles  |  Tips & Tricks Articles  


Using the Microsoft TreeView Control in Paradox
By Liz Woodhouse

Introduction

TreeViews are often the best way to show certain types of data. However, Paradox® does not include its own treeview object type, so to include one in a Paradox form will require using a third-party control. This article discusses the Microsoft® TreeView control which is included with most, if not all, versions of Windows®. We'll cover what's possible, details on how to accomplish what's possible, and also what's not possible with this particular treeview.


Background

Following are things one should know prior to reading this article. If you're not familiar with ActiveX/OCX controls, I recommend reviewing Peter Zevenaar's tutorial.

First, almost all of my testing has been done using Paradox 10 and Microsoft TreeView Control 6.0 (SP4) as included in MSCOMCTL.OCX. Limited testing has been done with Paradox 8 and Microsoft TreeView Control 5.0 (SP2) as included in COMCTL32.OCX. I cannot vouch for different versions of either.

Second, the MS treeview uses "Index" and "Key" as properties and there is absolutely no way to get ObjectPAL to retrieve their values. However, there are other less-attactive ways around this problem which I'll discuss in this article.

Third, the MS treeview only has one column. If you want a multi-column one, you'll need to find a different one. I've found some third-party OCX's which I'll comment on briefly at the end of this article.

Fourth, you'll find Microsoft's documentation of their treeview control here.

Fifth - I cannot get font settings (other than bold/not bold) to take, so if you decide to use the MS treeview, you'll be stuck with Arial 12pt.

Sixth - TreeViews seem to have a common terminology which it's useful to know. The treeview itself is called just that; it contains a collection object called nodes, which in turn contains the individual node objects. Different treeviews vary in terms of which objects can be accessed from other objects (some require you to go through nodes to get to an individual node, others allow you to access a specific node from the treeview itself). You'll have to study the treeview's documentation to know exactly how each control works.


Using the Microsoft TreeView Control

Setting up the treeview on a form

In Paradox, register MSCOMCTL.OCX or COMCTL32.OCX (which should be in your Windows system directory) (the latter has the earlier version of the treeview). Do this despite the fact that they show up in Paradox's list of OCXs. Then restart Paradox. Then start a new blank form, and add the treeview to the toolbar, then add the treeview to your form.

Set properties as desired: + and - signs only show for the first level of nodes if the LineStyle (OCX properties, right-click on treeview) is set to 1 - tvwRootLines, and if the Style is set to one which includes "PlusMinus" symbols. I recommend you take some time to experiment with the control's properties, so you get an idea of what the options are and what works in Paradox. I didn't try using pictures in the tree, so if you wish to do so, you'll have to experiment on your own. By default, users can edit node text, so I suggest you disable this unless it's desired (set the LabelEdit property to 1 - tvwManual).

Code to get which item is selected

We'll cover this first as it's easier than the code to populate the tree (which I'll do last), and you can test this code alone as the treeview has default sample nodes available at runtime. This is where key and index do not come in (because ObjectPAL refuses to accept them):

Index is a changeable integer representing where in the tree the node is now. It is zero-based, and as the tree could be sorted, the Index value can change.

Key is a user-definable hidden value which must be unique for each node, which does not change and which, if we could get at it, would be extremely useful, but we can't get at it. Work-arounds are to use the text of the node, or to include the key in the text and use breakapart or a similar method to extract it. (NOTE: We do use key in the add() syntax to relate parent and child nodes, so it has some use - more later.)

Use the tree's click() event (not a Paradox event, so it has a red dot in Object Explorer) to get the text of the selected item. You'll also want to use the KeyUp event, in case the user uses the keyboard to move through items. I would recommend posting a UserAction from these events so you don't have to duplicate the code:
var
  oaNode OLEAuto  ;// node object is also an OCX
  stText String
endVar

;// treeTest is the name of the OCX object on the form
oaNode = treeTest.SelectedItem
;// SelectedItem is a property and returns an
;// OLEAuto value which is the selected node

stText = oaNode.Text
;// Text is a node property and equates to
;// the text displayed for the node
;// now do what you want with it...
Code to populate the tree

I recommend either posting a UserAction on open and then doing the population in the action event after open is done, or using a button or some other event (don't try to force it in the open event). NOTE: in these examples, treeTest is the name of the treeview object on the form. MS treeviews have a property called Nodes which returns an OLEAuto var which is the collection of all nodes in the tree.

The MS treeview's add() syntax is as follows:

nodesObject.add(li/stRelative, liRelationShip, stKey, stText)

stText (string) = the text that will display for the node.

stKey (string) = the unique string to distinguish the added node from all other nodes, must be unique. I recommend you use a unique field or integer value (the key value of record would be good) so you can easily add children to the parent. However, from testing, it seems the treeview will not accept an integer cast as a string, so you'll need to add an alpha character if you decide to use an integer as key (see example).

li/stRelative (longInt for Index/string for Key) = The Index or Key of the new node's relative - the node to which it will be related - how it's related is defined in the next argument.

liRelationship (longInt) = the relation between the node specified in li/stRelative and this new node. Valid values are:
0 = First - First node in Nodes (on level of relative)
1 = Last - Last node in Nodes (on level of relative)
2 = Next - Next node in Nodes (on level of relative) (default)
3 = Previous - Previous node in Nodes (on level of relative)
4 = Child - Child node to node named in relative

Add() returns an OLEAuto var attached to the newly inserted node (using this var is optional).

Example 1:

Simple master-detail (1:M) relation where the details do not have details of their own:
var
  tcDetails, tcMasters TCursor
  oaNodes, oaNode OLEAuto
endVar

if eventInfo.id() = UserAction+1 then
  ;// attach to the nodes object
  oaNodes = treeTest.Nodes
  ;// clear existing values
  oaNodes.clear()

  if not tcMasters.open(":TREE:MASTERS.DB") then
    errorShow("Couldn't open :TREE:MASTERS.DB")
    return
  endIf

  ;// open details on MasterName index
  if not tcDetails.open(":TREE:DETAILS.DB", "MasterName") then
    errorShow("Couldn't open :TREE:DETAILS.DB")
    tcMasters.close()
    return
  endIf

  ;// add each master
  scan tcMasters :
    ;// set range on detail table
    ;//to show this master's details
    if not tcDetails.setRange(tcMasters."MasterName") then
      errorShow("Couldn't set range on " + tcMasters."MasterName")
      loop
    endIf
    ;// add master as parent node
    ;// by blanking the first two arguments
    ;// ("" is not allowed, no argument is not allowed)
    ;// the new node will be added at the end of the list
    ;// as a top-level node
    ;// mastername is both key and text to display
    oaNode = oaNodes.add(blank(), blank(), tcMasters."MasterName", tcMasters."MasterName")
    oaNode.Bold = True  ;// make masters bold
    scan tcDetails :
      ;// add details as child nodes
      ;// relative is the mastername
      ;// (key from the master we just inserted)
      ;// relationship (4) is child
      ;// key and text are the DetailName value
      oaNodes.add(tcDetails."MasterName", 4, tcDetails."DetailName", tcDetails."DetailName")
    endScan
  endScan
  tcDetails.close()
  tcMasters.close()
endIf
Example 2:

NOTE: The following example uses a single table (of articles and replies to articles) which contains 1:M master:detail relations within itself by means of two fields: "articlenumber" is a unique identifier for each record; "masteran" is the "articlenumber" value of this record's master - or the article being replied to (blank if no master). This relation is n deep, where n is unknown.
var
  tcMaster, tcDetail TCursor
    ;// both will point to same table
  oaNodes OLEAuto
    ;// the nodes collection
endVar

oaNodes = treeTest.Nodes
oaNodes.clear()

if not tcMaster.open(":TREE:ARTICLES.DB") then
  errorShow("Couldn't open :TREE:ARTICLES.DB")
  return
endIf
if not tcDetail.open(":TREE:ARTICLES.DB","masteran") then
  errorShow("Couldn't open :TREE:ARTICLES.DB")
  tcMaster.close()
  return
endIf

scan tcMaster :
  ;// if the article has no master -
  ;// add it as the last node at the main level (default)
  if isBlank(tcMaster."masteran") then
    ;// key requires a string -
    ;// couldn't get it to take string(longIntVal),
    ;// so I added alpha chars
    ;// text = subject :: author
    oaNodes.add(blank(),
                blank(),
                string("an",tcMaster."articlenumber"),
                tcMaster."subject" + " :: " + tcMaster."author")
  else
    ;// it's a detail and has been added (as all detail
    ;// records appear in the table _after_ their master)
    ;// but it may have details, so we'll add its details
  endIf
  tcDetail.setRange(tcMaster."articlenumber", tcMaster."articlenumber")
  scan tcDetail :
    ;// add detail linked to master (via master's 'an')
    ;// as child (4),
    ;// with its own articlenumber as its key
    ;// and subject :: author as text
    oaNodes.add(string("an",tcDetail."masteran"),
                4,
                string("an",tcDetail."articlenumber"),
                tcDetail."subject" + " :: " + tcDetail."author")
  endScan
endScan

tcMaster.close()
tcDetail.close()

msgInfo("done","done")

Conclusion

While some of the MS treeview's features aren't available in Paradox, in situations where the best way to display items is in a treeview, the MS treeview is simple to use once you get the basics down.


Other TreeViews

There are also third-party treeviews available out there. Many of these have multiple columns, making them even more useful than the MS treeview. However, I've found that each has its own limitations in Paradox. Below are some treeviews I've tested in Paradox 10 (please note that earlier version of Paradox aren't quite as cooperative when using OCX controls).

UPDATE: Note that Tony McGuire recently discovered a problem with these treeviews (problem found with SftTree/OCX 4.0 and assumed to be in the others); it seems their license information gets compiled into the executable. As Paradox cannot compile an executable, we cannot find a way to use the licensed treeviews in Paradox. So, until/unless someone finds a workaround, or another treeview more Paradox-friendly, we seem to be stuck with the Microsoft treeview.

SftTree/OCX 4.0 by Softel vdm, Inc. - this is the one I would recommend and is the only one whose columns seemed workable.
  • Multi-column
  • Includes good help file
  • Certain syntaxes which won't work using 'normal' ObjectPAL syntax, will work with invoke()
  • Font changes cannot be made at all - they won't stick
  • Doesn't have a key, but does have an "ItemData" value which is basically the same thing
  • Because there's no key, to relate records to on another, you must get the parent's index via FindItemData (but it works and seems fast)
FlyTreeX by Imca Systems
  • I could find no way to access columns other than the first - proper syntax (based on help file and a support email) returned an error: "Error accessing the method..."
  • Help file is sometimes difficult to understand (English is not their native language)
  • There is no convenient way to relate parent and child records unless they are added in order - would be fine for example 1 above, example 2 above would have to be approached completely differently, if it's even possible
  • While they have a "Data" property which could be used to store data you don't want the user to see, it doesn't help in relating nodes, and you have to explicitly free memory used by this property
  • One good thing: Font settings seem to work with this one
TList by Bennet-Tec Information Systems, Inc. has annoying properties applet, couldn't find any way of accessing columns other than the first from within Paradox (seems to require an ADO data source)

ActiveTreeView and TreeViewX by Infragistics I forget what was wrong with these, but I couldn't get them as far as I got the SftTree OCX

AgTreeX by Brew City Software not multi-column, didn't test

DataTree by GreenTree Technologies I forget what was wrong with this one.

Those are all the ones I tried, or at least considered.


Read this article in Italian! (PDF format).


Discussion of this article


 Feedback |  Paradox Day |  Who Uses Paradox |  I Use Paradox |  Downloads 


 The information provided on this Web site is not in any way sponsored or endorsed by Corel Corporation.
 Paradox is a registered trademark of Corel Corporation.


 Modified: 15 May 2003
 Terms of Use / Legal Disclaimer


 Copyright © 2001- 2003 Paradox Community. All rights reserved. 
 Company and product names are trademarks or registered trademarks of their respective companies. 
 Authors hold the copyrights to their own works. Please contact the author of any article for details.