The ListView Control


ListView (which is mapped in Figure 11-17) doesn’t have as many problems as TreeView, but you might need a few tricks to make it do everything you want.


For starters, you might want to store data that isn’t visible in a ListView, which doesn’t have the ItemData array common to ListBox-type controls. This was a serious limitation for some applications in Visual Basic version 4. Fortunately, in version 5, the Tag property of ListItems has changed from String to Variant type. This allows you to cram any data you want—Double, Currency, Date, Object—into each ListView item. Don’t confuse the Tag property of the ListView control with the Tag property of each ListItem. The one and only Tag property of ListView still has String type, but the Tag property of each ListItem is Variant. The same applies to all the other collections in the Common Controls OCX—ListImages in ImageList, Nodes in TreeView, Panels in StatusBar, Tabs in TabStrip, and Buttons in Toolbar.


Another problem with ListViews is that they don’t have an ItemDblClick event, but you need to identify double-clicks on ListView items to know when to open them. ListViews do have a DblClick event, but it won’t tell you which item was clicked. You can work around this limitation by using the SelectedItem property in the DblClick event.

Private Sub lvwFiles_DblClick()
Dim item As ListItem
Set item = lvwFiles.SelectedItem
If item Is Nothing Then
Debug.Print "Double-clicked column: ?"
Else
Debug.Print "Double-clicked item: " & item.Text
End If
End Sub


Figure 11-17. A ListView map.


This only works if you double-click on the item, which points out one of the most annoying limitation of ListViews. You can’t identify clicks or double-clicks on any column other than the first one. This fits the normal designed use of a ListView, but, in fact, I’d like to use a ListView as a kind of Grid and recognize clicks on any cell. I’d like to create a simple XGrid control by delegating to a ListView control, but the ListView doesn’t provide the column information I need.


The solution involves translating the x and y coordinates of a click into their corresponding row and column. Once you have the row, you can figure out the item. Let’s start with that. Thanks to hardcore programmer Jim Collins for the following API code. To identify clicks, you use MouseDown rather than Click because this event has coordinates. The DblClick event doesn’t have coordinates, but you can save them in module-level variables in MouseDown and then read the variables in DblClick. Once you have the coordinates, you can get the item from the following function:

Function ListItemFromLinePosition(lvw As ListView, _
ByVal x As Single, _
ByVal Y As Single) As ListItem
Dim rc As RECT, i As Integer, c As Long, dy As Long
c = lvw.ListItems.Count
If c = 0 Then Exit Function
' Get the height of a single item
rc.Left = LVIR_BOUNDS
SendMessage lvw.hWnd, LVM_GETITEMRECT, ByVal 0&, rc
dy = rc.bottom - rc.Top
' Calculate the index of the item under the mouse pointer
i = lvw.GetFirstVisible.Index - 1 + _
((yList \ Screen.TwipsPerPixelY) - 3) / dy
' Return the item (if any)
If i > 0 And i <= c Then
Set ListItemFromLinePosition = lvwFiles.ListItems(i)
End If
End Function

Here’s how you could call this code from the DblClick event of a ListView:

Private Sub lvwFiles_DblClick()
' Use module-level xList and yList saved from MouseDown event
Dim item As ListItem
' Determine if user double-clicked anywhere on the line
Set item = ListItemFromLinePosition(lvwFiles, xList, yList)
If Not item Is Nothing Then
Debug.Print "You double-clicked on the line of item: " & item.Text
End If
' Make any click on the line select the item
Set lvwFiles.SelectedItem = item
End Sub

I find it very annoying when a ListView application ignores or takes random actions when I click on a column. The last line of the sample above selects the item when you click on a column. This might not be the right action for every program, but users click columns for a reason. Don’t just ignore them.


Unfortunately, figuring out the column in the SubItems collection when a user clicks a column is more difficult than figuring out the line. I don’t know the solution. My first idea was to add up the widths of the ColumnHeaders collection to figure out what ­column the x position is on. That would probably work when the ListView is scrolled all the way left, but when you scroll right you’re lost. The ListView window has a notification message called LVN_COLUMN­CLICK. Maybe you can subclass that message to analyze column clicks. That’s where I’d start my experiments.


One Last Challenge


All the tools are now in place to create the Hardcore Hacker’s Object Chop Shop. Let’s take a little tour through this not-so-Basic development factory. It’s a set of add-ins that teach the Visual Basic IDE some new tricks.


For example, the Chop Shop knows resources. It can build RES files from RC files. It can build RC files from dialogs that allow you to select existing resources or create new ones, which leads to a second point.


The Chop Shop knows pictures. It can create or edit picture files containing bitmaps, icons, cursors, and metafiles. Iconically speaking, it knows small icons, large icons, and icons of any other size you want. It also knows about standard-sized bitmaps such as toolbar buttons. It can put these pictures in resource files, or it can load them into picture properties at design time.


The Chop Shop also knows projects. It can switch easily between modes where components are compiled or in source code.


The Chop Shop knows debugging. It handles asserts and log messages transparently.

And finally the Chop Shop knows source code. It can automatically call out to a real programmer’s editor, or it can intercept keystrokes in the code windows and translate them into macros. Either way, you can remap your keyboard and can get the code templates and other advanced editing features that hardcore programmers expect.

Sound good? Well, don’t expect to find support for all of these features in the VBIDE add-in model. You’re going to have to subclass a few windows and do other low-level API hacking to get what you want. But of course, that’s no problem. In fact, there’s nothing between you and that dream environment but a few weekends—OK, some weeknights too—of Hardcore Visual Basic.