Message control implementation.
In my previous post I described some history associated with the subclassing technique on .NET CF and promised to you to show my latest implementation of the subclassing control - Message control (I understand that the word "control" in the name is a little bit misleading because it's not a windows Control, but a helper class). Besides the fact that it replaces the window procedure, the MessageControl class handles some more complicated scenarios. For example, very often you need to sublcass the control's parent (Form), but you can have more than one control on the Form. In this case you would be "stealing" the window procedure from one another. So what the MessageControl is doing - it's checking if the particular window has already been subclassed and makes sure that there's only one instance of the same window procedure is been subclassed. Take a look at the following method which is a part of the MessageControl class:
/// <summary>
/// Subclasses the window.
/// </summary>
/// <param name="messageHost">Instance of the class that implements IMessage</param>
/// <param name="handle">Handle of the window to subclass</param>
/// <returns>An instance of the MessageControl</returns>
public static MessageControl Subclass(IMessage messageHost, IntPtr handle)
{
MessageControl msgControl = null;
// Check if we already subclassed this window
if (handleList.Keys.Contains(handle))
{
// We already subclassed it. Retrieve the MessageControl
msgControl = handleList[handle];
if (messageHost is Control)
{
((Control)messageHost).HandleDestroyed +=
new EventHandler(messageHost_HandleDestroyed);
}
// Add the message host to array list
msgControl.AddHost(messageHost);
}
else // New window
{
// Create new instance of the MessageControl
msgControl = new MessageControl();
// Associate it with the handle
handleList[handle] = msgControl;
// Add message host to array list
msgControl.AddHost(messageHost);
// Subclass the window
msgControl.Subclass(handle);
}
return msgControl;
}
As you can see from the code above this method is a factory method that will return an instance of the MessageControl to the caller. It also accepts the IMessage interface as a parameter which looks like this:
public interface IMessage
{
void WndProc(ref Microsoft.WindowsCE.Forms.Message m);
}
So all what is required for a class to receive the messages is to implement the IMessage interface. I've also added a few extention methods to make subclassing a little bit more user friendly by adding the ControlExtention class that implements a few helper methods:
/// <summary>
/// Enables subclassing of the Control.
/// </summary>
/// <param name="control">An instance of the Control.</param>
public static void EnableSubclassing(this Control control)
{
IMessage messageHost = control as IMessage;
if (messageHost != null)
{
MessageControl.Subclass(messageHost, control.Handle);
}
}
/// <summary>
/// Enables subclassing of the parent of the Control.
/// </summary>
/// <param name="control">An instance of the Control.</param>
public static void EnableParentSubclassing(this Control control)
{
IMessage messageHost = control as IMessage;
if (messageHost != null)
{
// Check if the parent is not null
if (control.Parent != null)
{
MessageControl.Subclass(messageHost,
control.Parent.Handle);
}
else
{
throw new ArgumentException("Cannot subclass.
Parent is null");
}
}
}
So to demonstrate the usage of these classes I decided to re-vamp one of my old samples (which, by the way, has a bug - it doesn't check for CBN_DROPDOWN message). In this sample I showed how to add the DropDown event to the ComboBox. So I modified the code besides raising the DropDown event, to also raise DropDownClose event. Below is the full source for the ComboBoxDrop control:
public class ComboBoxDrop : ComboBox, IMessage
{
// Events
public event EventHandler DropDown;
public event EventHandler DropDownClosed;
#region constants
private const int WM_COMMAND = 0x111;
private const short CBN_DROPDOWN = 7;
private const short CBN_CLOSEUP = 8;
#endregion
public ComboBoxDrop()
{
// Hook up into ParentChanged event
this.ParentChanged += new EventHandler(ComboBoxDrop_ParentChanged);
}
void ComboBoxDrop_ParentChanged(object sender, EventArgs e)
{
if (this.Parent != null)
{
// Start subclassing of the parent
this.EnableParentSubclassing();
}
}
#region IMessage Members
public void WndProc(ref Message m)
{
if (m.Msg == WM_COMMAND)
{
// Make sure that's its our ComboBox
if (m.LParam == this.Handle)
{
if (GetHighOrder(m.WParam) == CBN_DROPDOWN)
{
if (DropDown != null)
{
// Raise the event
DropDown(this, null);
}
}
else if (GetHighOrder(m.WParam) == CBN_CLOSEUP)
{
if (DropDownClosed != null)
{
// Raise the event
DropDownClosed(this, null);
}
}
}
}
// Call to the default window procedure
this.DefaultWinProc(ref m);
}
#endregion
private short GetHighOrder(IntPtr value)
{
return (short)((value.ToInt32() >> 16) & 0xffff);
}
}
In the code above you can see that we start subclassing of the ComboBox's parent Form by calling to the extention method EnableParentSubclassing().
As usual you can find the source code for the MessageControl and other classes attached.