by Peter G. Aitken (c)1998
Originally published in Visual Developer magazine
One of my favorite sayings is Less is More because it is appropriate in so many areas of life. One of my favorite examples is web page design. Surely you have seen those horrid pages that are a seething mass of icons, animations, hover buttons, scrolling marquees, and every HTML and Java gimmick ever invented. They are slow to load, prone to bugs, confusing to use, and just plain ugly! The most attractive and usable pages are those that use the minimum of elements required for the page's task. Less is most definitely more in web page design!
Sometimes, however, less is not more. There's no getting away from it, sometimes more is more. This is certainly the approach Microsoft has taken in subsequent releases of Visual Basic. With each new version the list of controls, components, and features has continued to grow. This, of course, is one reason for the phenomenal success of Visual Basic. With so many goodies in the Visual Basic armamentarium, however, it can be difficult to keep track of what's available to you. A software component that's just what you need for your current project may be sitting there ready to use, but if you don't know about it, it is not going to do you much good.
To help you avoid this problem, I am devoting this column to a review of some of the most useful but least well known controls that are available for use in your Visual Basic projects. These are the Windows Common Controls, a set of 9 controls that is an integral part of Windows itself. Because these controls are not included in Visual Basic's intrinsic controls, they do not appear in the toolbox unless you put them there. This is the reason, I believe, why so many programmers are unaware of them. Let me tell you, I have seen some pretty broad smiles on the faces of programmers when I showed them these controls. If you are not already using them, I think you'll react the same way.
To make the Windows Common Controls available in your project, press Ctrl+T or select Project, Components to display the Components dialog box. On the Controls tab scroll down and place a checkmark next to the "Microsoft Windows Common Controls 6.0" entry, then click OK. The nine controls in this set will appear as icons in your toolbox.
In this column I will show you how to use some of the Windows Common Controls, concentrating on those that are relatively complex and non-intuitive. I have already covered the TreeView control in a previous column (the December/January 1997 issue). You can find the text of my earlier columns , plus some other Visual Basic-related stuff, on my web page at http://www.pgacon.com/computer.htm.
The TabStrip control is, in my opinion, one of the most useful of the Windows Common Controls. I am sure you have seen it in action many times, for example in the Visual Basic Options dialog box. It lets you create multi-page dialog boxes with each page represented by a tab at the top of the box. Click the associated tab to bring its page into view. With a TabStrip control you can put a lot of functionality in a single dialog box without making it too large or confusing.
The TabStrip control contains a collection of one or more Tab objects, with the collection itself called Tabs. Each Tab object represents one page in the control. Most of the properties associated with a TabStrip control affect the entire control, specifying things like the font used for the captions on the tabs and whether the tabs are displayed at the top or the bottom of the dialog box. A few properties apply to individual Tab objects, specifying for example the caption displayed on the Tab or the Tool Tip Text displayed when the user hovers the mouse pointer over the Tab. To work with TabStrip properties you will need to use its Property Pages as some properties are not displayed in the normal Visual Basic properties window. To access a control's Property Pages right-click the control and select Properties from the pop-up menu.
Tabs can be added to or removed from the control at design time or at run time. At design time display the control's Property Pages and display the Tabs tab. Use the Insert Tab and Delete Tab buttons to add and remove Tabs. Each Tab in a TabStrip control is identified by an index number, with indexes starting at 1. A Tab can also be identified by its Key property. You can assign any unique string to the Key property and then use that value to access the Tab at run time without worrying about the Tab's Index property, which might have changed if additional Tabs were added to the TabStrip control. I find it most useful to make a Tab's Key property the same as its Caption property.
You can access a specific Tab by means of either its Index or its Key property. Here are two ways to set the Caption property of the Tab object with Index 2 and Key "graphics." Assume the TabStrip control is named TabStrip1:
TabStrip1.Tabs.Item(2).Caption = "Options"
TabStrip1.Tabs.Item("graphics").Caption = "Options"
Note that because the Item property is the default property of Collection objects, the following two lines of code are exactly equivalent to the above:
TabStrip1.Tabs(2).Caption = "Options"
TabStrip1.Tabs("graphics").Caption = "Options"
To add a Tab at run time, use the Tabs collection's Add method. The syntax is as follows:
TabStrip1.Tabs.Add IndexNumber, Key, Caption
If you omit the IndexNumber argument the new Tab will be placed at the end of the collection. If you specify an IndexNumber argument that is already in use, existing Tabs will be "bumped up" to make room for the new Tab. You cannot specify an IndexNumber that is more than 1 higher than the highest Index already in use. To determine this value, which also gives the number of Tabs in the TabStrip, query the Tabs collection's Count property. Here's an example. Assuming your project has a TabStrip control named tabOptions the following code will add two new Tabs:
tabOptions.Tabs.Add , "printing", "Printing"
tabOptions.Tabs.Add , "display", "Display"
Each Tab can display a small image instead of or along with its caption. To display images you must first load the images into an ImageList control and then associate the ImageList control with the TabStrip control (further details on the ImageList control will be presented later). The ImageList control must be on the same form as the TabStrip. You create the association between the two controls either using the TabStrip's Property Pages (on the General page) or in code:
TabStrip1.ImageList = ImageList1
You then assign a specific image from the ImageList to a specific Tab using the Tabs page in the TabStrip control's Property Pages or in code. The following code assigns the image whose Index property in the ImageList control is 2 to the Tab with Index 1 in the TabStrip control:
TabStrip1.Tabs(1).Image = 2
There are some other properties that control various aspects of the TabStrip's appearance but I am going to let you explore these on your own. With some experimentation and the assistance of the Visual Basic Help system I am sure you'll be able to figure them out. More important is that you understand how other controls are displayed on the pages of a TabStrip control, and how you manipulate the control to meet the requirements of your program.
You might think that each Tab in a TabStrip control provides a separate container on which you can place the other controls - Text Boxes, Labels, etc. - and then when the user clicks a Tab the associated controls are automatically displayed. This approach makes sense but is not the way Microsoft did things. You must provide your own container for the controls to be displayed on each Tab, then hide and display the appropriate containers when the user clicks one of the tabs on the TabStrip control. Here's the general sequence of steps. You can use either Frame or Picture Box controls as containers, but the lower overhead of the Frame control makes it the preferred choice. Here's what you need to do:
At design time:
1. Create a control array of Frame objects with one member for each Tab on the TabStrip control. Each Frame will be associated with a Tab, based on their Index properties. Because control arrays start at Index of 0 and the Tabs collection starts at an Index of 1, you should associate Frame(n) with Tab(n+1).
2. On each Frame, place the various controls that you want displayed when the user clicks the corresponding Tab.
3. Create a module-level variable that will keep track of which Tab is currently displayed.
Then, at run time:
4. In the Form_Load event specify the Tab and Frame that will be displayed initially. Also, use the Move method to position each Frame to completely cover the Tab's client area.
5. In the TabStrip's Clicked event procedure, read the SelectedItem.Index property to determine which Tab was clicked. If it is the same as the currently displayed Tab, do nothing and exit the Sub.
6. If a different Tab was clicked, set the Visible property of the currently displayed Frame to False and the Visible property of the Frame corresponding to the just-clicked Tab to True.
7. Set the global variable to reflect the newly displayed Tab.
Here is some example code to both set up the TabStrip when the form loads and to respond to clicks. Assume that the TabStrip is named TabStrip1, the array of Frame controls is called Frame1, and the global variable for keeping track of the current Tab is called CurrentTab
Private Sub Form_Load()
Dim i As Integer
' Move all frames to fill client area. Set
' Visible to False and BorderStyle to None for all.
For i = 0 To Frame1.Count - 1
.Move TabStrip1.ClientLeft, _
.Visible = False
.BorderStyle = 0
' Make Tab 1 active and its Frame visible.
CurrentTab = 1
Set TabStrip1.SelectedItem = TabStrip1.Tabs(CurrentTab)
Frame1(CurrentTab - 1).Visible = True
Private Sub TabStrip1_Click()
If TabStrip1.SelectedItem.Index = CurrentTab _
Then Exit Sub
Frame1(TabStrip1.SelectedItem.Index - 1).Visible _
Frame1(CurrentTab - 1).Visible = False
CurrentTab = TabStrip1.SelectedItem.Index
At run time it is best to have the Frame controls display without a border or caption, which can be done by setting the BorderStyle property to None. At design time, however, it is useful to be able to see the Frame and its caption, so you might want to consider setting the BorderStyle property to None in code at run time as was done in the sample code above. During design, how do you work with a stack of Frame controls that are by necessity overlapping each other? The trick is to use the Order command on the Format menu. Use Bring to Front to put a Frame on "top" of the stack so you can add controls to it and arrange them. Use Send to Back to move the current Frame to the "bottom" of the stack so you can work on another Frame.
The ImageList control is designed to let you create and manage a group of images. This control always works "behind the scenes" and you never actually see it on a form. It does not display images, but rather is used to make a set of images available to other controls that use them, such as the TabStrip control discussed above. An ImageList control can also perform certain types of manipulations on the images it contains, such as overlaying one image on another. While there is no restriction on the size of images placed in an ImageList control, it is generally used for small images such as icons.
An ImageList control contains a collection called ListImages containing ListImage objects. As with all collections each object is identified by a 1-based Index property giving its position in the collection, and by an optional Key property. You can add images to an ImageList control at design time or at run time. At design time, display the control's Property Pages and select the Images tab. Browse your disk for the image file, and optionally specify a Key property for the image. Repeat until you have added all the required images.
At run time you use the Add method to add images to the ImageList control. The syntax is similar to using the Add method for other collections. Since, however, we are dealing with loading a picture from a disk file the LoadPicture function must be used. The following example adds the image in APPLE.BMP to the ImageList and assigns it a Key property of "apple."
ImageList1.ListImages.Add , "apple", LoadPicture("apple.bmp")
While the images loaded into an ImageList control do not have to all be the same size, the control constrains them all to the same size once they are in the control. This makes sense because for most uses you would want all the images to appear at the same size when they are displayed, for example on the tabs of a TabStrip control. What determines this size? At design time, you can specify the image size on the General page in the ImageList's Property Pages. This is permitted only if the control contains no images at the time. Otherwise the native size of the first image loaded into the control will be used for all other images.
The ImageList control's Overlay method permits you to overlay one image on another. Since all images are the same size, overlaying images does not make much sense unless part of the top image is transparent so that the bottom image can show through. This is achieved with the ImageList control 's MaskColor property. Whatever color you specify in this property will become transparent in the top image,,mitting the second image to show through. Suppose you have an image consisting of a yellow lightning bolt on a green background, and you want to overlay the lightning bolt over another image. First, set the MaskColor property as follows:
ImageList1.MaskColor = vbGreen
Note that vbGreen is one of Visual Basic's intrinsic constants. You can find more details on intrinsic constants by searching the Help system. Once the mask color is set, call the Overlay method to perform the action. The following code overlays the image with Index 2 over the picture with Index 1 and displays the result in a Picture Box.
Pbox1.Picture = ImageList1.Overlay 1, 2
You could also create the overlay image and add it as a new image in the same ImageList control:
ImageList1.ListImages.Add , "overlaid image", _
ImageList1.Overlay 1, 2
You could also specify the images to be overlaid by their Key properties instead of using the Index property.
The ListView control permits you to display lists of items. I'll bet you are already familiar with this control! Take a look at the Windows Explorer; the right panel in the Explorer window is a ListView control (this is, after all, why they are called Common Controls - they are used in many different Windows applications). There are four types of view available in a ListView control:
- Icon view. Descriptive text with a large icon.
- SmallIcon view. Descriptive text with a small icon.
- List. A sorted list of items
- Report. Similar to List view but permits display of subitems.
You can get a feel for what these views are like by opening Windows Explorer and using the commands on its View menu to try the different views out. When a ListView control is in Icon view, the user can use the mouse to drag items around, rearranging them within the control as desired. Drag-and-drop is also operative, if it is enabled. SmallIcon view offers similar rearranging capabilities, but the items are displayed in list format with small icons rather than as large icons. List view appears similar to SmallIcon view, however you cannot rearrange items and the list is by default sorted. Report view offers the option of two or more columns in the list, permitting you to display additional details about each item. For example, in the Windows Explorer, Report view (called Details) displays not only the file name but also its size, creation date, and so on.
As was the case with the other controls we have been discussing, the ListView control makes use of collections. Each item displayed in the control is a ListItem object; all of these objects are within the control's ListItems collection. There is one other collection used by this control, as we will soon see.
Each ListView control can have two ImageList controls associated with it. The ImageList controls are used to store the icons that will be used in Icon and SmallIcon view. You can specify the ImageList controls at design time using the ListView controls Property Pages, or you can set them at run-time:
ListView1.Icons = ImageList1
ListView1.SmallIcons = ImageList2
Adding items to ListView control is done with (of course) the ListItems collections Add method. Heres the syntax:
ListView1.ListItems.Add index, key, text, icon, smallIcon
Index and Key are the same as for all the other collections we have seen the numerical position of the new item in the collection, and a unique identifying string. Text is the data to be displayed. The final two arguments identify the icons to be displayed with the item in Icon and SmallIcon views, respectively. These images are taken from the ImageList control or controls that were associated with the ListView control, as described earlier. You can specify an image using either its Index property or its Key property in the ImageList control. Here is how you would add a new item to a ListView control, at the next available Index position, specifying an icon by Index and a SmallIcon by Key:
ListView1.ListItems.Add , "apricot", "Apricot", 2, "fruit"
You can have the ListView control automatically sort the items it displays. The control has three properties that determine how the sorting is done:
The second collection that the ListView control uses is the ColumnHeaders collection. As you can probably guess by now this collection contains ColumnHeader objects. Each item in this collection corresponds to a column of data that is displayed in the ListView control, and of course is relevant only when the control is in Report view. You can add to this collection at design time, using the controls Property Pages, or at run time with the Add method. The syntax is quite similar to the Add method for the other collections we have dealt with:
ListView1.ColumnHeaders.Add index, key, text, width, alignment
The index argument specifies the position of the new ColumnHeader in the collection. If this argument is omitted the new entry is placed at the end of the collection. Because the columns are displayed across the top of the ListView control in numerical order (left to right) you can use the Index argument to determine display order.
The key argument is a unique string that is used to refer to the object in code, and the Text argument is the text that is displayed in the column heading. Width specifies the width of the column using the scale units of the container that the ListView control is placed in. You can omit the Width argument in which case the default value of 1440 Twips (1 inch) is used.
Alignment specifies how the both column header text and the data in the column are aligned. Possible values are lvwColumnLeft (value 0, the default), lvwColumnRight (value 1) and lvwColumnCenter (value 2). I am sure that you can figure out the meanings of these constrants! Be aware that the first column must be left-aligned and if you try to set a different alignment an error occurs.
Here is some code that will add three column headers to a ListView control, each with a width of 1000 twips.
ListView1.ColumnHeaders.Add 1, , "Last Name", 1000
ListView1.ColumnHeaders.Add 2, , "First Name", 1000
ListView1.ColumnHeaders.Add 3, , "Middle Initial", 1000
Once you have set up your columns, how do you add the details that will be displayed there? Each ListItem object in the ListView control has a SubItems property that consists of an array of strings. This array holds the detail data that is displayed when the ListView control is in Report view mode. This is a 1-based array; the first element holds the detail item to be displayed in the first extra column (the first, or left-most column displays the text stored in the ListItems Text property). Note that the ColumnHeader with Index 2 refers to the column containing the detail data in SubItems(1). The detail information must be added at run time, as shown here:
Dim lvi As ListItem
Set lvi = ListView1.ListItems.Add(, "Aitken", "Aitken")
lvi.SubItems(1) = "Peter"
lvi.SubItems(2) = "G."
ListView1.ListItems.Add , "Clinton", "Clinton"
Set lvi = ListView1.ListItems("Clinton")
lvi.SubItems(1) = "William"
lvi.SubItems(2) = "J."
ListView1.ListItems.Add , "Edison", "Edison"
ListView1.ListItems("Edison").SubItems(1) = "Thomas"
ListView1.ListItems("Edison").SubItems(2) = "A."
You can see that I used three different methods of achieving the same thing adding a ListItem and setting two of its SubItems. All of these approaches are exactly equivalent, and demonstrate the flexibility of the Visual Basic Collections model. After executing the two code fragments above, the ListView control will appear as in Figure 1.
Figure 1 A ListView control displaying items in Report view. [[bv1.tif]]
User access to items displayed in a ListView control is provided by means of the SelectedItem property. This property returns a reference to the ListItem object that is currently selected that is, highlighted. For example, place the following code in a ListView controls Click event procedure to display a message box identifying the list item clicked by the user:
Private Sub ListView1_Click()
Dim item As ListItem
Set item = ListView1.SelectedItem
You can also use the SelectedItem property to programmatically specify which item in the list will be highlighted. This code will highlight the second item in the list:
Set ListView1.SelectedItem = ListView1.ListItems(2)
I will be the first to admit that this has been a rather cursory introduction to these three controls. In order to get the most out of them, you will have to spend some time exploring their other properties, events, and methods. Now that you know the basics, however, that should be a relatively simple task. Don't forget to take a look at the other Windows common controls, which include such useful things as a Slider, a Progress bar, and a Status Bar. There's even a second set of common controls, identified in the Components list as Microsoft Windows Common Controls - 2. Here you'll find the Up-Down control and the Animation control. And remember - it's not always a bad thing to be a control freak!