To aid development of rich interactive applications, and reduce the amount of time spent on handling OS specific interactions. .Net applications hide a lot of the underlying message passing that occurs between an application and the OS. This obfuscation is done either at a control or CLR level and usually is perfectly reasonable.
However when implementing custom controls, interacting with Native COM objects (IE Tiling Engine, Webkit), or requiring fine grain control you will most likely need to start tinkering…
By default all applications start off as Single Threaded. Computation is performed on this thread, hopefully using the event methodology. What .Net developers may not realise that on that thread also sits the WndProc (Message Queue).
MSDN Extract:
“The system can display any number of windows at a time. To route mouse and keyboard input to the appropriate window, the system uses message queues.
The system maintains a single system message queue and one thread-specific message queue for each GUI thread. To avoid the overhead of creating a message queue for non–GUI threads, all threads are created initially without a message queue. The system creates a thread-specific message queue only when the thread makes its first call to one of the specific user functions; no GUI function calls result in the creation of a message queue.”
In English: The OS sends messages to GUI threads, some have to be immediately processed, others can be stored till the GUI thread becomes available.
WndProc
GUI developers in C / C++ are usually more familar with the WindowProc. It is a method that intercepts messages being sent from the OS, it handles functionality such as paint requests, mouse movement, requests to exit etc. and it becomes the applications responsibly to implement these. This gave rise to wrappers in C / C++ to abstract these into events to reduce the boiler-plate code needed to be written for every application.
C# went even further and fully hid the WndProc method from the developer by default. All system messages are internally intercepted, some require immediate processing or are disgarded, othersare added to the ‘Message Queue’, when the program is idle, the Message Queue is ‘pumped’ and all outstanding actions are processed. This gives rise to developers from the VB days as well noticing during long running processes the UI would not update or would become unresponsive. This is down to the Message Queue not been given time to pump its outstanding messages (usually paint events). Windows can use the outstanding messages on the Message Queue, and the response time to a request as an indicator as to whether an application has stopped responding.
When in a long running procedure, developers usually go down two routes:
- Split off the long running task into a new thread, thus leaving the GUI (Message Queue) thread idle to perform required actions.
- Insert Application.DoEvents() into the task where appropriate. This halts the current process and pumps all actions in the message queue. This is a cheap hack but most still use it.
Do: Split long running tasks into a seperate thread. Use the ThreadPool for small repeatitive tasks.
Don’t: Keep the main GUI thread busy. If using the MVC methodology you should only be using the GUI thread to update controls, not perform processing.
Full .Net WndProc support
In what has become the norm, support in the full .Net framework is far beyond that in the Compact Framework and is ever increasing.
The full .Net environment allows direct access to WndProc by overriding the internal functionality (Control.WndProc)
Below is an example of how to override the internal WndProc method and perform intermediate actions before falling through to the base implementation. The WM_ACTIVATEAPP operating system message is handled in this example to know when another application is becoming active
using System; using System.Drawing; using System.Windows.Forms; namespace csTempWindowsApplication1 { public class Form1 : System.Windows.Forms.Form { // Constant value was found in the "windows.h" header file. private const int WM_ACTIVATEAPP = 0x001C; private bool appActive = true; [STAThread] static void Main() { Application.Run(new Form1()); } public Form1() { this.Size = new System.Drawing.Size(300,300); this.Text = "Form1"; this.Font = new System.Drawing.Font("Microsoft Sans Serif", 18F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); } protected override void OnPaint(PaintEventArgs e) { // Paint a string in different styles depending on whether the // application is active. if (appActive) { e.Graphics.FillRectangle(SystemBrushes.ActiveCaption,20,20,260,50); e.Graphics.DrawString("Application is active", this.Font, SystemBrushes.ActiveCaptionText, 20,20); } else { e.Graphics.FillRectangle(SystemBrushes.InactiveCaption,20,20,260,50); e.Graphics.DrawString("Application is Inactive", this.Font, SystemBrushes.ActiveCaptionText, 20,20); } } [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name="FullTrust")] protected override void WndProc(ref Message m) { // Listen for operating system messages. switch (m.Msg) { // The WM_ACTIVATEAPP message occurs when the application // becomes the active application or becomes inactive. case WM_ACTIVATEAPP: // The WParam value identifies what is occurring. appActive = (((int)m.WParam != 0)); // Invalidate to get new text painted. this.Invalidate(); break; } base.WndProc(ref m); } } }
Note: You still need to call base.WndProc(…) otherwise all the messages you do not process will not be processed, thus your application may become unresponsive.
The values for m.Msg as used in the example can be found online in the Windows.h header file. An outline can be found here – though WndProc only intercepts the messages pre-fixed with WM_.
The one that is usually important is the WM_PAINT event, which occurs when the control is invalidated by the system and windows requests a repaint. This is somewhat different to internal invalidation where the user triggers it. In older (XP and earlier) painting was done in a one phase approach rather than having backing buffers for each windows via the Desktop Windowing Manager, thus if a control moved, all affected windows would have WM_PAINT called.
Accessing WndProc in the Compact Framework
The Compact Framework does not provide easy access to ‘protected override void WndProc(ref Message m)’ like the full .Net and so from the offset we have a problem, how do we access it?
Like most good things in the Compact Framework, they aren’t actually in there, but have to be accessed via P-Invoking into the Native system DLLs. Accessing WndProc is no exception. Newer versions of the .Net Compact Framework have improved matters, but still are annoying.
Alex Yakhnin who worked at Microsoft and on the Open Source (at the time) OpenNetCF project gives helpful insights.
On a side note, interoping between Managed and unmanaged code in the Compact Framework has always been a nightmare, especially version 1 where:
- It didn’t expose the native handle of a window, so Subclassing was never going to be possible. Nor was it easy (possible but extremely difficult) to perform direct native operations on managed controls.
- There was no way of native methods to call back to the managed world via delegates. Most calls were effectively ‘fire-and-forget’ from the managed side.
This was mainly in part because the COM Callable Wrapper (CCW) part of the CLR in the Compact Framework is tiny in comparison to the full .Net version. The CCW handles the ‘glue’ between managed and native functions, and performs marshalling and refactoring of data structures between the two.
Interesting Sidenote: Due to the limitations of the Compact Framework CCW, Marshalling complex data structures is not possible without major re-working. This is because the CCW will only marshall blittable types (1 to 1 byte mappings). Managed types like strings are not blittable when in a structure. This means there may be native functions you can not P-Invoke to from managed code, and may require an intermediate native function.
The .Net V2 approach was to create a static WindowProc for all registered controls in an application. This has the advantage of not requiring additional code reuse, and also would be the global approach if the application is single threaded – the singular GUI thread would have a singular shared WindowProc for all subcontrols. Subcontrols can then individually register new message sinks for particular events. I’ve extended Alex’s implementation, and corrected formatting so its consistent with C# (you could tell he was a C/C++ dev originally).
A summary of how it works:
Firstly you will need a class to house all the P-Invoke methods down to coredll.dll on the desktop you would be interacting with user32.dll instead.
using System; using System.Drawing; using System.Runtime.InteropServices; /// /// Contains managed wrappers or implementations of Win32 structs, delegates, /// constants and PInvokes that are useful for this sample. /// /// See the documentation on MSDN for more information on the elements provided /// in this file. /// public sealed class Win32 { public const int GWL_WNDPROC = -4; /// /// A callback to a Win32 window procedure (wndproc) /// ///The handle of the window receiving a message ///The message ///The message's parameters (part 1) ///The message's parameters (part 2) /// A integer as described for the given message in MSDN public delegate int WndProc(IntPtr hwnd, uint msg, uint wParam, int lParam); [DllImport("coredll.dll")] public extern static int DefWindowProc( IntPtr hwnd, uint msg, uint wParam, int lParam); [DllImport("coredll.dll")] public extern static IntPtr SetWindowLong( IntPtr hwnd, int nIndex, IntPtr dwNewLong); [DllImport("coredll.dll")] public extern static int CallWindowProc( IntPtr lpPrevWndFunc, IntPtr hwnd, uint msg, uint wParam, int lParam); }
Notice the exposed WndProc delegate. This is called from the native methods when a message is triggered.
using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; namespace SubclassSample { class WndProcHooker { /// /// The callback used when a hooked window's message map contains the /// hooked message /// ///The handle to the window for which the message /// was received ///The message's parameters (part 1) ///The message's parameters (part 2) ///The invoked function sets this to true if it /// handled the message. If the value is false when the callback /// returns, the next window procedure in the wndproc chain is /// called /// A value specified for the given message in the MSDN /// documentation public delegate int WndProcCallback( IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled); /// /// This is the global list of all the window procedures we have /// hooked. The key is an hwnd. The value is a HookedProcInformation /// object which contains a pointer to the old wndproc and a map of /// messages/callbacks for the window specified. Controls whose handles /// have been created go into this dictionary. /// private static Dictionary<IntPtr, HookedProcInformation> hwndDict = new Dictionary<IntPtr, HookedProcInformation>(); /// /// See hwndDict. The key is a control and the value is a /// HookedProcInformation. Controls whose handles have not been created /// go into this dictionary. When the HandleCreated event for the /// control is fired the control is moved into hwndDict. /// private static Dictionary<Control, HookedProcInformation> ctlDict = new Dictionary<Control, HookedProcInformation>(); /// /// Makes a connection between a message on a specified window handle /// and the callback to be called when that message is received. If the /// window was not previously hooked it is added to the global list of /// all the window procedures hooked. /// ///The control whose wndproc we are hooking ///The method to call when the specified /// message is received for the specified window ///The message we are hooking. public static void HookWndProc( Control ctl, WndProcCallback callback, uint msg) { HookedProcInformation hpi = null; if (ctlDict.ContainsKey(ctl)) { hpi = ctlDict[ctl]; } else if (hwndDict.ContainsKey(ctl.Handle)) { hpi = hwndDict[ctl.Handle]; } if (hpi == null) { // We havne't seen this control before. Create a new // HookedProcInformation for it hpi = new HookedProcInformation(ctl, new Win32.WndProc(WndProcHooker.WindowProc)); ctl.HandleCreated += new EventHandler(ctl_HandleCreated); ctl.HandleDestroyed += new EventHandler(ctl_HandleDestroyed); ctl.Disposed += new EventHandler(ctl_Disposed); // If the handle has already been created set the hook. If it // hasn't been created yet, the hook will get set in the // ctl_HandleCreated event handler if (ctl.Handle != IntPtr.Zero) { hpi.SetHook(); } } // stick hpi into the correct dictionary if (ctl.Handle == IntPtr.Zero) { ctlDict[ctl] = hpi; } else { hwndDict[ctl.Handle] = hpi; } // add the message/callback into the message map hpi.messageMap[msg] = callback; } /// /// The event handler called when a control is disposed. /// ///The object that raised this event ///The arguments for this event static void ctl_Disposed(object sender, EventArgs e) { Control ctl = sender as Control; if (ctlDict.ContainsKey(ctl)) { ctlDict.Remove(ctl); } else { System.Diagnostics.Debug.Assert(false); } } /// /// The event handler called when a control's handle is destroyed. /// We remove the HookedProcInformation from hwndDict and /// put it back into ctlDict in case the control get re- /// created and we still want to hook its messages. /// ///The object that raised this event ///The arguments for this event static void ctl_HandleDestroyed(object sender, EventArgs e) { // When the handle for a control is destroyed, we want to // unhook its wndproc and update our lists Control ctl = sender as Control; if (hwndDict.ContainsKey(ctl.Handle)) { HookedProcInformation hpi = hwndDict[ctl.Handle]; UnhookWndProc(ctl, false); } else { System.Diagnostics.Debug.Assert(false); } } /// /// The event handler called when a control's handle is created. We /// call SetHook() on the associated HookedProcInformation object and /// move it from ctlDict to hwndDict. /// /// /// static void ctl_HandleCreated(object sender, EventArgs e) { Control ctl = sender as Control; if (ctlDict.ContainsKey(ctl)) { HookedProcInformation hpi = ctlDict[ctl]; hwndDict[ctl.Handle] = hpi; ctlDict.Remove(ctl); hpi.SetHook(); } else { System.Diagnostics.Debug.Assert(false); } } /// /// This is a generic wndproc. It is the callback for all hooked /// windows. If we get into this function, we look up the hwnd in the /// global list of all hooked windows to get its message map. If the /// message received is present in the message map, its callback is /// invoked with the parameters listed here. /// ///The handle to the window that received the /// message ///The message ///The message's parameters (part 1) ///The messages's parameters (part 2) /// If the callback handled the message, the callback's return /// value is returned form this function. If the callback didn't handle /// the message, the message is forwarded on to the previous wndproc. /// private static int WindowProc( IntPtr hwnd, uint msg, uint wParam, int lParam) { if (hwndDict.ContainsKey(hwnd)) { HookedProcInformation hpi = hwndDict[hwnd]; if (hpi.messageMap.ContainsKey(msg)) { WndProcCallback callback = hpi.messageMap[msg]; bool handled = false; int retval = callback(hwnd, msg, wParam, lParam, ref handled); if (handled) return retval; } // if we didn't hook the message passed or we did, but the // callback didn't set the handled property to true, call // the original window procedure return hpi.CallOldWindowProc(hwnd, msg, wParam, lParam); } System.Diagnostics.Debug.Assert( false, "WindowProc called for hwnd we don't know about"); return Win32.DefWindowProc(hwnd, msg, wParam, lParam); } /// /// This method removes the specified message from the message map for /// the specified hwnd. /// ///The control whose message we are unhooking /// ///The message no longer want to hook public static void UnhookWndProc(Control ctl, uint msg) { // look for the HookedProcInformation in the control and hwnd // dictionaries HookedProcInformation hpi = null; if (ctlDict.ContainsKey(ctl)) { hpi = ctlDict[ctl]; } else if (hwndDict.ContainsKey(ctl.Handle)) { hpi = hwndDict[ctl.Handle]; } // if we couldn't find a HookedProcInformation, throw if (hpi == null) { throw new ArgumentException("No hook exists for this control"); } // look for the message we are removing in the messageMap if (hpi.messageMap.ContainsKey(msg)) { hpi.messageMap.Remove(msg); } else { // if we couldn't find the message, throw throw new ArgumentException( string.Format( "No hook exists for message ({0}) on this control", msg)); } } /// /// Restores the previous wndproc for the specified window. /// ///The control whose wndproc we no longer want to /// hook ///if true we remove don't readd the /// HookedProcInformation /// back into ctlDict public static void UnhookWndProc(Control ctl, bool disposing) { HookedProcInformation hpi = null; if (ctlDict.ContainsKey(ctl)) { hpi = ctlDict[ctl]; } else if (hwndDict.ContainsKey(ctl.Handle)) { hpi = hwndDict[ctl.Handle]; } if (hpi == null) { throw new ArgumentException("No hook exists for this control"); } // If we found our HookedProcInformation in ctlDict and we are // disposing remove it from ctlDict if (ctlDict.ContainsKey(ctl) && disposing) { ctlDict.Remove(ctl); } // If we found our HookedProcInformation in hwndDict, remove it // and if we are not disposing stick it in ctlDict if (hwndDict.ContainsKey(ctl.Handle)) { hpi.Unhook(); hwndDict.Remove(ctl.Handle); if (!disposing) { ctlDict[ctl] = hpi; } } } /// /// This class remembers the old window procedure for the specified /// window handle and also provides the message map for the messages /// hooked on that window. /// class HookedProcInformation { /// /// The message map for the window /// public Dictionary<uint, WndProcCallback> messageMap; /// /// The old window procedure for the window /// private IntPtr oldWndProc; /// /// The delegate that gets called in place of this window's /// wndproc. /// private Win32.WndProc newWndProc; /// /// Control whose wndproc we are hooking /// private Control control; /// /// Constructs a new HookedProcInformation object /// ///The handle to the window being hooked ///The window procedure to replace the /// original one for the control public HookedProcInformation(Control ctl, Win32.WndProc wndproc) { control = ctl; newWndProc = wndproc; messageMap = new Dictionary<uint, WndProcCallback>(); } /// /// Replaces the windows procedure for control with the /// one specified in the constructor. /// public void SetHook() { IntPtr hwnd = control.Handle; if (hwnd == IntPtr.Zero) { throw new InvalidOperationException( "Handle for control has not been created"); } oldWndProc = Win32.SetWindowLong(hwnd, Win32.GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(newWndProc)); } /// /// Restores the original window procedure for the control. /// public void Unhook() { IntPtr hwnd = control.Handle; if (hwnd == IntPtr.Zero) { throw new InvalidOperationException( "Handle for control has not been created"); } Win32.SetWindowLong(hwnd, Win32.GWL_WNDPROC, oldWndProc); } /// /// Calls the original window procedure of the control with the /// arguments provided. /// ///The handle of the window that received the /// message ///The message ///The message's arguments (part 1) ///The message's arguments (part 2) /// The value returned by the control's original wndproc /// public int CallOldWindowProc( IntPtr hwnd, uint msg, uint wParam, int lParam) { return Win32.CallWindowProc( oldWndProc, hwnd, msg, wParam, lParam); } } } }
There are two classes, a static Window Procedure handler, which contains the exposed message loop and methods to hook up new controls, as well as another class to store intermediate information.
HookedProcInformation is then created per Control, passed into the constructor is the controls native handle and a WindowProc callback. Exposed via a public property is a mapping for hooked messages to be processed, those that aren’t are passed back to the default message handler.
Now you just need to wire up your control, if you are writing your own control from scratch then thats great, but if you are wanting to hook up an existing control, then all you need to do is extend the existing control class by using it as a base. This should work with most build in controls as they do not implement the ‘sealed’ attribute.
For example extending a TreeView would be done via:
public partial class SubClassedTreeView : TreeView
Now you need to wire everything up, this all occurs in the Overridden OnParentChanged Event which informs a control when its parent has been instantiated or changed.
/// /// The original parent of this control. /// private Control prevParent = null; protected override void OnParentChanged(EventArgs e) { // unhook the old parent if (this.prevParent != null) { WndProcHooker.UnhookWndProc(prevParent, Win32.WM_NOTIFY); } // update the previous parent prevParent = this.Parent; // hook up the new parent if (this.Parent != null) { WndProcHooker.HookWndProc(this.Parent, new WndProcHooker.WndProcCallback(this.WM_Notify_Handler), Win32.WM_NOTIFY); } base.OnParentChanged(e); }
The above code sample hooks onto the WM_NOTIFY event but you could easily change it to hook onto the WM_PAINT event by adjusting the HookWndProc and UnHookWndProc events:
WndProcHooker.HookWndProc(this.Parent, new WndProcHooker.WndProcCallback(this.WM_Paint_Handler), Win32.WM_PAINT);
Do: Make sure you have correct references to the Native WM_… Event Uint value.
Do: Make sure you unhook all your hooked events and vice versa, otherwise you may raise COM exceptions.
The example callback handler methods look like the following, and can be extended to do whatever you require.
private int WM_Notify_Handler( IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled) { return 0; }
The Marshaller can perform a conversion of wParam and lParam values where some inbound messages hold additional structure information.
There are limitations with the above approach, mainly if there are more than one control on a parent window. This can sometimes cause the WindowProc controller to constantly shift between instances.
Eventually .Net 3.5 Compact Framework came and offered a little bit more support. Mainly in the form of additional extension to controls via factory objects. Instead of calling the WndProc hooking functions manually this can be done for us in a control extension. Alex goes on to suggest an implementation in one of his newer blog posts.
Summary: Subclassing controls to access the WndProc in .Net Compact Framework is diffuclt, but isn’t impossible. The primary rule of P-Invoking is not to use it, minimise it where possible. If you can shift as much across to either side of the P-Invoke divide then thats great. Think about even re-writing your control to not need the WndProc, or push more code onto the native side as that can much easier interact with WndProc.