Part I of this series briefly looked at the history of MFC as a framework for writing Windows applications and how MFC developers had often been at a disadvantage compared to Visual Basic developers in the area of third-party control availability. It also looked at how MFC 8.0 allows Windows Forms controls to be used within an MFC application. This article builds on the integration story with a look at control placement and event handling, the MFC classes used for the Windows Forms integration, and the Windows Presentation Foundation, the new Windows UI framework formerly known as Avalon.
Control Placement and Initialization
The Scribble sample application from Part I replaced an MFC Control that was already on the form with a Windows Forms control. This scenario calls for simply replacing the dynamic data exchange function (DDX_*) so that the control ID recorded in the application’s resources hooks up with the Windows Forms control rather than an MFC control—or in Scribble’s case, a simple integer member variable (click here to download the sample application). Unfortunately, there is no way to add a Windows Forms Control to the MFC toolbox, so WYSIWYG control placement and setting the initial value of control properties via the Properties Window are not possible. The design time infrastructure of MFC controls is very different from that of MFC, and retrofitting the MFC forms designer to support Windows Forms design-time behavior would be a major engineering exercise.
The easiest technique for placing a new Windows Forms control on a form is simply placing a MFC Static Text control onto the form in the correct location and with the correct dimensions, and then changing the DDX function to DDX_ManagedControl. You may need to place the managed control on a Windows Forms form first to get the appropriate dimensions, or just use a trial-and-error approach.
The lack of design-time support also means that the initial values of a control need to be set programmatically. As with MFC controls, Windows Forms controls have a set of default values; you don’t have to set the value of every control property. The DDX_ManagedControl is responsible for actually creating the Windows Forms control at runtime, and because DDX_ManagedControl will be present only in a form’s DoDataExchange function, it is important to wait until the underlying control has been created before accessing the Windows Forms control member variable. The overridden OnInitDialog is the correct location to set the initial value of a control’s properties. Whereas you often could get away with setting a MFC control’s properties earlier than OnInitDialog (particularly if you used DDX to manage the storage of a control’s data in primitive data types), this can cause major problems when using Windows Forms controls.
Control Event Handling
The pattern of Windows Forms control event handling has been designed to appear very familiar to MFC developers who are used to the message map macros that are a major part of MFC programming. At a conceptual level, Windows Forms uses delegates, which are a type-safe form of function pointers, rather than Windows messages to signal that an event has taken place. Instead of listening for a particular message, code that wants to know when a particular event has occurred registers a delegate with the event raiser, which then calls the delegate function when the event has occurred. Using the example from Part I of this series, the code below shows how to receive notification when the Windows Forms control that was added loses focus:
//in the public section of the class declaration
void OnLostFocus( System::Object^ sender, System::EventArgs^ e );
BEGIN_DELEGATE_MAP( CPenWidthsDlg )
//in the CPP file
//any other initialization code
+= MAKE_DELEGATE( System::EventHandler, OnLostFocus );
void CPenWidthsDlg::OnLostFocus( System::Object^ sender,
System::EventArgs^ e )
//code to handle event
The new macros are relatively straightforward: BEGIN_DELEGATE_MAP and END_DELEGATE_MAP closely parallel the BEGIN_MESSAGE_MAP and END_MESSAGE_MAP macros that are placed in the source code file of an MFC form. EVENT_DELEGATE_ENTRY is a macro that takes a function name followed by the parameters for the named function.
The final piece of the event hook-up logic is contained in the OnInitDialog override, where the MAKE_DELEGATE macro is used to construct a .NET delegate handle ready for assignment to the control’s event, which is LostFocus in this case.
In the example, you do not need to unsubscribe the event handler (the CPenWidthsDlg object) from the event raiser (the underlying Windows Forms control), because both share the same lifetime and circular references are not a problem with the form of garbage collection that the .NET Framework uses. However, if a short-lived object registers to listen for an event from a long-lived event raiser, a handle to the delegate returned by the MAKE_DELEGATE macro should be kept in a member variable, and the delegate should be removed from the event listeners queue using a -= call against the event. This prevents a live reference (event raiser to event handler) from keeping an object alive for more garbage collection cycles than necessary.
MFC Implementation of Windows Forms Control Integration
The workhorse of the new MFC classes that implement the Windows Forms control integration is clearly CWinFormsControl. This templated class is responsible for creating and managing the entire lifetime of the Windows Forms control, and it acts as the bridge between the MFC and Windows way of interacting with controls. Like other MFC controls, CWinFormsControl is derived from CWnd. Hence, it provides all the standard MFC-style functionality that a seasoned C++ developer would expect. CWinFormsControl has a GetControl method that returns a managed handle to the templated type that it wraps, which allows all the System::Windows::Forms::Control and derived methods and properties to be accessed. To make the syntax of setting control properties and calling methods more natural, the -> operator is overridden to behave the same as the GetControl method.
DDX_ManagedControl is responsible for the creation and management of a CWinFormsControl object. DDX_ManagedControl calls CWinFormsControl::CreateManagedControl under the covers. After this, the standard MFC DoDataExchange interaction moves data between member variables and the underlying control. As Part I showed, DDX_ManagedControl is not as sophisticated as some of the other DDX_ functions that can bypass CWnd-derived member variables entirely and go straight to primitive data types. This means that using Windows Forms controls will typically result in a bit more code ending up in the DoDataExchange function. There are no DDV_ functions for data validation against managed controls.
Outside of these two main functions, macros can alleviate the gruntwork of hooking up event handlers. These were covered in the previous section.
Windows Presentation Foundation and MFC Integration
Beyond Windows Forms, Microsoft is planning a major change in the way application UIs are written. A new UI toolkit, known as the Microsoft Windows Presentation Foundation (WPF), is slated for release in the Longhorn time frame. WPF combines a HTML-like markup file known as XAML with procedural code written in a managed language like C++/CLI to produce complex user interfaces with less hand-coding than MFC or Windows Forms require.
Microsoft doesn’t expect an overnight transition from Windows Forms to WPF, and any investment in updating an MFC application to work with Windows Forms isn’t going to be wasted. From an MFC perspective, Windows Forms and WPF are very similar—both offer managed controls for building the UI of an application. The actual code for integrating MFC with WPF controls is quite similar to MFC/Windows Forms integration, and given the beta nature of WPF, drilling down into it isn’t worthwhile.
The following three points are key when deciding whether to wait for WPF to take advantage of a managed UI:
- The best way to prepare for WPF is to begin with Windows Forms controls now.
- WPF won’t replace Windows Forms completely for quite a few release cycles.
- The work that has gone into allowing Windows Forms to integrate with MFC means that WPF and MFC will integrate well from the first WPF release, and the release-cycle lag that occurred with Windows Forms 1.x won’t occur.
Download the Code
To download the sample code for this article, click here.
About the Author
Nick Wienholt is an independent Windows and .NET consultant based in Sydney, Australia. He is the author of Maximizing .NET Performance from Apress, and specializes in system-level software architecture and development with a particular focus on performance, security, interoperability, and debugging. Nick can be reached at NickW@dotnetperformance.com.