Introduction
It happens to every CPian at some point in time. They actually have to submit an article. For the first time in my .NET development career, I couldn't find an answer to my problem. So, here it is, my first article. It is a fairly simple control, but I found several other questions on this topic without an answer.
Background
The main purpose for this control is to make an easier to use system for non-technical people. The ListView
seems like a good way of displaying information, without having to go with a full blown Grid control. The problem is there is no way to lock the size of the column headers. I can hear the support calls now, like "I can't see the totals anymore". Issues like this are difficult to troubleshoot and cause frustration on the user's part. Plus this application which I was developing would be touch screen driven, so it would be easy to move the columns, but somewhat difficult to get them back to the right spot.
So the search began for a C# solution. But nothing seemed to be available. I did find a C++ article on this topic by Paul DiLascia from the June 2003 MSDN Magazine - C++ Q & A article. Now I was armed with the basic knowledge, but this had to be translated to C#. Being new to .NET, I headed to CodeProject and found Georgi Atanasov's article on Customizing the Header Control in a ListView. This filled in the details needed to make the control function as I wanted.
Using the code
The control is quite simple once all of the details are available. First we will subclass the standard ListView
control. The mouse tracking messages need to be captured and discarded to prevent sizing of the column headers.
public class myListView : System.Windows.Forms.ListView
{
protected override void WndProc( ref Message message )
{
const int WM_NOTIFY = 0x004E;
const int HDN_FIRST = (0-300);
const int HDN_BEGINTRACKA = (HDN_FIRST-6);
const int HDN_BEGINTRACKW = (HDN_FIRST-26);
bool callBase = true;
switch ( message.Msg )
{
case WM_NOTIFY:
NMHDR nmhdr = (NMHDR)message.GetLParam(typeof(NMHDR));
switch(nmhdr.code)
{
case HDN_BEGINTRACKA:
case HDN_BEGINTRACKW:
if(locked)
{
message.Result = (IntPtr)1;
callBase = false;
}
break ;
}
break;
}
if(callBase)
{
base.WndProc( ref message ) ;
}
}
}
The begin tracking notification is part of the WM_NOTIFY
message. Notice that both the ANSI and UNICODE versions of the message are captured.
This works great. The user can no longer size the column headers, but it doesn't look quite right. The cursor still changes to the sizing cursor on mouse over. To handle this, the messages going to the header of the ListView
must be captured. When this is done, the WM_SETCURSOR
can be discarded. Now the cursor does not change and everything looks correct.
private class HeaderControl : NativeWindow
{
private myListView parentListView = null;
[DllImport("User32.dll",CharSet = CharSet.Auto,SetLastError=true)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg,
IntPtr wParam, IntPtr lParam);
public HeaderControl(myListView m)
{
parentListView = m;
IntPtr header = SendMessage(m.Handle,
(0x1000+31), IntPtr.Zero, IntPtr.Zero);
this.AssignHandle(header);
}
protected override void WndProc(ref Message message)
{
const int WM_LBUTTONDBLCLK = 0x0203;
const int WM_SETCURSOR = 0x0020;
bool callBase = true;
switch ( message.Msg )
{
case WM_SETCURSOR:
if(parentListView.LockColumnSize)
{
message.Result = (IntPtr)1;
callBase = false;
}
break;
}
if(callBase)
{
base.WndProc( ref message ) ;
}
}
}
The control is looking good now. However, there is still a side effect. The user can double click on the header and the column auto-sizes. This turns out to be easy, we just capture and discard the double click for the header. Here is the updated WndProc()
to correct this problem.
private class HeaderControl : NativeWindow
{
protected override void WndProc(ref Message message)
{
const int WM_LBUTTONDBLCLK = 0x0203;
const int WM_SETCURSOR = 0x0020;
bool callBase = true;
switch ( message.Msg )
{
case WM_LBUTTONDBLCLK:
case WM_SETCURSOR:
if(parentListView.LockColumnSize)
{
message.Result = (IntPtr)1;
callBase = false;
}
break;
}
if(callBase)
{
base.WndProc( ref message ) ;
}
}
}
Thanks to the team on Code Project, yet another side effect was discovered. Geert Baeyaert pointed out that CTRL+ was not being handled. The ListView
resizes all columns on CTRL+. So after a bit of digging, I found how to capture and discard the CTRL+ key stroke. Here is the bit of code to handle CTRL+:
protected override void OnKeyDown(KeyEventArgs e)
{
if(e.KeyValue==107 && e.Modifiers==Keys.Control && locked)
{
e.Handled = true;
}
else
{
base.OnKeyDown (e);
}
}
The control now functions as desired. The option to enable and disable the size of the columns is wrapped in a property called "LockColumnSize
", which is shown under the properties in the Behavior section. The property is active at design time, so the column width will need to be set before locking the columns.
Points of Interest
Programming .NET is a whole new way of thinking. My background is programming C for embedded systems. Sometimes the embedded world is easier. You can look at the schematics and datasheets and they clearly tell you what is possible and how to do it. In .NET, there is an ocean of information and it is somewhat difficult to find the solution you are looking for.
I hope you find the control useful and I look forward to hearing comments. I need all the help I can get in learning how to do .NET code properly.
History
- 2005-09-20: Modified to capture CTRL+ to prevent resizing of all columns.