Purpose
This document is aimed at assisting programmers who are wishing to implement the Microsoft Visual Basic ActiveX ListView Control in their applications. Methods of creating the ListViews, setting up the column headers and other properties, filling the lists with data from rowsets, adding and removing elements, saving the list items back to a rowset, navigating through the elements in the lists and printing the lists are demonstrated using code examples from a working application.
Acknowledgements
I would like to thank Ken Mayer, Gary White, Jean-Pierre Martel, Dan Howard, Bowen Moursund, Vic McClung, David L. Stone (my proof-reader) and others in the dBASE Newsgroups for their suggestions, code samples and generous help.
Why Use ListView?
Some time ago, I created a form that allowed a user to transfer all or selected values in two arrays from a source Listbox to a destination Listbox and vice versa. I wanted the user to be able to switch the elements back and forth until satisfied before actually saving the data back to the rowsets.
I borrowed heavily from Ken Mayer’s Mover.cc program to accomplish this.
Recently, I needed to create another form to do likewise but this time I needed the Listboxes to display more than one column of data and to be able to horizontally scroll across the lists. The in-built Visual dBASE Listbox (up to Version 7.5 anyway) is unable to do this.
After much research on the subject, I decided to give the Visual Basic ActiveX control ListView a go, based on a suggestion from Gary White.
Benefits of Using ListView
The ListView control is part of a group of ActiveX controls that are found in the MSCOMCTL.OCX file. To use the ListView control in your application, you must add the MSCOMCTL.OCX file to the project. When distributing your application, install the MSCOMCTL.OCX file in the user’s Microsoft Windows System or System32 directory.
Using the Set up ActiveX Components option of the Visual dBASE Component Palette, add the Microsoft ListView Control component to your palette. Another article in the dBASE Developers Bulletin has covered this subject thoroughly enough for me to not have to repeat the process in detail here.
Create a form, drop the ListView component onto it and you’re then ready to start programming it!
About the Example Form
First, to help you understand the code samples in this document, here is some background information on the form and it’s associated tables used in the example code.
The purpose of the form is to allow a user to display all the qualifications issued to employees and to issue and remove these qualifications as required. The user may optionally print qualification forms and certificates as they are issued and also print lists of qualifications issued or not issued to the employees.
There are four types of qualifications: Appointments, Authorisations, Skills and Learner Permits. The user can choose to have the source list display the entire list of the qualifications or just those qualifications assigned to the employee’s job title.
The form contains a three-page Tabbox. The first page lets the user operate on one employee and his/her issued or not-issued qualifications:
The second page of the Tabbox lets the user operate on one qualification and all the employees issued/not issued with it:
The third page of the Tabbox lets the user operate on ALL the qualifications of each type and ALL the employees issued with them:
On the first and second pages of the form, there are two ListView components called SourceBox and DestinationBox respectively, while on the third page, the DestinationBox ListView is re-sized to cover the width of the form while the SourceBox ListView is hidden.
The relevant tables and their fields
used by the form are:
Table Name | Field Name | Purpose | |
Details.dbf (Employee details) | Emp_no | Employee Number | |
Surname | Surname | ||
Chrst_name | Given Name | ||
Jobname | Job Title | ||
Appntcod.dbf (Qualifications Lookup) | Appointmnt Qualification | Title | |
Key_Code | Qualification Type Qualifier | ||
Appdet.dbf (Employee Qualifications) | Appointmnt | Qualification Title | |
Key_Code | Qualification Type Qualifier | ||
Last_Used | Date Stamp | ||
Qualjobs.dbf (Job Title Qualifications) | Key_Code Qualification | Type Qualifier | |
Appointmnt | Qualification Title | ||
Jobname | Job Title | ||
Creating the ListView Components
If you have successfully added
the ListView component(s) to the form, the underlying code should look
something like this:
this.SOURCEBOX = new ACTIVEX(this) with (this.SOURCEBOX) height = 8.86 left = 2 top = 2.4545 width = 43.4286 pageno = 0 license = "9368265E-85FE-11d1-8BE3-0000F8754DA1" state = "DCFEEFCDBJBBBBBBBDCQBBBBMPCEB..." // Truncated classId = "{BDD1F04B-858B-11D1-B16A-00C0F0283628}" endwith |
|
I added the following two events
to the ActiveX Control:
with (this.SOURCEBOX.nativeObject) DblClick = class::MOVECURRENTRIGHTBUTTON_ONCLICK ColumnClick = class::NATIVEOBJECT_COLUMNCLICK endwith this.DESTINATIONBOX = new ACTIVEX(this)
|
|
I added the following two events
to the NativeObject of
the ActiveX control:
with (this.DESTINATIONBOX.nativeObject) DblClick = class::MOVECURRENTLEFTBUTTON_ONCLICK ColumnClick = class::NATIVEOBJECT_COLUMNCLICK endwith |
|
Adding the Column Headers and Customising the ListViews
Because my program needs to vary the Column Headers at runtime, I created a function called Prepare_Set() which is called at various times by certain user actions. Prepare_Set() sets up the ListViews appropriate to what the user is doing.
Therefore, this is the logical place to set up the Column Headers and other features. Here is a part of the code for Prepare_Set() which sets up the ListViews when page one of the Tabbox is selected.
Note that you must use the
NativeObject when referring
to the in-built properties of the control:
Function Prepare_Set // Set up the Source and Destination ListView controls // Initialise a variable «x» as a reference
to the SourceBox properties
// Clear the list of all existing elements and column
headers (thanks Gary White)
// Set the various Visual Basic properties for the ListView // Disable user editing of the elements
// Enable sorting in the ListView
// Set the ListView output to «report»
style
// Make the highlight bar stretch across the whole
row
// Don't hide whatever the user selects
// Turn the highlight bar another colour when the
mouse cursor is on it
// Allow or don’t allow the highlight bar to «follow»
the mouse cursor (I found it better off)
// Allow the user to re-arrange the column order
// Same for the Destination ListView
// If page one of the Tabbox is selected
// Assign Column Headers where
// Assign which column to initially
sort by (0 = the main item itself rather than subitems)
// Repeat for the DestinationBox,
this time adding two columns
// Populate the Source ListView
control with data from a rowset
// Populate the Destination ListView
control with data – this is more complex as it
|
|
When dBASE instantiates an ActiveX control, this control becomes a dBASE object. However, that object still has the properties, methods and functions set by its developer. In the case of the ListView control, its Visual Basic roots are revealed in the three lines of code above shown in red. For a dBL developer unfamiliar with the Visual Basic language, this piece of code is rather un-orthodox. So let’s give some explanation. The first of these lines adds a “parent” row to the Listbox. This must contain a value (in this case, x). The second line adds a sub-item to the parent row (on other words, another column). The third line assigns the value of y to this sub-item.
Moving the Elements Back and Forth
As in Ken Mayer’s
Mover.cc there are 4 ways
to move the elements: Move all left, Move selected left, Move all right
and Move selected right. Below are some snippets from the
MoveCurrentLeft() function:
Function MoveCurrentLeftButton_onClick // Move the currently selected item in DestinationBox
back to the SourceBox
// First, check to see if we’re working with an
empty list
// Now move just the currently selected element: // This bit of code sets the variable p to the index
of the next item
// Assign the variable x to the text of the selected
item
// Create a new element in the SourceBox ListView
and make it’s value
// Now remove the unwanted element from the DestinationBox
// And finally, Auto-Select the next item in the
DestinationBox
|
|
Programmatically Changing the Values of Elements in the ListViews
Attempting to do this successfully the Visual Basic way caused me many, many hours of chagrin and frustration. I could change the values without error using the method explained in the Visual Basic Reference but could never get the ListViews to actually display the changed data.
Finally I came up with a work-around that firstly saves the data in the element that is to have it’s ListItem and/or ListSubItem changed programmatically. It then removes the element and immediately adds it again with the changed value(s). Ugly, but it works good!
The following function changes
a date value in one of the ListSubItems at the user’s request.
Function UpdatePushbutton_onClick Dest_Box = Form.DestinationBox.nativeobject
// Assign variable p to the next item in the list
for later auto-selection
// Update just the element selected:
x = Dest_Box.ListItems(i).text
// Auto-Select the next item in the list
|
|
Saving the Data in the Lists Back to the Rowsets
Here is some code from the «Save»
button onClick() event
of the form. This is invoked by the user when finished moving the elements
back and forth to get the final required result. The routine firstly checks
the rowset for data not contained in the DestinationBox and deletes the
offending rows. Then it adds any new data in the DestinationBox back to
the rowset.
Function SaveButton_onClick // Delete any records not in the Destination List
// If the Destination List is empty, fuggettaboudit
!
// Check each item in the Destination List
|
|
Allowing the User to Sort the Elements
Here is the function referred to
in the constructor code for the ListViews which lets the user sort the
lists:
Function NativeObject_ColumnClick(ColumnHeader) this.SortKey = ColumnHeader.Index-1 If this.listItems.count <> 0 this.SelectedItem.EnsureVisible() Endif return |
|
Printing the Lists
This function uses temporary tables created in the form’s DataModule to store the elements in the ListViews before calling a standard report form to print them. There are probably much better ways to do this, perhaps using Vic McClung’s Printer Class but, c’est la guerre!
The code in the DataModule looks
like this:
this.PAGEONE = new QUERY() this.PAGEONE.parent = this with (this.PAGEONE) onClose = class::PAGEONE_ONCLOSE execute = class::PAGEONE_EXECUTE left = 2.5714 top = 5.1818 session = form.session1 database = form.database1 sql = "" active = true endwith function PageOne_execute
|
|
The function that prints the lists
looks like this:
Procedure ReportButton_onClick // First, empty the PageOne temporary table
// If the user has chosen the DestinationBox List
to be printed
// Duplicate the above for the SourceBox List
// Now print the damn thang
|
|
Selecting the First Item in the ListViews
These functions are called quite
often whenever the lists have been refreshed.
Function SelectFirstDest // Select the 1st item in the Destination List
Function SelectFirstSource // Select the 1st item in Source List
|
|
Displaying the Number of Elements in Each ListView
This little function continually
updates the count of elements in each ListView as elements are added and
removed. The sort of thing users appreciate!
Function ListCount Source_Box = Form.SourceBox.nativeobject Dest_Box = Form.DestinationBox.nativeobject newstring = substr(Form.SOURCETITLE.text,1,at("(",Form.SOURCETITLE.text)) Form.SOURCETITLE.text = newstring + ; ltrim(rtrim(str(Source_Box.ListItems.count))) + " records)" newstring = substr(Form.DESTTITLE.text,1,at("(",Form.DESTTITLE.text)) Form.DESTTITLE.text = newstring + ; ltrim(rtrim(str(Dest_Box.ListItems.count))) + " records)" return |
|
Conclusion
My overriding need to use the ListView was to have the capability of displaying multiple columns from tables in the list and to be able to scroll across the columns. I also needed a replacement for the rather ill-behaving VdB Grid that would also allow me to display multi-dimensional array data.
The ListView control has given me all these advantages and more in a well-behaved, very programmable, flexible and attractive control.
Clients who have used the control in my applications have remarked on the speed and user-friendliness of the control compared to earlier versions of the same applications that used single column lists or cumbersome grids.
For further information on the ListView Control itself, visit the Visual basic Reference site at: http://msdn.microsoft.com/library/default.asp?URL=/library/devprods/vs6/vbasic/vbcon98/vbstartpage.htm