Introduction
I was recently tasked with figuring out a way to disable column resizing in a ListView
control. After chasing leads on a Google search, reading documentation, and a little trial and error, I finally figured out how to do it. The solution is simple, elegant, and may be used as a basis for doing other things with the ListView
control using the same technique.
Background
When the ListView
control is placed in report mode, an additional child window is added to the control to hold column headers. The Windows class for this child window is a SysHeader32
class. Unfortunately, it appears that Microsoft neglected to expose a wrapper for this control in the .NET Framework, and provided no simple means to access to it.
Ultimately, this means that a whole host of Windows messages are being sent to the SysHeader32
window of your ListView
control that fires no events for your control and to which you cannot respond.
Solution
The solution is a three step process:
- Get a Windows handle for the
ListView
's SysHeader32
child window.
- Use the Windows handle to somehow take over message handling for its window.
- Write code to respond to the messages of interest to accomplish our goal.
Step 1
The first step is easy. Remember the good ol' days of Win32 API calls? Well...they still work as well in Visual Basic .NET as they did in VB6. Put the following declarations in a scope reachable by your form load event:
Private Declare Function GetWindow Lib "user32" Alias "GetWindow" _
(ByVal hwnd As IntPtr, ByVal wCmd As Integer) As IntPtr
Private Const GW_CHILD As Integer = 5
Dim SysHdr32Handle As IntPtr
and in your form load event, add the following:
SysHdr32Handle = GetWindow(ListView1.Handle, GW_CHILD)
The code above assumes you've created a form window and drawn a ListView
control on it. Since the ListView
control only has one child window (the SysHeader32
window), we can rest easy knowing this call will obtain its Windows Handle.
Step 2
We now need a way to intercept messages being sent to the child window. Fortunately, the .NET Framework Class Library contains a class designed especially for this purpose - the NativeWindow
class. The MSDN help description for the class states:
Provides a low-level encapsulation of a window handle and a window procedure.
To use the class, we need to create a new class that inherits the NativeWindow
class so we can override the WndProc
message handling function to do what we want. Add the following class definition to your form code:
Private Class ListViewHeader
Inherits System.Windows.Forms.NativeWindow
Private ptrHWnd As IntPtr
Protected Overrides Sub WndProc(ByRef m As _
System.Windows.Forms.Message)
MyBase.WndProc(m)
End Sub
Protected Overrides Sub Finalize()
Me.ReleaseHandle()
MyBase.Finalize()
End Sub
Public Sub New(ByVal ControlHandle As IntPtr)
ptrHWnd = ControlHandle
Me.AssignHandle(ptrHWnd)
End Sub
End Class
Of course, we'll need a form global variable declaration to work with, and we'll need to initialize our instance of the class. Put the following declaration in your form class declaration:
Private ListViewHeader1 As ListViewHeader
Then add the following line of code to your form load event:
SysHdr32Handle = _
GetWindow(ListView1.Handle, GW_CHILD)
ListViewHeader1 = New ListViewHeader(SysHdr32Handle)
Step 3
Now that we have a way to receive messages, we can add code to do something about them. In this simple example, we are going to intercept WM_SETCURSOR
and WM_LBUTTONDOWN
messages and throw them away to prevent the user from seeing a resize column cursor and from being able to resize the columns. Modify the WndProc
method of our ListViewHeader
class as follows:
Protected Overrides Sub WndProc(ByRef m _
As System.Windows.Forms.Message)
Select Case m.Msg
Case Is = &H20
m.Msg = 0
Case Is = &H201
m.Msg = 0
End Select
MyBase.WndProc(m)
End Sub
Conclusion
The above example gives you a bare-bones method of getting a handle on those SysHeader32
messages. As such, the example is rather crude. For instance, what if you wanted to disable column resizing for two ListView
controls? As written above, you'd have to create two distinct classes. A robust implementation would add events to the class definition and fire them when Windows messages are received in the WndProc
method, thus allowing the creator of the class instance to deal with them. For now, however, this will get you started.