Explore the VB6 Control Toolbox

by Francesco Balena

Reprinted with permission from Visual Basic Programmer's Journal, 10/98, Volume 8, Issue 12, Copyright 1998, Fawcette Technical Publications, Palo Alto, CA, USA. To subscribe, call 1-800-848-5523, 650-833-7100, visit www.vbpj.com, or visit The Development Exchange

Visual Basic 6.0 comes with many new controls and many enhancements to existing controls.

Visual Basic 1.0 kick-started the component industry several years ago with its VBX controls. Each new version of the VB has added more controls—some by Microsoft, some licensed from other companies—and VB programmers have become less and less dependent on third-party products. VB6 continues this trend; it includes several brand new controls and a handful of interesting enhancements to existing controls.

In this article I will give you a quick tour of the new and updated controls in VB6. In some cases, I will also provide code samples. There are so many things to say that I simply can't cover all the new features in this article. But I'll point you in the right direction and let you focus on what interests you most.

However, be advised that I am working with a late beta (RC1), so it is possible that a few features will be different in the release version of Visual Basic 6.0. Remember this when you try out the code and always refer to the on-line help when things don't work as described. For the same reason, I won't mention a few bugs that I encountered when testing these controls because I am confident that they will be fixed in the definitive release.

Common Controls enhanced

A lot of time has passed since the Windows Common Controls debuted with Windows 95. Almost every Internet Explorer release or operating system service pack released since has included updated controls in DLL files, but the new features of these controls weren't easy to access from VB because the corresponding OCX has never been enhanced significantly. VB6 changes this; it includes several new OCX files that provide access to the controls in the system DLLs. For example, MSCOMCTL.ocx replaces the COMCTL32.ocx file and includes an improved version of all the usual Windows Common Controls.

The ListView control sports a number of interesting new features (see Figure 1). It supports check boxes for each item (CheckBoxes property), full-row selection (FullRowSelect property), optional dividing lines (GridLines property), and a background bitmap (Picture and PictureAlignment properties). Column headers in Report mode can host an icon and can be reordered optionally using the mouse at run time if you set the AllowColumnReorder property to True. If you set the CheckBoxes property to True, the ListView control activates an ItemCheck event when an item is checked or unchecked:

Figure 1: Everything You've Ever Wanted in a ListView Control. The ListView control includes individual Bold and ForeColor attributes for each subitem; tooltips; checkboxes; full-row selection; re-orderable column headers with optional icons; hot-tracking; background picture (not shown here), and a lot more.

Private Sub ListView1_ItemCheck _
   (ByVal Item As ComctlLib.ListItem)
      If Item.Checked Then
         ' user checked an item
      Else
         ' user unchecked an item
      End If
End Sub

The new ListView control also exposes a HotTracking property that lets you select any item by simply moving the mouse cursor over it.

ListItem objects, which correspond to individual lines of the ListView control, include several new display attributes, such as Bold and Checked. These objects also expose the ListSubItems collection, which replaces the SubItems array. You can create new sub-items using this collection's Add method, and you can also specify a different icon, ForeColor property, Bold attribute, and a tooltip text for each sub-item. ListSubItem objects also support a Tag, so you can associate data with them without using auxiliary arrays or hidden columns.

The TreeView control has been enhanced too, but to a lesser degree. Like the ListView control, the TreeView control supports checkboxes next to each node, the selection of full rows, and the HotTracking feature. You can also decide whether users can expand with a single click by setting the SingleSel property to True. The control exposes three new events, NodeCheck (which fires when a checkbox is clicked), Scroll, and Validate.

The most interesting new feature of the Toolbar control is that it finally supports the "flat" style that you see in apps such as Internet Explorer. Implementing this functionality is as simple as assigning the tbrFlat constant to the Style property, at either design-time or run time. This is particularly cool because it means you can customize the appearance of your toolbars on-the-fly. Note that the Toolbar control doesn't expose a HotTracking property; the control automatically enables the "hot tracking" feature when you use flat toolbars (see Figure 2).

Figure 2: VB6's Controls Add Flexibility, Power. Is VB6 a serious threat to ActiveX Control independent vendors? Probably not, yet it is stunning how many visual and functional features you can now add to your applications without ever purchasing a commercial or shareware OCX. Here are the new Toolbar and TabStrip controls in action.

VB6 also lets you use the new tbrDropDown style to attach drop-down menus to toolbar buttons. You create such menus at design-time in the Toolbar property page, at runtime when the form loads, or even when the user clicks the button. You can also customize each menu line through its Text, Visible, Enabled, and Tag properties. One drawback of the Toolbar control is that it doesn't offer support for check marks or secondary submenus.

The improved Toolbar control supports three companion ImageList controls: one for buttons in the normal state (the ImageList property), one for disabled buttons (DisabledImageList property), and one for buttons under the mouse cursor when "hot tracking" is enabled (HotImageList property). Each button on a Toolbar control has only one index for the three ImageList controls; a button's state that determines which list property the button uses.

The Toolbar control exposes a few new events as well. The ButtonDropDown event fires when a user clicks on the down arrow next to a button with the tbrDropDown style. This event lets you customize the dropdown menu before it becomes visible. The ButtonMenuClick event fires when a user selects a menu item:

Sub Toolbar1_ButtonMenuClick _
   (ByVal ButtonMenu As ButtonMenu) 
      Select Case ButtonMenu.Index 
         Case 1 
            ' first item selected 
         Case 2 
            ' second item selected 
      End Select
End Sub

VB6 also includes a significantly enhanced TabStrip. For example, you can use its new HotTracking property to select a tab automatically when the user moves the mouse over it, and you can use the MultiSelect property to let the user select multiple tabs when the style is TabButton. However, the most appealing new feature is the TabStyle property, which lets you create an interesting visual effect with rows of facing tabs (see Figure 2).

There is little worth noting about the remaining Windows Common Controls. The ProgressBar control now supports an Orientation property that you can use to create vertical progress bars, the Scrolling property lets you build segmented and solid progress bars. The Slider control also contains an interesting new feature. You can now associate it with descriptive text (the Text property) that "follows" the mouse while you drag the slider indicator. Finally, the ImageList control now supports GIF images.

What's new with Common Controls

The MSCOMCTL.ocx file includes a brand new Windows Common control, the ImageCombo control. The FileOpen and FileSave common dialogs use this control to acquire the list of drives in the upper portion of the dialog. If you think this control is nothing more than a plain combo box with support for icons, think again. The ImageCombo control resembles a TreeView control more than a standard combo box. For example, the control includes neither AddItem nor RemoveItem methods because the ImageCombo correctly employs a more object-oriented syntax. Instead, you must invoke the Add method of the ComboItems collection to add a new ComboItem object, which corresponds to an individual item in the control. For example, take a look at this code to produce an ImageCombo control (see Figure 3):

Figure 3: Does It Look Familiar? You can use the ImageCombo control to build drop-down lists of objects with independent icons and indentation, and create great dialogs that closely mimic the FileOpen and FileSave common dialogs.

Dim desktop As ComboItem
With ImageCombo1.ComboItems
   Set desktop = .Add(, , "Desktop", _
      "desktop", , 0)
   .Add , , "My Computer", "mycomp", ,2
   .Add , , "Floppy (A:)", "floppy", ,4
   .Add , , "(C:)", "drive", , 4
   .Add , , "Windows", "folder", , 6
   .Add , , "System", "folder", , 8
   .Add , , "Recycled", "wastebin", , 6
   .Add , , "(D:)", "drive", , 4
   .Add , , "(E:)", "drive", , 4
End With

The last argument of the Add method indicates the indentation level of each ComboItem object (each unit equals ten pixels). Rather than set the ListIndex property, you select an item using the SelectedItem property:

Set ImageCombo1.SelectedItem = desktop

You can show a different icon whether the item is currently selected or not, acting on the ComboItem's SelImage property:

' items 5 and 6 above are folders
With ImageCombo1
   .ComboItems(5).SelImage="openfolder"
   .ComboItems(6).SelImage="openfolder"
End With

Finally, you can scroll the drop-down list programmatically using the SetFirstVisible method, retrieving whichever ComboItem is currently on top of the list using the GetFirstVisible method:

' show the beginning of the list
ImageCombo1.SetFirstVisible = desktop

Another file, MSCOMCT2.ocx, derives from the now-discontinued COMCT232.ocx control. MSCOMCT2.ocx contains the Animation and the UpDown controls, as well as three new controls: MonthView, DateTimePicker, and FlatScrollBar.

The FlatScrollBar is the simplest of the group, and its name says it all. It's a scrollbar control with an optional "flat" appearance that can contribute to the modern look of your applications. You can drop the control on a form and change the its orientation by acting on the Orientation property. Another neat feature is the ability to disable one arrow selectively. You usually do this in the Change event when the scrollbar's Value reaches its minimum or maximum value:

Private Sub FlatScrollBar1_Change()
   With FlatScrollBar1
      If.Value = .Min Then
         ' disable the Up/Left arrow
         .Arrows = fsbRightDown
      ElseIf.Value =.Max Then
         ' disable the Down/Right arrow
         .Arrows = fsbLeftUp
      Else
         ' enable both arrows
         .Arrows = fsbBoth
      End If
   End With
End Sub
Create Calendars in two flavors

Saying that MonthView is a calendar control doesn't do justice to its capabilities (see Figure 4). You can use the control to display up to twelve months at once by setting the MonthRows and MonthColumns properties. You also have the option to hide the "Today" label near the bottom border (ShowToday property). The calendar can also show number of weeks since January 1 (ShowWeekNumbers property), and you can decide which day should be considered the first one in the week (StartOfWeek property).

Figure 4: Ever Seen an Italian Calendar? You don't have to worry about localizing your software for other languages if you use the MonthView common control. You can jump to a given month using a drop-down menu, and you can highlight a range of dates. This figure also shows when I am preparing the article you're reading right now (note the minor bug; the "Today" string is not localized!).

You can also select the color for most of the calendar's visible items: the main area; the month/year title; and the color of "trailing days," the days that belong to the previous or following month.

Your code can use the Value property or the Day, Month, Year, and Week properties to read and modify the date that is currently selected. The MinDate and MaxDate properties set the range of selectable dates. If the MultiSelect property is True, end users can highlight a number of consecutive dates, up to the value stored in MaxSelCount property. The default property for MaxSelCount is seven days. You can use the SelStart and SelEnd properties to determine the selected range.

You can further customize the calendar appearance by adding some custom code in the GetDayBold event procedure (see Listing 1).

Listing 1 (VB6):

Sub MonthView1_GetDayBold(ByVal StartDate As Date, _
   ByVal Count As Integer, State() As Boolean)
      Dim i As Long, d As Date
      d = StartDate
      For i = 0 To Count - 1
         If Weekday(d) = vbSunday Then
            ' mark all Sundays
            State(i) = True
         ElseIf Month(d) = 12 And Day(d) = 25 Then
            ' Xmas time
            State(i) = True
         Else
            ' deal here with other holidays ...
         End If
         d = d + 1
      Next

End Sub

Listing 1: Honor Your Holidays. The GetDayBold event fires whenever a new group of dates becomes visible in a MonthView control. It is passed the first visible date, the number of visible days, and an array of Boolean. Each element is in a one-to-one relationship with the visible day. You must set to True all the array items corresponding to the dates that you want to display with bold characters. This example shows how you to highlight all Sundays and other holidays.

Other custom events allow you to tailor the calendar behavior to your specific needs. For example, DateClick and DateDblClick fire when the user clicks on a valid date, and SelChange is invoked when a user selects a new date or date range. The HitTest method lets you understand which portion of the control the mouse cursor is currently over. This method proves useful for showing custom tooltips, appointments for a given day, and so on.

The MSCOMCT2.ocx file includes another calendar control, the DateTimePicker control. This is a textbox control for entering date and time data, and it has many interesting capabilities. If you set the UpDown property to True, the control behaves as a text box. You advance through date and time fields using the Left and Right arrows, and modify values using Up and Down arrows (or the spin button on the left).

However, a down arrow replaces the spin button when the UpDown property is set to False, and a full-fledged drop-down control appears when a user clicks on this arrow (see Figure 5). Although this control doesn't support multiple monthly views, it is apparent that this calendar is "borrowed" from the MonthView control. Thus, it isn't a coincidence that the DateTimePicker control shares many properties with the MonthView control, including Value, Day, Month, Year, MinDate, MaxDate, and all the properties that affect the calendar's color attributes.

Figure 5: Two Controls in One. You can use the DateTimePicker control as a simple textbox for entering date and times (above), or you can use it in drop-down calendar mode (below). In the first case, you can increase or decrease individual components' values using the arrow keys or the spin buttons on the left; in the second case, you display the calendar with the Alt-Down key, or by clicking on the down arrow.

The DateTimePicker control has a number of interesting peculiarities of its own, however. For example, you can decide to display a checkbox besides the date (setting the CheckBox property to True). If the user doesn't want to select a particular date, he or she can clear that checkbox, in which case the control's Value property returns Null.

The real power of this control is the way it lets customize you its editable area. Apart from the usual formats (short date, long date and time), you can also select an additional "custom" format that uses a syntax similar to VBA's Format function:

DTPicker1.Format = dtpCustom
DTPicker1.CustomFormat = "hh:mm"

You can also add custom fields by inserting a sequence of one or more X's. For example, you might want to add a field that displays how many days have elapsed since January 1, and another field that reports the season's name. This requires that you set up two distinct, custom fields:

DTPicker1.Format = dtpCustom
DTPicker1.CustomFormat = _
   "(XX) MMM dd, yyyy [XXX]"

The DateTimePicker control knows nothing about these two custom fields, so it's up to you to tell the control how large the fields are and what values it should display in them. You set up custom fields' maximum width inside the FormatSize event. This event is called only when the form loads or when the CustomFormat property changes.

Visual Basic fires a Format event for each custom field when a new date is displayed in the control; you must respond by setting the proper value for it. You must also write the appropriate code in the CallbackKeyDown event procedure if you want custom fields to react to arrow keys (see Listing 2).

Listing 2:

Private Sub DTPicker1_Format(ByVal CallbackField _
   As String, FormattedString As String)
      Dim v As Date, y As Integer
      v = DTPicker1.Value
      y = DTPicker1.Year
      Select Case CallbackField
         Case "XX"
            ' days from January 1st
            FormattedString = 1 + DateDiff("d", _
               DateSerial(y, 1, 1), v) 
         Case "XXX"
            ' retrieve the season's name using a 
            ' separate function (not shown here)
            FormattedString = SeasonName(v)
      End Select
End Sub

Private Sub DTPicker1_FormatSize(ByVal CallbackField _
   As String, Size As Integer)
      Select Case CallbackField
         Case "XX"
            ' days can be max 3 digits
            Size = 3
         Case "XXX"
            ' max length of season's name is six
            Size = 6
      End Select
End Sub

Private Sub DTPicker1_CallbackKeyDown(ByVal KeyCode _
   As Integer, ByVal Shift As Integer, ByVal _
   CallbackField As String, CallbackDate As Date)
      If CallbackField = "XX" Then
         If KeyCode = vbKeyDown Then
            CallbackDate = DTPicker1.Value - 1
         ElseIf KeyCode = vbKeyUp Then
            CallbackDate = DTPicker1.Value + 1
         End If
      End If
End Sub

Listing 2: Get Complete Control Over DateTimePicker Fields. The Format event fires when the DateTimePicker control needs to display a value in a custom sub-field. You must therefore discern which sub-field is being requested, and return the corresponding value in the FormattedString argument. The FormatSize event fires only once, when the control needs to learn how wide is each custom field. The CallbackKeyDown event fires whenever the end user presses a key on a custom sub-field, and gives you the opportunity to react to Up/Down and PgUp/PgDn keys.

Hitch a ride with ADO

The most important additions to VB6 involve data access. If you want to be trendy and create business applications the VB6 way, you should forget about the Data and RemoteData controls, and jump on the ADO wagon. The outdated Data and RemoteData controls are still supported for backward compatibility, but the new data-aware controls and their new binding features work only with the newer ADO Data control.

The ADO Data control resembles older Data controls in appearance, but these similarities are only skin deep. For example, you can connect this control to a number of different data sources: a Data Link File (a special file that stores information about the database's position and attributes), an ODBC Data Source Name, or a custom connection string (see Figure 6). In the last case, the ADO Control filters a few attributes for its own use, such as the Provider and File Name attributes, and passes the others unmodified to the data source.

Figure 6: ADO Data Control Adds Choices. (a) You can create new Data Link files for your data source from within the Property0 Page of the ADO Data control, (b) you can connect it to an ODBC DSN, or (c) you can provide a custom connection string. In each of these cases, you should then jump to the RecordSource tab to select a table, a stored procedure, or a SQL query that feeds data rows to the control.

The ADO Data control exposes an impressive number of properties, but most of them will look familiar if you know the ADO programming model. The CommandType property denotes which kind of ADO Command object provides the actual data. For example, it indicates adCmdStoredProc for a stored procedure or adCmdText for a textual SQL query string. The CursorLocation property indicates where the cursor is stored (adUseClient or adUseServer for client-side and server-side cursors, respectively), and the CursorType property sets the type of cursor (dynamic, keyset, static, and forward-only). You can set the LockType property to select which lock is enforced (read-only, pessimistic, optimistic and batch optimistic). Space precludes going into all these options in depth, but, if you have some experience with client/server and RDO programming, the switch to ADO is not as difficult as it might seem at first.

The ADO Data control is supercharged with a lot of new events. You can trap almost every action performed on data, such as when a field changes (WillChangeField and FieldChangeComplete events), when a record is modified (WillChangeRecord and ChangeRecordComplete events), when the pointer to current record moves (WillMove and MoveComplete events), or when the end of recordset is reached (EndOfRecordset event).

A data control is useless without an arsenal of powerful bound controls. The ADO Data control works with most VB6's intrinsic controls, as well as a few external controls, including the MonthView, DateTimePicker, and ImageCombo controls.

Two more external ActiveX controls, DataList and DataCombo, serve as the ADO counterparts of the old DBList and DBCombo controls. The interfaces exposed by these controls are identical to their non-ADO equivalents, so I won't describe them in more detail.

The power of data binding manifests itself when you link the ADO Data control to a grid. Visual Basic 6.0 comes with three new grid-like data-aware controls: DataGrid, Hierarchical FlexGrid, and DataRepeater. The DataGrid control is source-code compatible with the DBGrid control, except that it doesn't support unbound mode. This might seem a major limitation at first glance, but the truth is that it doesn't affect the control's usefulness or versatility because you can bind the control to a data-aware class that wraps the data in memory (see the sidebar, "Data Binding under Visual Basic 6.0").

The MSFlexGrid control has been greatly improved and is now named Hierarchical FlexGrid. The name is extremely appropriate because this incredible control can show hierarchical ADO recordsets in their entirety; you don't need to write a single line of code. A hierarchical recordset is a powerful ADO concept that represents multilevel parent-child relationships among tables in a database using a single recordset object whose fields contain its dependent recordset objects.

Rather than create such complex ADO objects through code or intricate SQL statements, you simply design them in a DataEnvironment designer, then drag the Command object and drop it on a form using the right-mouse button. When you release the button, a popup menu appears and gives you a choice of several bound controls, a simple grid, and a hierarchical grid (see Figure 7).

Figure 7: Four Tables, Three Relationships, No Code. I created this form using drag-and-drop from a DataEnvironment window connected to the ubiquitous Biblio.mdb database. The first relationship is between the Author table and the Author_Titles table, which in turn is related to the Titles table that is finally related to the Publisher table. The rows in the Hierarchical Flex Grid control can be expanded and collapsed to show or hide all the fields in dependent tables after a given level.

The DataRepeater control is a hybrid control. In other words, it works in conjunction with another ActiveX control, typically a UserControl that you have built previously. At run time, this control "duplicates" the base ActiveX control and binds its properties to a database (see Figure 8). While using the DataRepeater control is not as immediate as other data-aware components, it gives you unprecedented flexibility. You can change the contained control at run time, get a notification when the user clicks on another row, scrolls the list, and more. The control also lets you embed any ActiveX control as a child control, so you can include pictures, calendars, buttons and icons, and anything else your imagination suggests.

Figure 8: Feel the Magic of the DataRepeater Control. This control lets you save resources because there is only one "real" instance of the child control running: all the controls except the one on the current row are just images drawn there by the DataRepeater control. Unfortunately, can't use UserControl modules private to a project because the DataRepeater control can use only registered ActiveX Controls.

VB6 includes so many new and enhanced controls that many programmers will find it convenient to upgrade if only for what you find in the control Toolbox. Undoubtedly, VB programmers will continue to buy third-party controls to cover their most sophisticated user interface requirements, but it is evident that you can find anything you need for building nearly every kind of application right in the VB6 package. ?

CODE ONLINE

You can find all the code published in this issue of VBPJ on The Development Exchange (DevX) at http://www.vbpj.com. For details, please see "Get Extra Code in DevX's Premier Club" in Letters to the Editor.

Explore the VB6 Control Toolbox

Locator+ Codes

Listings for the entire issue (free Registered Level): VBPJ1098

Listings for this article only (subscriber Premier Level): FB1098

Data Binding Under VB6

Visual Basic has been targeted at creating business applications since VB3, which introduced the Data control and the concept of bound controls. While the Data control and bound controls changed little in VB4 and VB5, VB6 changes everything.

VB6 still ships the old Data and RemoteData controls, but you should no longer consider these controls the preferred means to display data from a database because they have been replaced by the ADO Data control.

You link a data-aware control to an ADO Data control through its DataSource property. This is the same way you linked controls to the Data and RemoteData controls in previous versions of VB. The binding mechanism is much more flexible than in the past, however. First, you can assign the DataSource property to an ADO Data control even at run time, which you can't do with the Data and RemoteData control. Second, you can assign an ADO Recordset object to the DataSource property. This means that you can bind a control to a data source without depending on an existing ADO Data control. Third, you can create your own classes and ActiveX controls that work as data sources, and then assign them to the DataSource property. This is important because it adds incredible flexibility to your programs.

For example, many legacy DOS applications written in QuickBasic use binary files, which are difficult to deal with under VB. If you create a suitable class that wraps that particular data, you can have your Windows application coexist with the DOS counterpart without any problem until the transition is complete. Plus you don't have to forgo any advanced VB features such as data binding in the meantime. When a port is complete, you simply throw away this class and let your app work with a more modern SQL Server or ODBC source. You don't have to change one line of code in the rest of the application. Talk about scalability!

Another great way to exploit this opportunity is to bind controls to memory objects such as arrays of UDTs or matrices of strings or variants. All you have to do is wrap a class around your data, set the class's DataSourceBehavior property to vbDataSource, and write the code that performs the data transfer. You can expect to see many commercial and shareware ActiveX controls that wrap around any type of data, including some esoteric data sources such as memory-mapped files, serial ports, HTML pages, and so on.

Finally, you can connect the DataSource property of a bound control to a DataEnvironment object. VB6 comes with the DataEnvironment ActiveX Designer, which lets you visually establish your data sources, queries, relations, and more. You must set an additional item when you connect a bound control to a DataEnviroment object: the DataMember property (see Figure A). You can set the DataField property after you complete this step, just as you used to do in VB5 and previous versions.

Figure A (related to sidebar on Data-binding): ADO Increases Binding Options. Visual Basic 6.0 lets you bind controls to a lot of difference data sources. At design-time a control can be bound to an ADO Data control on the same form, or to a DataEnvironment object defined in the current project. If you are binding to a DataEnvironment, you must also select which particular ADO Command object should be used, and assign it to the DataMember property.

Download the code for this article here