How do Windows Forms events Happen?

How do Windows Forms events Happen?


Have you ever wondered how windows forms works? I think we’ve all heard the standard spiel - that it is an Object Oriented wrapper on top of the windows API, but that doesn’t begin to explain how it interacts with the underlying operating system. It’s also not a particularly satisfying description because every rich client technology from Microsoft for the past 10 years (from MFC onwards) has been a more object-oriented wrapper on top of the Win32 API. So how does Windows Forms interact with the underlying Win32 API?

The Win32 API is a series of C functions implemented in a number of DLLs. Central to windows UI programming is the concept of a handle - a handle is a pointer to something graphical such as a window, a text box, an image etc. Sometimes in Windows API programming this is written as hWnd (for handle to a Window). Retaining a handle to something allows you to manipulate it in code in a similar way to a reference in .NET.

Windows forms controls are (for the most part) “native” windows controls - they’re not re-implementations of the existing controls in .NET, they’re a wrapper around the existing API so it is unsurprising that all controls have a handle property (corresponding to the Win32 API’s hWnd). All controls also have an internal variable called ControlNativeWindow which derives from the NativeWindow type. It is through this that the handle reference is retained. The handle is created by calling into the windows API. We know the .NET framework was built to be able to call out to un-managed code via the DllImport attribute in the System.Runtime.InteropServices namespace, where we can declare the name of a target DLL and the name and method signature of a method we wish to call from our managed code. Since most of the types the windows API deals with for creating UI elements are fairly simple (integers, and structures of integers by and large) the interoperability between .NET Windows Forms and the underlying Win32 API is not that remarkable - our managed code just needs to be able to store these simple types and sometimes do some formatting to pass the structures down to the underlying API in an intelligent way.

So what about events? For an event to be raised in managed code requires something in windows to send a message to our .NET code (or for our .NET code to be constantly polling something in windows to see if anything has happened - but this doesn’t sound like a very performant way to write a windowing system). So how does .NET receive that message from the underlying windows API? Win32 API code registers to receive “messages” from the operating system by passing a function pointer to a particular Win 32 API call - SetWindowLong in User32.dll. This function pointer (along with a hWnd that is also supplied as a method argument) instructs the Win32 API where it should send messages for a particular hWnd. Surprisingly .NET does something very similar - it creates a delegate (an object that represents a pointer to a function) and passes that to SetWindowLong and receives “messages” from windows regarding that particular GUI object [1]. Somehow through the magic of the CLR and the power of Jim Miller, Anders Heilsberg and co this Just Works ™. These messages (which are just a series of numeric codes) get turned into events by the method that has been registered to receive them in a special design pattern called “a big case statement”. In Windows Forms the method that gets registered by default to handle these windows messages is called WndProc.

So how is this information in anyway useful? - well firstly it demonstrates why performing long-running operations on the main GUI thread “blocks” other events such as form resizes etc. The WndProc method can’t run to receive more messages because that thread is blocked doing the long-running operation. Secondly there may be some instances where native “events” are not exposed by .NET controls. If the control is not sealed you can subclass the control and over-ride the WndProc and listen for the message for the windows API that you want to act on. Even if the class you want to extend is sealed then all is not lost. By obtaining a reference to the window handle you can register some of YOUR CODE to receive messages from the message pump. You may need to use reflection to get access to the handle and you may also need to use reflection to get a reference to the existing WndProc so you can call that AFTER your done with the messages from windows. And remember kids, just because you CAN do something with the windows API doesn’t mean you SHOULD. Obviously this is a fairly “high-level” view of how this all happens (from what I gather), but am I leaving out anything really important?

[1] - This is something of an oversimplification. There are in fact two overloads to SetWindowLong that are called by classes in System.Windows.Forms - one takes a particular type of delegate and another takes a HandleRef structure.