Udostępnij za pośrednictwem


Subclassing Controls with a Managed Window Procedure

The .NET Compact Framework provides the capability for native code to call managed code by using a callback delegate. By subclassing a managed control to receive a callback from its corresponding native control, you can create controls with functionality not directly available in the .NET Compact Framework.

This is an advanced topic for developers knowledgeable about Windows programming and control subclassing. To subclass a control, you will need to know the inner details of the native control and how it maps to the functionality you want to provide in the extension to the managed control. You will need to know which Windows messages to monitor and native Windows Embedded CE functions to invoke to provide the desired functionality.

This topic describes subclassing the TreeView and Button controls, and the following topics provide the code examples and instructions for building the applications.

How-to topic

Demonstrates

How to: Subclass a TreeView by Using Native Callbacks

Subclassing the TreeView control to create an implementation of the NodeMouseClick event. The .NET Compact Framework does not directly support this method because of the need to limit its size for constrained resources.

How to: Subclass a Button by Using Native Callbacks

Subclassing the Button control to display a colorful gradient fill.

Note that this button is provided primarily for demonstrating how to use subclassing and callbacks. For an easier way to create a gradient filled button, see How to: Display a Gradient Fill.

Both of these subclassing programs include the WndProcHooker class and a helper class of native Win32 structures, platform invoke declarations, and a WndProc delegate. For code listings, see How to: Use a Class for Hooking Windows Procedures and How to: Use a Helper Class for Platform Invokes.

WndProcHooker Class

The WndProcHooker class provides the ability to have a native control, or window, invoke a callback to managed code when a particular Windows message is received for that control. This is achieved by substituting a native control's window procedure (WndProc) with a generic window procedure, WindowProc, that performs a look-up to determine if the control is in a list of controls associated with callback methods to be invoked. If so, the control is considered to be hooked.

If the control is hooked, WindowProc determines if the control is mapped to respond to a particular Windows message. This is the message map, which maps a Windows message with a WndProcCallback delegate that calls your managed method containing the desired functionality. If the message map contains the message, the WndProcCallback delegate invokes your code using the message parameters that were supplied to WindowProc.

Hooking Controls

The HookWndProc method associates the control's handle with a message map to be used by the generic window procedure WindowProc. This is referred to as hooking a control.

The HookWndProc method determines whether a control has already been hooked. If not, it creates a HookedProcInformation object for that control. This object contains a reference to the control and the message map. If the handle to the control has already been created, it hooks the window by creating a pointer to the window's original window procedure for later restoration. If the handle has not been created, it will get hooked by the ctrl_HandleCreated method that handles the HandleCreated event.

Next, the HookWndProc method adds the HookedProcInformation object to one of two generic dictionary collections:

  • The hwindDict dictionary, which contains a global list of all the hooked window handles. The key is an hwnd. Controls whose handles have been created go into this dictionary. Controls in this dictionary are evaluated by WindowProc for any mapped messages.

  • The ctlDict dictionary, which contains controls whose handles have not been created. When the ctrl_HandleCreated method is called, the control is moved into the hwndDict dictionary.

Unhooking Controls

The UnhookWndProc method provides two ways to unhook a control:

  • Remove a message from the message map for a control but still keep the control in the hwndDict dictionary of hooked windows. This method also restores the original window procedure for the control by using a pointer kept in the HookedProcInformation object.

  • Remove the control from the hwndDict dictionary of hooked controls, and either remove its handle and place it in the ctrlDict dictionary or dispose of the control entirely. This method also restores the original window procedure for the control by using a handle kept in the HookedProcInformation object.

Subclassing the TreeView Control

The sample program TreeViewBonus class, listed in How to: Subclass a TreeView by Using Native Callbacks, extends the TreeView control to include the NodeMouseClick event, which is not directly available in the .NET Compact Framework.

The NodeMouseClick event is obtained by adding the WM_NOTIFY message to the message map for the control, as performed by the WndProcHooker class. The managed callback method WM_Notify_Handler invokes the native GetMessagePos function to get coordinates of the mouse cursor at the time the Windows messages were sent.

Note that these coordinates are relative to the visible client area of the screen, not relative to the TreeView control. The TreeViewBonus class converts screen coordinates to client coordinates with the PointToClient method for the control. Then these client coordinates are sent along with the TVM_HITTEST message to determine if and where the TreeViewBonus object was clicked.

The TreeViewBonus class contains code to get the coordinates relative to the control by using the native control's TVM_HITTEST message.

If the click occurred on one of the tree view nodes, the native TVHITTESTINFO structure contains the handle to that node. The final step is to recursively traverse through the managed TreeView nodes, performed by the FindTreeNodeFromHandle method, to find the matching handle and raise the NodeMouseClick event. The TreeNodeMouseClickEventArgs class provides the following data:

  • The node that was clicked.

  • The button that was clicked.

  • The number of clicks, set at 1.

  • The x-coordinate where the click occurred.

  • The y-coordinate where the click occurred.

The TreeViewBonus class hooks the native tree view control's parent to a managed window procedure as performed by the WndProcHooker class. It responds to the OnParentChanged event by hooking the parent control, thereby accommodating the possibility that the TreeView was moved to a new parent, such as from the Form and into a Panel.

Subclassing the Button Control

The GradientFilledButton and GradientFill classes listed in How to: Subclass a Button by Using Native Callbacks extend the Button control to display a gradient fill between two colors. This program is primarily meant to demonstrate subclassing. However, an easier way to display a gradient fill in a button is to create a custom control derived from Control, as described in How to: Display a Gradient Fill.

The constructor for the GradientFilledButton class creates instances of the WndProcHooker class to map Windows messages to managed callbacks. These callback methods draw the button in the appropriate state depending on the Windows message and the state of the Capture property for the control. The following table lists the mapped Windows messages and their corresponding callbacks.

Windows message

Managed callback method and description

WM_KEYDOWN

WM_KeyDown_Handler - Redraws the button in a pushed state if the SPACEBAR or ENTER (or Action) key is pressed.

WM_KEYUP

WM_KeyUp_Handler - Redraws the button in an unpushed state and raises the Click event if the key pressed is the SPACEBAR or ENTER (Action) key.

WM_LBUTTONDOWN

WM_LeftButtonDown_Hander - Redraws the button in a pushed state and sets the mouse Capture property for the control to true.

WM_LBUTTONUP

WM_LButtonUp_Handler - Redraws the button in an unpushed state, raises the MouseUp event if the cursor is released within the client area of the control, and sets the mouse Capture property for the control to false.

WM_MOUSEMOVE

WM_MouseMove_Handler - Redraws the button if it was previously clicked and Capture is true.

WM_PAINT

WM_Paint_Handler - Redraws the button in the appropriate state.

These managed callback methods use the DrawButton method to draw the button in the appropriate state. This method has two overloads for drawing the button either on a window, used by this example, or on a Graphics object. Both overloads take a Boolean value that is true if the button was pressed.

The GradientFilledButton class uses the GradientFill class to perform the platform invoke calls to native code to do the fill. The GradientFill class provides properties to set the start and ending colors and to specify the fill direction as left-to-right or top-to-bottom.

See Also

Tasks

How to: Use a Class for Hooking Windows Procedures

How to: Use a Helper Class for Platform Invokes

How to: Subclass a TreeView by Using Native Callbacks

How to: Display a Gradient Fill

Concepts

.NET Compact Framework How-to Topics

Other Resources

Interoperability in the .NET Compact Framework