2003.10.20 10:21 PM

Autosize ListView Columns

While browsing through Windows Management Instrumentation scripting samples (more on using WMI to track down network configurations later), I ran into an interesting bit of code in this otherwise unrelated sample on VBnet. Buried in the sample is a snippet showing how to autosize the columns of a ListView control in Report mode using the SendMessage API. This may be old news to everyone else, but I was glad to see it, and the timing was perfect.

I'd like to show you the relevant parts of the sample's code, but the author, Randy Birch, has unfortunately decided that I "may not reproduce or publish this code on any web site, online service, or distribute as source on any media without express permission." Okay. Well, I'm not going to call Mr. Birch at this hour to get his permission, so I guess I'll show you how I use the technique in my code.

Private Declare Function SendMessage _
                Lib "user32" _
                Alias "SendMessageA" _
                (ByVal hwnd As Long, _
                 ByVal wMsg As Long, _
                 ByVal wParam As Long, _
                 lParam As Any) As Long

Private Sub AutosizeColumns(ByVal TargetListView As ListView)

  Const SET_COLUMN_WIDTH    As Long = 4126
  Const AUTOSIZE_USEHEADER  As Long = -2

  Dim lngColumn As Long

  For lngColumn = 0 To (TargetListView.ColumnHeaders.Count - 1)
   
    Call SendMessage(TargetListView.hwnd, _
                     SET_COLUMN_WIDTH, _
                     lngColumn, _
                     ByVal AUTOSIZE_USEHEADER)
        
  Next lngColumn

End Sub

Please pardon the excessive line-continuations. I'm trying to avoid cutting off code samples at lower resolutions.

This technique offers a couple of real bonuses. First, it sizes columns to the wider of the ColumnHeader.Text or the widest ListSubItem.Text. Second, the last column is automatically sized to the remaining ListView width, if it's larger than the column's header or widest value. Cool. Maybe there's an easier way to accomplish this in VB6, but I don't know what it is.

By the way, the ListView control has changed in .NET (see System.Windows.Forms.ListView). It now exposes a Columns property containing a collection of ColumnHeader objects, and these now expose a Width property that can be set to adjust the width at runtime. Specifically, -1 sets the width to the widest column text, while -2 sets the width to the larger of the column header text or the remaining ListView width (which is cool, but still doesn't get you a complete autosize as above, which considers the column text, column header text, and remaining ListView width).

Here's an interesting Code Project article by Chris Beckett that improves on the new .NET ListView options by causing the last column to autosize with any ListView control size change. Note that it is Mr. Beckett who points out that a Width of -2 gets you the remaining ListView width behavior; the MS documentation doesn't mention it.


Comments

Hi Eric,

I found your piece about autosizing ListView cols by Googling. I've known about this technique for some time, but recently I've found a (.NET WinForms) listview in my app where it doesn't work - any header set to width -2 simply disappears! I can set them to any +ve value and they reappear.

This is particularly strange because there are plenty of listviews in the same app, even one in the same form, where it's working fine, and I cannot see any difference either in my code or that autogenerated by the form designer.

Just wondered whether you had ever come across this. If so, I'd love to hear about it. If not, no worries!

Best wishes

Dave

Dave Hallett | 2003.12.20 04:15 PM

Hi Dave,

Unfortunately (fortunately?), I haven't seen that, but my experience with ListView controls in .NET has been limited to a few exercises, including playing with Mr. Beckett's sample. I'm trying to imagine what I would look for in the non-working ListView you describe, and can only think of a couple of things:

1) Perhaps the execution order of setting the ColumnHeader.Width vs. setting the actual headings and/or column contents is causing a problem (i.e., sizing before filling or showing)?

2) Maybe the non-working ListView is contained in an auto-sizing parent of some sort that's affecting its behavior?

Also, I'm wondering whether after setting the previously -2 Width to some other value, thus showing the incorrectly hidden column again, if you then set it to -2 again, does it work correctly the second time?

Just some thoughts. Hope you figure it out! Please let us know if you learn anything more.

ewbi.develops | 2003.12.20 10:35 PM

Thanks for the quick response Eric. You put me on the right track. The problem seems to be that this ListView wants to have it's column widths set *after* being filled. If I do that, all is fine.

This is odd, as all the other ListViews in the app seem quite happy to have their col widths set first and be filled second. I trapped the Resize event, and that doesn't seem to happening. Setting the width positive and then to -2 before filling, still hides the column. So I still don't understand.

But at least I have a work-around.

Thanks!

Dave

Dave Hallett | 2003.12.21 05:24 AM

In case anyone is interested in a URL to the demo of this code alone, please see http://vbnet.mvps.org/code/comctl/lvcolumnautosize.htm

There are about 30 different Listview-specific demos under the main page at http://vbnet.mvps.org/code/comctl/index.html

Randy

Randy Birch | 2004.09.14 10:20 AM

... and I should mention, the primary reason for asking that code not be republished is simply to ensure anyone using source from my site has the opportunity to obtain the most up-to-date and bug-free code.

In The Beginning, I found that many sites had copied the code prior to bugs/issues having been addressed. This is simply a mechanism to ensure developers have working code ... life's too short to waste time tracking down bugs in downloaded code!

While the listiview autosize example is very simple, and to my recollection has had only one update to the code itself, there are many, many samples on my site involving complicated API methodologies.

By informing developers, via the 'copyright', that they can always obtain the most up-to-date version of the code directly from my site, and by involving those same developers in the code so as to identifying issues that I am unable to determine (because I can't run every OS combination to test each demo), the result is possibly the most tested and resiliant code around.

Randy

Randy Birch | 2004.09.14 10:27 AM

Randy,

Thanks for the explanation and the links. I haven't read my post above in nearly a year and it now seems to come off harsher than I intended. I only thought the prohibition was "unfortunate" because I wanted to repeat the logic verbatim, along with the accompanying link, but didn't want to wait to get permission (it was after midnight). In the end, I had to implement similar logic anyway, so I simply published my own implementation.

Anyhow, this particular post has gotten quite a bit of Google traffic in the last year. I sure hope it's steering a lot of folks over to http://www.mvps.org as there's a ton of great stuff there.

Thanks again for taking the time.

ewbi.develops | 2004.09.14 11:11 PM

Hi Eric,

I have done a bit of research trying to find a way of catching the resize event of the ListView control (for VB6). 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.

I emailed Randy, but am waiting for a reply so I thought I’d give you a shot as you were the next most relevant (and optimistic) page I had found (in English) during my Google searches.

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

Thanks in advance.

Rawden.

Rawden Hoff | 2004.12.16 05:15 AM

Hi Rawden,

That's the first time I've seen Mr. Birch's ListView header subclassing code, which relies on the ubiquitous HookMe.bas logic by Karl E. Peterson and Zane Thomas. 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.

Good luck!

ewbi.develops | 2004.12.16 09:51 AM

I've posted a sample project illustrating the suggestions I made to Mr. Rawden regarding a generalized routine for trapping ListView column header events:

http://ewbi.blogs.com/develops/2004/12/trap_listview_h.html

ewbi.develops | 2004.12.31 12:03 PM

Thanks, this code was very helpfull, And works perfictly (better than mine).

Mark Farmer | 2005.02.08 11:15 AM

Mark - Thanks for the feedback!

ewbi.develops | 2005.02.09 12:44 AM

Nice article by William Vaughn from the January 2004 edition of Hardcore Visual Basic regarding sizing DataGrid columns:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnhcvb04/html/vb04a7.asp

"According to Bill Vaughn, one of the most compelling features in the first Visual Studio .NET beta was the ability to automatically size a DataGrid control using a single Boolean property setting. Unfortunately, this feature was dropped from the shipping versions of the .NET Framework—both versions 1.0 and 1.1. And although he expects DataGrid sizing to reappear some day, power users won't want to wait until then. This month, Bill shows you how to reclaim this 'lost' functionality."

ewbi.develops | 2005.02.22 09:00 AM



Post a Comment

 
  (optional)
  (no html)
 
   


TrackBack

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

Listed below are links to weblogs that reference Autosize ListView Columns: