Introduction
It has always been a pet-peeve of mine, that I prefer grid or list controls
to auto size their columns to fit the control canvas. Some of you may remember
the early OCX and ActiveX controls that implemented auto-size last column
properties to resolve this problem. Essentially, the last column in the grid or
list view is automatically expanded to take up any available space. As the form
is resized, or column widths are resized by the user at runtime, the last
column size is automatically increased or decreased to keep the column aligned
with the control right edge.
I love the Windows Explorer look, and I love the ListView control features
and appearance - we just have to do something with that last column! Having
scoured the internet trying to find examples or clues, the end result that, finding a solution proved to be difficult, but implementing the solution
was actually very simple.
Setting the Column Size
In fact, the ListView
class does provide a ColumnHeader.Width
property. The MSDN
documentation describes two special values related to auto-sizing the column, as
follows: "To adjust the width of the longest item in the column, set the
Width
property to -1. To auto size to the width of the column heading, set
the Width
property to -2".
A special feature of setting the Width
to
-2, is that it ALSO automatically expands the last column to the
right-edge of the control. This would seem to solve the problem, except that it
does it a one-time auto-size that is not preserved if the user resizes any of
the column widths at runtime, or the ListView
control is set to resize with the
form.
Responding to Change
When I first approached this problem, I figured the answer would lie in
responding to some event when the user resized a column or the control was
resized, or sub-classing the ListView
to gain access to some protected
interfaces. In fact, the ColumnHeaderCollection
and ColumnHeader
classes used to
provide the implementation of the ListView.Columns
member are completely buried
inside the parent control.
My next approach was to look to the Win32 API, but faced
the same obstacles. I could find no examples of how to access the ListView
column headers directly.
The final step to any tricky problem like this was to
look to the Window message loop. A handy feature of .NET controls is they expose
the WndProc
method for handling messages received by the control. By analyzing
the messages that were pumped through the ListView
control while columns widths
were resized, or the control was resized, I discovered that the hidden
ColumnHeader
class sends a WM_PAINT
message to the parent control to
notify it during a column resize, and as a final step when the user finishes
resizing the column. The WM_PAINT
method is also the last message
processed when the user resizes the ListView
control.
The Solution
To implement the the auto-size of the last column, subclass the ListView
control, and
override the WndProc
method.
protected override void WndProc( ref Message message )
{
const int WM_PAINT = 0xf ;
switch ( message.Msg )
{
case WM_PAINT:
if ( this.View == View.Details && this.Columns.Count > 0 )
this.Columns[this.Columns.Count - 1].Width = -2 ;
break ;
}
base.WndProc( ref message ) ;
}