2004.12.31 10:13 AM

ListView Column Header Events in VB

[Update: Some problem resolutions and explanations in this new post.]

Back in October 2003, I posted some info on autosizing ListView columns in VB. The info I provided was based on some code I happened to find on Randy Birch's respected Visual Basic Developers Resource Centre. Recently, a very nice fellow named Rawden Hoff with Nebula Systems happened onto my old post and followed its links to Mr. Birch's site where he found some code that did most of what he wanted, which was to detect the resizing of ListView columns. Unfortunately, the code on Mr. Birch's site, which was partially attributed to Karl E. Peterson and Zane Thomas owing to its use of HookMe.bas logic, was constructed in such a way as to make reuse across multiple forms or multiple ListView controls on a single form quite difficult. So, Mr. Rawden asked in my comments whether I could help:

I found Randy Birch's answer to my problems at http://vbnet.mvps.org/index.html?code/subclass/lvheadernotifications.htm.

When testing the code posted, I found that with my limited knowledge of API calls and callback events etc., I could not successfully modify the code to be reusable. The project I am currently working on has many forms with listview controls on, all of which I could do with catching the column resize for.

As the HookFunc function hard coded the form instance the listview control was on, I was out of my depth in finding a way to make the code reusable.

Any light you could shed on the problem would be greatly appreciated.

I took a quick look at the code on Mr. Birch's site and responded to Mr. Rawden:

I agree with you that its use of CopyMemory to create a reference to an instance of the target class (in this case a form) in order to make an early-bound call to its WindowProc method is unfortunate, as it certainly makes generalization harder, particularly in an app having lots of forms with lots of ListView controls.

Off the top of my head, it seems a simple alternative would be to:

1) Modify the HookWindow routine so that instead of using CopyMemory and SetProp to save a reference to the ListView's form in a dynamically created "ObjectPointer" property of the ListView control, just store a reference to the form in a module-level collection keyed on the ListView's hwnd (don't forget to CStr() the hwnd first).

2) Modify the HookFunc routine so that instead of using an explicitly dimensioned form variable to create a reference to the instance of the form via CopyMemory, simply use the ListView's hwnd to reference the form in the module-level collection and make the WindowProc call using CallByName:

HookFunc = CallByName(MyCollection(CStr(hwnd)), "WindowProc", VbMethod, hwnd, msg, wp, lp)

3) Modify UnhookWindow to remove the ListView.hwnd-keyed collection entry.

Mr. Rawden later contacted me directly to explain that he was having difficulty making the changes I suggested. Clearly a sample was in order. However, I figured any sample I produced would essentially repeat Mr. Birch's code with only minor modifications, and in so much as Mr. Birch maintains a fairly strict republishing copyright (which he kindly explained in a comment to my earlier post), I thought it best to first contact Mr. Birch, which I did:

I don't mind helping [Mr. Rawden], but wanted your permission to reproduce your code first, as this would essentially be your code with only minor modifications.

While waiting for Mr. Birch's reply, I went to work on a sample for Mr. Rawden. When I was finished, it looked nothing like Mr. Birch's original code:

  • I left out the whole InitCommonControls[Ex]/IsNewComctl32 module thing, as it seems hardly necessary these days (if you think you might be running this on a pre-IE3 Comctl32.dll system, please see Mr. Birch's original code).
  • I eliminated the CopyMemory and SetProp thing which was saving a reference to the ListView's form in a dynamically created "ObjectPointer" property of the ListView control.
  • I opted to maintain a module-level collection of monitored ListView controls, instead of their forms, as I'd originally suggested in my comment.
  • The hWnd received by the message hook is then used to retrieve a reference to the target ListView control and a call is made to a function in its parent form using VB's CallByName for processing the event.
  • All Windows message evaluation and categorization is done just once in the shared message hook routine before calling the ListView-specific event handler in its parent form, which keeps the form code simple.

The end result of all this is a nice little sub-system for easily catching and responding to the following ListView column header events for as many ListView controls as your VB project may contain:

Public Enum lvHeaderActions
  lvHeaderActionClick = 1
  lvHeaderActionRightClick = 2
  lvHeaderActionDividerDoubleClick = 3
  lvHeaderActionResizeBegin = 4
  lvHeaderActionResizeEnd = 5
  lvHeaderActionChange = 6
  lvHeaderActionDragBegin = 7
  lvHeaderActionDragEnd = 8
End Enum

Utilizing this new functionality is pretty simple. First, add the following line of code to the form load event for each ListView control whose column header events you want to trap (be sure to change "ListView1" to match your ListView control's name):

Call RegisterListView(ListView1)

Next, add this line to the form's unload event:

Call UnregisterListView(ListView1)

Finally, add a function to the form for each registered ListView control to be called whenever one of the column header events listed above is detected. The function is named for the ListView control followed by "_HeaderEvent" and has the following signature:

Public Function ListView1_HeaderEvent(ByVal Action As lvHeaderActions, ByVal Column As Long) As Boolean

  ' Action: Indicates the event type.
  ' Column: 0-based index of the column acted upon.

  ' Return True to cancel resize events.  You can do this conditionally based on Column.  Don't worry
  ' about checking the event type via action, as only resize events can be cancelled anyway.

End Function

That's it.

It's now been two weeks since I wrote Mr. Birch, and I've still not received a reply. And, at least by my own reckoning, my sample code is entirely different from Mr. Birch's original code. So, I'm going to go ahead and post my sample code now.

Here you go: ListViewHeaderEvents.zip (see my new post for the latest code)

The sample contains a VB6 project with two forms, with one of the forms having two ListView controls and the other having a single ListView control. Each of the ListView controls is registered to receive event notifications via ListView-specific functions in their respective forms. The functions receive an enumerated constant indicating the action that occurred (e.g., click, right-click, resize, etc.) along with the affected column number (0-based). You can set the function's return value to True to cause resize events to be cancelled. The only thing I'm doing in the sample's event functions now is writing the activity to the debug window, but I think you will see how to monitor the events to trigger additional activities. See the comments at the top of ListViewHeaders.bas for info on implementing the logic in your own projects.

As always, I'm offering no warranties and there's no liability assumed. This code is provided for demonstration purposes only.

Note that I've only tested this on the machine I developed it on, which is running Windows XP Professional SP1, using VB 6.0 SP5.

Let me know if you have any questions.


Comments

Hai
I executed the ListViewHeaderEvents.zip. Its very good. first of all thanks for the code. Since long i am searching web for right click event on listview headers. But i have one problem, while form unload, windows is terminating the application without any error. i am using VB6 with SP6. Please suggest me the solution. i also looking for "how to hide/unhide a column in list view control". please help me.

Thanks in advance.

YSReddy | 2005.01.08 01:05 AM

YSReddy,

Sorry I wasn't able to respond faster. Glad you could use the code.

Regarding the "termination of the application without any error", I'll need a little more info about what you mean. How, for instance, do you know that the application terminated during the form unload? I assume the application termination wasn't expected following the unload of the form, as this is the default behavior for VB form-based applications?

In terms of hiding a ListView column, off the top of my head I believe your only option is to set the corresponding column's ColumnHeader width to 0. In effect, this "hides" the column, but a user isn't prevented from resizing it "visible" again. However, if you combine this with the ListView header event code in the sample I provided, you can cancel subsequent resizes of the "hidden" column. For instance, if you were to add the following line to the bottom of Form2's Form_Load event:

MyListView.ColumnHeaders(2).Width = 0

This would "hide" the second column, and the existing sample code that prevents column 2's resizing would ensure that it stays "hidden".

Hope that helps. Good luck!

ewbi.develops | 2005.01.11 02:47 AM

Big thanks for this great piece of code but can I am getting some peculiar results and hoped you could help.

I am trying to keep the width and column order of two listviews synchronised. I use the ResizeEnd and DragEnd events to monitor the columnheaders in list A and then modify List B. However, the width property of the column that has been resized in List A does not get its new value until AFTER the ResizeEnd event is triggered. This means that the resizing is always one step behind! Am i doing anything wrong?

Dave | 2005.05.20 05:13 AM

Hi Dave,

I'm not having the problem you describe and am wondering if the problem might have to do with the fact that the event's Column parameter is 0-based, but the ListView control's ColumnHeaders collection is 1-based.

Using the sample project linked to in the post above, I added the following code to ListView1_HeaderEvent:

If Action = lvHeaderActionResizeEnd Then
If Column = 1 Then
ListView2.ColumnHeaders(Column + 1).Width = ListView1.ColumnHeaders(Column + 1).Width
End If
End If

This keeps the second column (Column=1) of the second ListView control sized the same as the first one.

Let me know if I've missed the mark or if the above code doesn't work for you. Good luck!

ewbi.develops | 2005.05.20 09:58 AM

Dave,

I've posted an update here:

http://ewbi.blogs.com/develops/2005/05/listview_column.html

Thanks again for taking the time to share your results.

ewbi.develops | 2005.05.24 05:22 PM

Hello My Friend i need help for you..i have source code about listview control and how save file avi in to directory. please am have your support.. and thanks

Muhalif | 2005.10.20 06:08 AM

Hello My Friend i need help for you..i have source code about listview control and how save file avi in to directory. please am have your support.. and thanks

Muhalif | 2005.10.20 06:09 AM

Muhalif,

Sorry for the delayed response (I've been moving cross country the last few weeks). Can you provide some additional information about what you're trying to do and what specifically isn't working for you? I'll try to help if I can.

ewbi.develops | 2005.11.08 03:52 PM

Thanks for the code. Solved my problem quickly and easily.

Rajinesh | 2006.08.03 01:32 AM

Rajinesh, cool - glad to hear it!

ewbi.develops | 2006.08.03 11:17 AM


TrackBack

TrackBack URL:  http://www.typepad.com/services/trackback/6a00d8341c7bd453ef00d8346a724e69e2

Listed below are links to weblogs that reference ListView Column Header Events in VB: