HeaderControl

 

Copyright © 2005 David A. Ferguson <www.davidaferguson.com>. Anyone may use this software in anyway they like. NO WARRANTY.

Revised 2005-04-02


Summary

HeaderControl is a C# windows application that demonstrates wrapping a native Win32 control for use in C#.  The program is no where near complete.  Only a few events a properties have been completed.  No WinForms design time support is provided.  If you are looking for a finished control, this is not it.  If you are curious about how to wrap a native control you may find some insights here.

 

Click here to download the project.

 

The header control is a part of the common controls.  It displays the a series of columns that you can resize, drag and drop and other manipulations.  The header control is used internally by the list view control (in detail mode) to display the column headers.

 

HeaderControl.cs This is the C# wrapper.
Win32.cs The definitions necessary to P/Invoke the native windows control.
MainFrame.cs Small demo form

 

 

Normally you would put HeaderControl.cs in a separate DLL but I am just experimenting here.

 

The demo app should let you switch between button like headers and flat non-clickable headers by changing the checkbox.  When button style is enabled, clicking (and double clicking) on header will update the label controls with index of the header clicked and a count (to let you see that multiple clicks are occuring).  Notice that every double click event is preceded by a click event.  This is a feature of windows.  You can resize the headers in either mode, but I don't generate tracking or change events in this demo.

 

The first thing I noticed was how easy it was to get a basic control on the form.  This is all you really need to display a Win32 control.

 

public class HeaderControl : Control
{
    protected override CreateParams CreateParams
    {
        get
        {
            SetStyle(ControlStyles.UserPaint, false);  // this was non-obvious
            CreateParams cp = base.CreateParams;
            cp.ClassName = Win32.WC_HEADER;
            cp.Style |= _style;
            return cp;
        }
    }
}

 

Inheriting from Control does most of the heavy lifting.  The call to Control.SetStyle() was non-obvious to me (thanks Google).  I guess Control makes the assumption that you will be doing all the drawing and optimizes some internal plumbing.  Anyway, setting it to false allows the Windows common control to do the paint.  If you don't set it to false Control is expecting you to do all the painting in the OnPaint function, and you won't see anything.

 

The virtual CreateParams should be familiar from MFC.  These are the parameters that will eventually be used in the CreateWindow() call.  The class name for the system Header control is in the MSDN docs as 'SysHeader32'.  The normally defined 'C' macro WC_HEADER is defined the the Win32.cs file.  Notice that a call to the base class CreateParams is made first and the our style flags are ORed in.  This does provide a way for us to turn off a style set in the base class, but so far I have only added Header Control specific styles.

 

The Header Control supporst many styles.  I only experimented with the HDS_BUTTONS style.  If set the header will be drawen to look like a button and will respond to click (and double click) events.  The style bit needs to be set in two places, our internal _style flag, and in the window (if it exists).  We must keep the internal style updated in case WinForms ever decides to recreated the OS window, in which case CreateParams would need updated style.

 

Inserting a column header is just an exercise in mapping parameters into the HDITEM structure.  I don't support bitmaps in this demo, though the Header Control itself can have bitmaps.  The interesting thing here was the sort marker.  These are the little up and down arrows that show up when a column is sorted.  It seems simple enough to ask for sort markers but it didn't work.  The problem is that by default Windows will load an older version of the common controls library.  To get around this you have to create a manifest file.  You can read all the gory details here, yuk!

 

The final thing we need is to provide some events.  The Control class makes it trival to insert code in the message loop, simply override the WndProc method.  The message is passed by ref so thats fast, cool.  I had almost forgot why we trap the OCM_NOTIFY message instead of the WM_NOTIFY.  Thinking back to the early days you may remember that SDK controls would send the WM_NOTIFY message to the parent window.  This was great when the window message was the way that other objects were notified of change.  For an encapsulated control you often want to handle the message in the control not in the parent.  The solution is to reflect messages back to the control.  So, the Header Control does send WM_NOTIFY to the parent but the parent reflects it right back as an OCM_NOTIFY.

 

The Message class has a nice casting feature that lets you get infamous LPARAM out as something usefull, without having to do unsafe pointer acess.

 

Win32.NMHDR nmBase = (Win32.NMHDR) m.GetLParam(typeof(Win32.NMHDR));

 

There is a lot more that needs to be done to make this a user friendly control.  All of the Header Control features need to be exposed (bitmaps, drag and drop, sort, colmun repositiong.  A method to make the column definitions designable needs to be implemented (probably a collection of header defintions, like ListView).  And much more.  Oh ya, maybe some documenation too...naaaa.

 

EOF