If you're going to mix your MFC and Windows Forms code, there are two things you'll most likely want to do: host MFC COM controls in Windows Forms, and host your Windows Forms controls in MFC. Hosting MFC COM Controls in Windows FormsThe first step to hosting a COM control in your Windows Forms application is to get a COM control onto the Toolbox. You do this by right-clicking on the Toolbox, selecting Choose Items, clicking on the COM Components tab, clicking on the check box next to your COM control's name, and clicking the OK button. Figure B.1 shows a sample MFC COM control being added in this way. Figure B.1. Adding a COM Control to the Windows Forms ToolboxAfter the control is added to the Toolbox, you can drop it onto your forms or user controls at will, as shown in Figure B.2. Figure B.2. Hosting a COM Control on a Form
When dropped, you can set the control's properties and handle events using the Properties window, as shown in Figures B.3 and B.4. Figure B.3. Setting a COM Control Property in the Properties Window
Figure B.4. Handling a COM Control Property in the Properties Window
The generated code works almost as you'd expect: partial class HostForm { ... void InitializeComponent() { ... // axMFCCOMControlLibrary this.axMFCCOMControlLibrary.OcxState = ((System.Windows.Forms.AxHost.State) (resources.GetObject("axMFCCOMControlLibrary.OcxState"))); this.axMFCCOMControlLibrary.ClickEvent += this.axMFCCOMControlLibrary_ClickEvent; ... } #endregion AxMFCCOMControlLibraryLib.AxMFCCOMControlLibrary axMFCCOMControlLibrary; } ... void axMFCCOMControlLibrary_ClickEvent(object sender, EventArgs e) { ... } } Notice that the Click event handler is established using normal .NET means, whereas the setting for the custom Label property is nowhere to be found. That's because COM controls prefer to be initialized in a big chunk via a COM property bag, which is a set of name/value pairs that the Properties window tucks into the form's .resx resource file (discussed in Chapter 13: Resources). The controls are then set via the OcxState property. If you want to get or set properties or call methods on a COM control programmatically, you should feel free to do so: partial class HostForm : Form { ... void axMFCCOMControlLibrary_ClickEvent(object sender, EventArgs e) { // Set a COM property this.axMFCCOMControlLibrary.Label = "Ain't interop grand?"; // Call a COM method this.axMFCCOMControlLibrary.AboutBox(); } } The interop between Windows Forms and COM controls isn't perfect, but it should be good enough for you to leverage your existing investment in MFC controls without having to rewrite your working MFC control code. Hosting Windows Forms Controls in MFCIf you'd like to avoid porting your MFC application code but extend it by hosting Windows Forms code, you can do that, too. The built-in support for hosting a Windows Forms control in an MFC 8 application relies first and foremost on the MFC application being compiled as a managed applicationthat is, an application managed by the Microsoft Common Language Runtime (CLR). Because MFC has been around as a native application development platform that has direct access to the Win32 API, you might think that turning your MFC application into a managed one would be a big deal. Au contraire, mon frère. The C++ compiler writers at Microsoft have worked long and hard to make sure that you can flip the managed application switch on your MFC applications and compile them without change.[6] To build your MFC application as managed, giving you access to all the .NET types including your Windows Forms controls, open your MFC project's properties and set Configuration Properties | General | Project Defaults | Common Language Runtime support to "Common Language Runtime Support (/clr)," as shown in Figure B.5.
Figure B.5. Compiling Your Native MFC Project as a Managed .NET ProjectAt this point, compiling and executing your MFC application as managed should look and feel exactly the same. To host a Windows Forms control, you create a wrapper around it that MFC can talk to using COM control interfaces. For this, you need the CWindowsFormsControl MFC class, which is defined in the afxwinforms.h header file. This file is usually included at the bottom of your stdafx.h file: // stdafx.h : include file for standard system include files, // or project-specific include files that are used frequently // but are changed infrequently ... #include <afxwinforms.h> With this header included, you can create an instance of the class in your MFC dialog class: class CMfcWindowsFormsHostDlg : public CDialog { ... private: // A wrapper for a Windows Forms control CWindowsFormsControl<System::Windows::Forms::MonthCalendar> m_wndWindowsFormsCalendar; }; The CWindowsFormsControl template class is parameterized with the type of the Windows Forms control so that MFC can create the control at dialog initialization time. In this example, I'm using the built-in Windows Forms MonthCalendar control, but if you'd like to bring in your own custom Windows Forms controls, you can do so by right-clicking on your project in Solution Explorer, choosing References, and adding your assembly, as shown in Figure B.6. Figure B.6. Adding a Custom Windows Forms Assembly to an MFC ProjectAfter you've created the wrapper type using the CWindowsFormsControl template class, you need a place to put your control after it's created. The CWindowsFormsControl class is designed to take the ID of an existing control on your dialog resource and use the size and location for its own size and location. This isn't anything like the designability that you've come to know and love from Windows Forms, but if you put a dummy placeholder control on your dialog, you can replace it at run time with your Windows Forms control. The easiest control to use for your placeholder is the Static Text control, as shown in Figure B.7. Figure B.7. Using a Static Text Control as a Placeholder for a Windows Forms ControlNotice in Figure B.7 that the placeholder has an ID of IDC_CAL_PLACEHOLDER. This ID is used to indicate to the CWindowsFormsControl at run time where the placeholder control goes. Notice also that the placeholder contains text. This is a handy way to indicate what that Static Text control is doing in the middle of your dialog. Because the placeholder control will be hidden at run time, it doesn't matter what's in it, only its size and location. To indicate to the CWindowsFormsControl object where it should go requires only one line in the DoDataExchange method: void CMfcWindowsFormsHostDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_ManagedControl(pDX, IDC_CAL_PLACEHOLDER, m_wndWindows FormsCalendar); } However, if you happen to be hosting the Windows Forms control in a groupbox or some other kind of container, as I do in Figure B.7, it's possible that your Windows Forms control will be obscured by the control that's supposed to be hosting it, even if the Static Text control shows itself in front of the hosting control at design time. To avoid this, I like to stick a BringWindowsToTop call in OnInitDialog: BOOL CMfcWindowsFormsHostDlg::OnInitDialog() { CDialog::OnInitDialog(); ... // Make sure your control is showing m_wndWindows FormsCalendar.BringWindowToTop(); return TRUE; // return TRUE unless you set the focus to a control } Figure B.8 shows a dialog that hosts the unmanaged and managed MonthCalendar controls side by side on the same MFC dialog. Figure B.8. Hosting a Windows Forms Control on an MFC Dialog
In addition to merely hosting a Windows Forms control in your MFC dialogs, you'll want to call methods, set properties, and handle events. You may also want to host your Windows Forms controls in a view instead of a dialog or even show a Windows Forms form, modally or modelessly. All these things are possible, and they're described very nicely in the VS05 product documentation.[7]
|