Hosting of MFC MDI Applications from Within WinForms and WPF Applications






4.97/5 (48 votes)
Building old MFC app in .NET app, replacing MFC MDI Framework with modern WinForms or WPF Framework and integrating MFC Views and Dialogs in it
Programming Guide with source code for hosting simple wizards MFC/Dialog application:
- Download demo - 3.2 MB
Source code ofMfcAdapter
with hostedUMLEditor
(Free source code supports only VS2003\VS2005!!! With VS2010 (MFC 10), some features got it wrong.) - Download MfcAdapter VS2003\VS2005
Try MfcAdapter 3.0 (VS 2019) from my new article: "MfcAdapter - not OLE .Net/WPF container of MFC applications" - Download MfcAdapter 3.0 Demo VS2019
(See "Deployment and installation" for careful installation)
Contents
- Introduction
- Mixed MFC With A CWinApp-derived Class and a Managed C++ DLL
- Architecture of the MfcAdapter
- Hosting of MFC Views and Dialogs in a WinForms Application
- WPF Auto-layout in Hosted WinForms Controls
- .NET 2.0 Control Layout Adapter to .NET 1.x Controls
- Demo Framework
- Deployment and Installation
- Points of Interest
- History
Introduction
As far as the way in which .NET has been developed, for a bunch of applications made in the MFC MDI architecture, there is a new problem of expensive migration into .NET. I've found only one migration tool - RC Converter by DudeLabs. But it supports the transformation of a small part of MFC MDI GUI - MFC resources only. The larger part of the GUI and all business logic, you have to convert yourself.
As an alternate to the migration scenario, Microsoft offers some interoperation solutions (in an interoperation scenario, you don't try to turn the MFC code into .NET automatically, you just try to ensure that they can work together):
HWnd
-based Win32 and WPF Interoperation (WPF and Win32 Interoperation Overview - Microsoft MSDN)- COM based MFC Windows Forms support classes (Using a Windows Forms User Control in MFC - Microsoft MSDN)
The first technology allows to host the MFC control only. ("If your Win32 logic is already packaged up nicely as a control...") You can turn a dialog into a control with some efforts - but not the larger MFC MDI application.
The MFC Windows Forms support classes allow the backwards integration of the .NET code in an MFC application. It lets you arrange .NET Forms as Views or Dialogs. There are some important limitations in this case: the .NET Framework cannot be integrated (the old MFC Framework has to be used), the part of the integrated Forms functionality is not available (for example, laying out), and a separate control can be integrated as a Form only (for example, the .NET MenuStrip
cannot be used in the MFC Framework). I find that these kinds of interoperation solutions are pure alternates to the migration scenario, and I haven't found real examples of these solutions on the Internet.
In view of a specific application (UMLEditor - revisiting the vector editor by Johan Rosengren), this article illustrates how you can build your old MFC MDI application in an existing .NET application or rapidly create a new WinForms/WPF GUI Framework for it. After this, you can gradually migrate from the old MFC dialogs and views to the new WinForms and then to the WPF UI elements. Such an intermediate integration of MFC and WinForms might seem to be more expedient for larger MFC MDI projects in comparison with a complete migration into .NET.
From the technical point of view, a mixed MFC DLL with a CWinApp
-derived class, the hosting of MFC views in WinForms controls, and the support of the WPF auto-layout in hosted .NET 1.x and .NET 2.0 WinForms controls used in this article, are not discussed on the Internet.
It is an example of how your application's GUI framework can be evolved by using the MfcAdapter
wrapper (50 KB! source code for the WinForms and WPF version):
UMLEditor
as an original MFC MDI application:
UMLEditor
built using the WinForms Framework - Forms with docking and skinning (stardock):
UMLEditor
built in a WPF page (FlowDocument
with two UML diagrams):
Mixed MFC With a CWinApp-derived Class and Managed C++ DLL
As a rule, the MFC class based application architecture is strongly typed. A lot of classes, for example, the MDI classes CDocument
, CView
, CFrameWnd
, and others cannot be used without the CWinApp
(application) class and a complicated initialization. The simple solution in this case is to put the full application in a DLL and to mix with the managed C++ classes. We can use these classes as the managed interface between the WinForms Framework and the MFC application. In this case, we have to resolve the problem with a "second" MFC application main window. It has to be invisible, and all the MFC windows have to be hosted in a WinForms MainForm window. However, all the functionality of the MFC application can be used in a managed Framework in this case. First of all, let's discuss mixed DLL creation. We have different solutions for VS2003 and VS2005 as the mixed DLL loading process is different in each case (Initialization of Mixed Assemblies).
VS2003
Let's use a standard solution from MSDN (Converting Managed Extensions for C++ Projects from Pure Intermediate Language to Mixed Mode). See the paragraph "To modify your DLL that contains consumers that use the managed code and DLL exports or managed entry points."
In order to prevent calling unsafe code during DLL initialization (loader lock problem), Microsoft offers to use the /noentry linker option. In this case, the mixed DLL has no entry point, _CRT_INIT
doesn't call the static
MFC constructors, and _DllMainCRTStartup
doesn't call the MFC DllMain
(which calls CWinApp::InitInstance
). Only managed objects would be created and initialized.
To create static
MFC objects and to initialize MFC objects, you have to call the __crt_dll_initialize()
static
function from your managed code. You have to be careful about the place to call __crt_dll_initialize()
, and be careful with your MFC constructors and the InitInstance
method code - don't forget about the loader lock problem, and don't use un-initialized MFC native code!!!
Besides this, you have to explicitly call InitApplication
at the beginning of InitInstance
instead of calling CWinApp::InitInstance()
. Otherwise, that will cause a problem with CDocManager
, PreTranslateMessage
(translation of the short keys into command messages), memory leaks, and others.
There are some general notes on the initialization of mixed MFC and managed C++ DLLs. I won't describe the special cases of MFC object initialization (for example, the initialization of an extension DLL's resource chain), because that is not the main theme of this article. The last note is about termination. You have to be careful to close your CWinApp
object first (see the example code in MfcAppAdapter.cpp) and then make a call to __crt_dll_terminate()
. The WinForms Framework would be closed properly in this case.
Now, we can check this solution with the UMLEditor
(or with the simple wizards MFC/Dialog application). In the case of the UMLEditor
, the following project configuration changes were made:
Configuration Type | Dynamic Library (.dll) |
Character Set | Use Unicode Character Set |
Use Managed Extensions | Yes |
Output File | .dll |
Additional Options | /noentry |
Update options which come against the compiler option /clr for the project configuration file and for other source files.
Basic Runtime Checks | Default |
Create the MfcAppAdapter.cpp file, as shown in the picture below, with a simple managed class. The use of the "Singleton" pattern allows us to encapsulate initialization and the disposing of this class from the WinForms code. It enables the MFC application to initialize before its first use, and terminate the MFC application before the WinForms application exits. Besides this, the MfcAppAdapter
enables wrapping of the MFC command message interface. We use this class only for DLL testing (more safety code is in the download):
#include "stdafx.h"
#include "_vcclrit.h"
using namespace System;
using namespace System::Windows::Forms;
public __gc class MfcAppAdapter
{
private:
static MfcAppAdapter* m_Instance;
MfcAppAdapter()
{
__crt_dll_initialize();
Application::add_ApplicationExit(
new EventHandler(this,
&MfcAppAdapter::OnApplicationExit));
}
void OnApplicationExit(Object* sender, EventArgs* e)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CMDIFrameWnd* pMainFrame =
dynamic_cast <CMDIFrameWnd*>(AfxGetMainWnd());
::SendMessage(pMainFrame->GetSafeHwnd(),WM_CLOSE,0,0);
//change DLL flag in AFX_MODULE_STATE to careful call
//ExitInstance as for application context
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
pModuleState->m_bDLL = NULL;
__crt_dll_terminate();
m_Instance=NULL;
}
public:
static MfcAppAdapter* GetInstance()
{
if(m_Instance==NULL)
{
m_Instance = new MfcAppAdapter();
}
return m_Instance;
}
bool OnCmdMsg( int nID)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CMDIFrameWnd* pMainFrame =
dynamic_cast <CMDIFrameWnd*>(AfxGetMainWnd());
return pMainFrame->OnCmdMsg(nID, 0, NULL, NULL) !=
FALSE;
}
};
The UMLEditor
doesn't need special initialization, so we only add InitApplication()
to UMLEditorDemo::InitInstance
in UMLEditorDemo.cpp. (For other applications, some small changes are necessary.)
After building the DLL, we can host and test it in a simple C# WinForms application (see the picture):
public Form1()
{
...
private void button1_Click(object sender,
System.EventArgs e)
{
//ID_APP_ABOUT
MfcAppAdapter.GetInstance().OnCmdMsg(0xE140);
}
}
We send the ID_APP_ABOUT
command message to the UMLEditor
using a button click:
VS2005
As we can see above, only managed initialization is performed in VS2003. We make an unmanaged initialization ourselves after a managed initialization. In VS2005, both unmanaged and managed initializations are performed - the unmanaged initialization takes place first, and the managed initialization takes place after that. Loader lock can still occur (for example, if the code within DllMain
accesses the CLR), but now it occurs deterministically, and is detected. Don't try to skip the unmanaged initialization with a /noentry linker option and call __crt_dll_initialize()
in VS2005!!!
To guarantee that the code within DllMain
and the static
variable constructors don't access the CLR, the new mixed DLL loading process needs a special project configuration. No function in the call tree rooted at DllMain
can be compiled to MSIL, because it means access to not initialized CLR. We can resolve the issues here, the object file containing these functions should be compiled without /clr. In our case, we set "No Common Language Runtime support" in project properties and "Common Language Runtime Support (/clr)" in every file with the managed classes' properties. If DllMain
attempts to execute the MSIL directly, it will result in a compiler warning (level 1) C4747. However, the compiler cannot detect cases where DllMain
calls a function in another module, which in turn attempts to execute the MSIL. Static
variables with MSIL instructions in the initialization code have to be placed in the managed modules, and should not be initialized and used by unmanaged initialization.
Then, we have to resolve a linker problem - the LNK2005 error. Microsoft does not support CWinApp
derived classes in a MFC extension DLL - so may be it is not a linker bug. In any case, if DllMain
from the CRT library is linked before the DllMain
from the MFC libraries, there will be a link error: "mfcs80ud.lib(dllmodul.obj) : error LNK2005: _DllMain@12 already defined in msvcrtd.lib(dllmain.obj)". I resolve this problem with a small trick: add macro AFX_MANAGE_STATE (AfxGetStaticModuleState())
to the static
constructor or at the beginning of InitInstance
.
The problem with InitApplication
has the same solution as in VS2003.
We don't need to call a special function to create and initialize the static MFC objects, and we don't need to call the termination function! But we have to close the MFC application (use the same code as in VS2003).
In contrast to VS2003, if the MFC application makes an activation of the MainFrame window after the WinForms Main Form activation, then the Managed Debugging Assistants throw the LoaderLock
exception. But as will be discussed later, we don't need two activated main windows, so we don't activate the MFC MainFrame window.
We write all the managed classes from the mixed DLL in the new C++/CLI. Use "Managed Extensions for C++ Syntax Upgrade Checklist" for migrating managed VC++ to C++/CLI, or use VS2003 MC++. In this case, simply set the project option /clr:oldSyntax instead of /clr. All other project settings would be the same as for C++/CLI.
Now, we can check this solution with the UMLEditor
(or with the simple wizards MFC/Dialog application).
After auto-updating the UMLEditor
in VS2005, make the following changes in the project configuration:
Configuration Type | Dynamic Library (.dll) |
Character Set | Use Unicode Character Set |
Use Managed Extensions | No |
Output File | .dll |
Update options which come against the compiler option /clr for the project configuration file and for other source files:
Basic Runtime Checks | Default |
Enable Minimal Rebuild | No |
Enable C++ Exceptions | With SHE Exceptions (/EHa) |
Debug Information Format | Program Database (/Zi) |
Create/Use Precompiled Headers | Not Using Precompiled Headers |
Entry Point |
Create the MfcAppAdapter.cpp file, from the picture below, with a simple managed class. We removed __crt_dll_initialize()
, __crt_dll_terminate()
from the VS2003 version and used the new C++/CLI syntax. Set CLR support in the MfcAppAdapter.cpp file property:
Compile with Common Language Runtime support | Common Language Runtime Support (/clr) |
#include "stdafx.h"
using namespace System;
using namespace System::Windows::Forms;
public ref class MfcAppAdapter
{
private:
static MfcAppAdapter^ m_Instance;
MfcAppAdapter()
{
Application::ApplicationExit +=
gcnew EventHandler(this,
&MfcAppAdapter::OnApplicationExit);
}
void OnApplicationExit(Object^ sender, EventArgs^ e)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CMDIFrameWnd* pMainFrame =
dynamic_cast <CMDIFrameWnd*>(AfxGetMainWnd());
::SendMessage(pMainFrame->GetSafeHwnd(),WM_CLOSE,0,0);
//change DLL flag in AFX_MODULE_STATE to careful call
//ExitInstance as for application context
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
pModuleState->m_bDLL = NULL;
delete m_Instance;
m_Instance=nullptr;
}
public:
static MfcAppAdapter^ GetInstance()
{
if(m_Instance==nullptr)
{
m_Instance = gcnew MfcAppAdapter();
}
return m_Instance;
}
bool OnCmdMsg( int nID)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CMDIFrameWnd* pMainFrame =
dynamic_cast <CMDIFrameWnd*>(AfxGetMainWnd());
return pMainFrame->OnCmdMsg(nID, 0, NULL, NULL) != FALSE;
}
};
Add InitApplication()
to UMLEditorDemo::InitInstance
in UMLEditorDemo.cpp. Build the DLL and test it in a simple WinForms 2005 application.
Architecture of the MfcAdapter
Where are we? So far, we have discussed only the technical problems involved with creating mixed MFC with a CWinApp
-derived class and a managed C++ DLL. But, how can we host MFC windows in WinForms or WPF Framework windows? Let's discuss the architecture of the MfcAdapter
wrapper:
The MfcAdapter
is a managed component that consists of MfcAppAdapter
and a suitable WndManager
:
MfcAppAdapter
(C++/CLI) is a wrapper to an unmanaged legacy MFC application. This is the kern of theMfcHost
. The general task involves the initialization of the encapsulated MFC application, the command interface to this application, and its windows hosting. The command interface means an interface to the command handlers in the legacy MFC application. To access the Framework, first of all, for attaching the MFC windows to the Framework,MfcAppAdapter
uses an externalIWndManager
interface. This solution allows us to use only oneMfcAppAdapter
for any Framework type.MfcAppAdapter
classes use only MFC based classes of the legacy MFC application, so it's independent of the specific classes ofUMLEditor
and can be used for wrapping other applications.WndManager
is a facade to the Framework which allows us to realizeMfcAppAdapter
as a component of the used Framework - WinForms component for the MDI Forms Demo, or WPFFrameworkElement
for the WPF Page Demo. For other managed Frameworks, suitableWndManager
s can be implemented. The common part ofWndManager
s is theIWndManager
interface.WndManager
implements this interface for the specific Framework. The remaining part ofWndManager
depends on the Framework and on the MFC application functionality that we need. For example, theMdiFormsWndManager
realizes the handling and update of the Framework'sToolStripMenuItems
andToolStripButtons
(MenuItem
s andToolBarButton
s for VS2003) in the MFC application, and supports the Visual Studio Designer in design-time.WpfPageWndManager
realizes theOpenDocument
method, and supports the WPF Autolayout manager. In this article, we will discussMfcAppAdapter
andWpfPageWndManager
.MdiFormsWndManager
is supplementary, in our case, and won't be discussed in this article, but you can use the source code.
Hosting of MFC Views and Dialogs in a WinForms Application
We discuss here the most important issues of the MfcAppAdapter
implementation. Use the source code for a detailed understanding.
Changing of the Encapsulated MFC Code
Before we can use the created mixed DLL, we have to resolve some problems.
Unicode Set
Unicode Character Set to simplify string message marshalling.
Hosting Dialogs, Creation, and Activation of the MFC Main Window (UmlEditorDemo.cpp)
MFC dialog windows use the main window of the legacy MFC application as the default parent window. We can keep the modal properties of the hosted dialogs, if we set the WinForms Framework MainForm window as a parent for the legacy MFC application main window. We can resolve this with the following solution:
- Add the
m_ExtFramework
member,AttachApplication
method, and theAttachApplication
static function to our application class.m_ExtFramework
saves the interface to the WinForms Framework (IFramework
).AttachApplication
method is a setter form_ExtFramework
, and theAttachApplication
static
function is a simple interface to access theAttachApplication
method (we keep the wrapper classesMfcAppAdapter
andViewCtrl
independent of the MFC application). The WinForms Framework calls theAttachApplication
method after a mixed DLL initialization but before using any of the legacy MFC applications. - Remove the creation of the main window from
InitInstance
to theAttachApplication
method. - Override
CMainFrame::PreCreateWindow
, and set the MainForm window handle fromIFramework
incs.hwndParent
. - By closing the MainForm window, the child's windows are destroyed. The legacy main window receives the
WM_DESTROY
message. We have to be careful to close the legacy MFC application in this case. But the normal close scenario occurs when a client calls dispose on theMfcAppAdapter
and closes the legacy MFC application before the WinForms Framework MainForm is closed. So, we write a warning to the output window if the client forgets this. - The message loop implementation is different for .NET 1.x and .NET 2.0. In .NET 1.x, the message loop tries to preprocess some common control messages (
WM_NOTIFY
,WM_COMMAND
) from the modeless dialog controls, first of all, in theMainForm
. We resolve this problem by explicitly pre-translating these messages in the legacy MFC application.
We also use CNotifyHook
as the COleFrameHook
interface realization, to enable the WinForms Framework windows to synchronize with the legacy MFC application windows.
We keep the legacy MFC application main window invisible. So, some source code has to be removed from the application InitInstance
:
//// The main window has been initialized, so show and update it.
//pMainFrame->ShowWindow( m_nCmdShow );
//pMainFrame->UpdateWindow();
The command line processing is in the WinForms Framework now. So, remove this code too:
//// Dispatch commands specified on the command line
// if (!ProcessShellCommand( cmdInfo ))
// return FALSE;
Hosting View
For hosting MFC views in WinForms controls, we have to change the view classes and the dependent classes, as well:
- First of all, the view's content has to be invisible when creating and initializing. It would be activated only after attaching to the WinForms control. In the source code, we set
bMakeVisibleparameter
tofalse
in the overriddenMultiDocTemplate::OpenDocumentFile
,CDocManagerEx::OpenDocumentFile
, andCMainFrame::OnWindowNew
. - We have to save the pointer to the last opened document, which we will use in the WinForms control. It's realized by the
SetOpenedDocument
method of ourIFramework
interface. - Don't use
CView::OnCreate
. Creating and initializing of our view class' members inherited fromCWnd
,Control
,DropTarget.Register
,ToolTip
, and so on, should be removed from theOnCreate
method to theOnInitialUpdate
. In this case, these members will be created after the view is attached to the WinForms control. - After the view is attached to the WinForms control by the
WndManager
, theCView::GetParent
method will return the parenthWnd
of the WinForms control. So, the entire code uses theCView::GetParent
; for exampleSetTitle
, has to be changed to theGetNativeParent
method of ourIFramework
. We make this forCDocManagerEx::OpenDocumentFile
,CUMLEditorDemoDoc::CanCloseFrame
, andCUMLEditorDemoDoc::UpdateFrameCounts
. By closing the control container,WndManager
has to dispose the control with the attached view!!! In this case, the view would be detached, the MFCCFrame
parent of the view would be restored, and then the view would be closed in the MFC application. We have to check that all views are detached from the WinForms controls before we close the MFC application.UMLEditorDemo::DetachApplication
method does this check. - For auto-layout support, we have to implement some methods in our hosted view:
AutoSize
,GetPreferredSize
, andScaleControl
. We use the base classCLayoutView
with these methods as the interface for theMfcAppAdapter
from the hosted view. The view has to inherit this class. All custom properties of the view which are used in the control have to be implemented in this fashion. - In this article, we don't describe the changes of the view classes inherited from the controls and views with
m_dropTarget
(support of drag & drop), because theUmlEditor
does not have these views.
Wrapper Classes
MfcAppAdapter
The command interface of the legacy MFC application is wrapped by two methods: OnCmdMsg
(CCmdTarget::OnCmdMsg
) and OnUpdateCmdMsg
. We also use the class CCmdUIHandler
for the user interface update. This class realizes the CCmdUI
interface for the framework menu items and toolbar buttons. The GetMfcMenuString
method realizes the access to the MFC CMenu
to support "Recent" menu items in the WinForms menu.
In the VS2003 version, the TranslateMsg
method supports message dispatching for common controls.
MfcAppAdapter
is a singleton. The method CreateInstance
can be used to access a private static
instance. The MfcAppAdapter
constructor calls UMLEditorDemo::AttachApplication
to initialize the legacy MFC application. The MfcAppAdapter
's Dispose
checks if all the hosted views are closed, and then closes the legacy application with the UMLEditorDemo::DetachApplication
method.
ViewCtrl
ViewCtrl
supports MFC view hosting in WinForms controls. It is a helper class of MfcAppAdapter
.
- As a child of a WinForms control,
ViewCtrl
inherits theControl
interface. For a view hosting, we have to detach theCView
object from the "CView
" window and attach theCView
object to the "ViewCtrl
" window by creating a "ViewCtrl
" window. - We keep dispatching messages from the "
CView
" window to theCView
object, by replacingCViewWndProc
withStubWndProc
.StubWndProc
dispatches these messages from the "CView
" window to the "ViewCtrl
" window and then to theCView
object. - To keep the
CWnd
interface for the "CView
" window, we have to attach aStubWnd
(CWnd
based) to the "CView
" window. Now, ourCView
object is connected with the "ViewCtrl
" window and with the "CView
" window! It's not so bad. See the picture: - We can make these operations by creating the "
ViewCtrl
" window, if we change the "Windows Class" in the overriddenViewCtrl::CreateParams
. Then, we call the methodViewCtrl::AttachView
inViewCtrlWindowProc
by using theWM_CREATE
message. When theCView
object is attached to the "ViewCtrl
" window, we callCView::OnInitialUpdate
; and the view content is rendered in the WinForms form, instead of the MFCCFrame
. - We have to synchronize our
ViewCtrl
frame (WinForms form) with the MFCCFrame
for supporting the MDI logic of the legacy MFC application. We make this with the following methods:OnGotFocus
ProcessDialogKey
SetText
GetParent
- Overriding the
OnHandleDestroed
method detaches the view from the control to the MFCCFrame
and closes the view using dispose. - For autolayout support, the methods
AutoSize
,GetPreferredSize
, andScaleControl
of the base control (VS2005) or of the special base classLayoutControl
(VS2003) are overridden. The hosted view, connected through theCLaoutView
interface, implements the necessary functionality for these methods. - The
MfcAppAdapter
uses theIWndManager
interface for attaching the createdViewCtrl
controls to the external Framework and for activating the main window when the control receives theGotFocus
event. Otherwise, the external Framework gets theOnClosing
cancel event handler of theMfcAppAdapter
. The Framework can use this handler to support the "CanClose
" function of the legacy application. In the WinForms Framework, we simply connect theOnClosing
cancel event handler with theClosing
event of the MainForm.
UmlEditorCommand
The UmlEditorCommand
enumeration contains all the command IDs which can be handled in the legacy MFC application.
WPF Autolayout In Hosted WinForms Controls
The WPF class WindowsFormsHost
allows us to realize the MfcAppAdapter
as a WPF FrameworkElement
. Moreover, the VS2005 Visual Designer does not support WPF, and it makes the integration of the MfcAdapter
and the WFP Framework easier! The only problem that has to be resolved is that the current version of WindowsFormsHost
does not support the WPF Autolayout manager. In this simple version of WPF WndManager
, we show how it can be realized.
UmlEditorHost
UmlEditorHost
is an extended WindowsFormsHost
with auto-layout support for the hosted control. The standard solution involves overriding the WindowsFormsHost
base methods: MeasureOverride
and ArrangeOverride
.
In the MeasureOverride
, we get the preferred size from the hosted control (method Control.GetPreferredSize
), and decrease it to the desired size, if it's necessary, by keeping the width/height ratio constant.
In the ArrangeOverride
, we scale the hosted control to the preferred size, decreased to the final size, if it's necessary, and multiply it with the zoom factor. Zoom factor is kept in the added dependency property Zoom
, which can be bound with an external layout manager.
WpfPageWndManager
WpfPageWndManager
is the WPF realization of the IWndManager
interface. In the AttachCtrl
method, we simply add the control created by MfcAppAdapter
to UmlEditorHost
. In our case, we don't edit the hosted UML diagrams, so we disable the hosted control and don't use its OnClosing
event handler.
WpfPageWndManager
is responsible for the creation and disposal of MfcAppAdapter
and others created by the MfcAppAdapter
controls. It's is a singleton, and it creates the MfcAppAdapter
instance in its own constructor. In the DisposeContainer
, it saves all the hosts with the created controls. Using the Application.Current.MainWindow.Unloaded
event, we dispose the DisposeContainer
and then the MfcAppAdapter
.
.NET 2.0 Control Layout Adapter to .NET 1.x Control
We need MfcHost1Adapter
between WpfPageWndManager
and MfcAppAdapter
if the last one is a .NET 1.x component. The reason for this solution is that .NET 1.x WinForms controls don't have the new layout methods AutoSize
, GetPreferredSize
, and ScaleControl
, and cannot directly support WPF auto-layout. Using a composite control, the .NET 2.0 LayoutControlAdapter
, as the container and the .NET 1.x LayoutControl
as a constituent control, the MfcHost1Adapter
allows us to realize the .NET 1.x WinForms control as a .NET 2.0 WinForms control for the WpfPageWndManager
. The standard methods of the LayoutControlAdapter
: AutoSize
, GetPreferredSize
, and ScaleControl
would be overridden by the custom methods of the contained LayoutControl
. The LayoutControlAdapter
can be used in other projects for hosting .NET 1.x controls in the .NET 2.0 Framework.
Extended MfcAppAdapter
and WndManagerAdapter
allow us to substitute the attached LayoutControl
for the composite LayoutControlAdapter
.
Demo Framework
You can create simple demo applications yourself or use the downloaded demos. Try using other managed Frameworks instead of these demos (it's more interesting). Here, I'll describe two demo applications: an MDI forms demo and a WPF page demo.
MDI Forms Demo
Create a C# Windows application project and add collections of ToolStripMenuItem
s and ToolStripButton
s (MenuItem
s and ToolBarButton
s for VS2003) to the main form. Add MdiFormsWndManager.dll to your toolbox. Then, drag UmlEditorComponent
from the toolbox to your main form. Now, ToolStripMenuItems
and ToolStripButtons
have become the CommandName
extended properties, where you can set the suitable command for the connected UmlEditorComponent
from the listbox as shown in the picture. Simply compile and run the application:
WPF Page Demo
Create a WinFX Windows application. Add these references - MfcAppAdapter.dll, WpfPageWndManager.dll, and WindowsFormsIntegration.dll. If you use the VS2003 version of the MfcAppAdapter.dll, add MfcHost1Adapter.dll also.
Create a page instead of the window in the picture. We have a simple FlowDocument
with AS:UMLEditorHost
and FrameworkElement
s. The FileSource
property of the AS:UMLEditorHost
defines the path to the UML diagram which has to be added to the document. For the SinglePageViewer
zoom support, the property Zoom
of the AS:UMLEditorHost
is bound with SinglePageViewer.Zoom
in the SinglePageViewer
resource. Simply compile and run the application:
<!--WPF Page Demo-->
<?Mapping XmlNamespace="AS" ClrNamespace="AS.MfcHost2"
Assembly="WpfPageWndManager"?>
<Page xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
xmlns:AS="AS" Width="800" Height="540">
<SinglePageViewer Name="ZoomSource">
<!--Bind AS:UMLEditorHost.Zoom and SinglePageViewer.Zoom
for the Zoom support-->
<SinglePageViewer.Resources>
<Style TargetType="{x:Type AS:UMLEditorHost}">
<Setter Property="Zoom"
Value="{Binding ElementName=ZoomSource,
Path=Zoom, Mode=OneWay}"/>
</Style>
</SinglePageViewer.Resources>
<FlowDocument TextAlignment="Left" Background="AliceBlue">
<Paragraph>
<Bold>Hosting MFC application in WPF Page</Bold>
</Paragraph>
<Paragraph KeepTogether="True">
Document - <Italic>UMLEDI1.uml</Italic>
<!--use hosted MFC application as FrameworkElement-->
<AS:UMLEditorHost FileSource="../../UMLEDI1.uml"/>
</Paragraph>
<Paragraph KeepTogether="True">
Document - <Italic>UMLEDI2.uml</Italic>
<!--use hosted MFC application as FrameworkElement-->
<AS:UMLEditorHost FileSource="../../UMLEDI2.uml"/>
</Paragraph>
</FlowDocument>
</SinglePageViewer>
</Page>
Deployment and Installation
See "Deployment and Installation" from "MfcAdapter - not OLE .Net/WPF container of MFC applications".
- Hosted simple wizards MFC/Dialog application:
Programming Guide with Source Code for VS 2005 and VS2010
I offer the programming guide and source code of a simple wizards MFC/Dialog application
DialogWiz
as a starting point for your MFC application hosting. For simplicity, I removed the hosting MFC views support from theMfcAppAdapter
component.Simply change your MFC application as described in the programming guide. Using
MfcAppAdapter.OnCmdMsg()
, you can send commands from a .NET Form to your MFC command handlers and show your dialogs.Code is compatible with VS 2003-VS 2012, you need only update the project settings.
- MfcAdapter VS2003\VS2005 with hosted
UMLEditor
:Source Code
After we unzip, we have two solutions -
UMLEditor2003
(VS2003) andUMLEditor2005
(VS2005). The legacy MFC source code (directory Legacy) is common for both solutions. Please don't convert theUMLEditor2003
solution to VS2005 orUMLEditor2005
solution to VS2010 - you need some source code changes in this case.The
UMLEditor2003
solution contains theMfcAppAdapter
,MdiFormsWndManager
, and theMdiFormsDemo
projects. Set theMdiFormsDemo
as the startup project, and then build and run.The
UMLEditor2005
solution has additionalWpfPageWndManager
,WpfPageDemo
, andMfcHost1Adapter
projects.WpfPageDemo
andWpfPageWndManager
are compatible with the WinFX Runtime Components 3.0 - Beta 2 (Jan CTP assemblies, version 6.0.5070.0).Check the reference to WindowsFormsIntegration.dll (normally it's ..\Program Files\Reference Assemblies\Microsoft\WPF\v3.0\WindowsFormsIntegration.dll). Set
MdiFormsDemo
orWpfPageDemo
as the startup project, and then build and run.If you want to use the VS2003 version of the
MfcAppAdapter
with WPF, add a reference to the VS2003 MfcAppAdapter.dll inMfcHost1Adapter
. BuildMfcHost1Adapter
, and add references to the VS2003 MfcAppAdapter.dll and MfcHost1Adapter.dll inWpfPageWndManager
andWpfPageDemo
, instead of the VS2005 MfcAppAdapter.dll. Rebuild and run.I tried to keep the encapsulated MFC code as short as possible, so I have not fixed some of the native problems of this code, for example, an unhandled exception when opening a recent file that does not exist.
- MfcAdapter 3.0. Demo VS2019:
MfcAdapter
3.0 does not need a special installation. After extraction from the archive, You will have source code and executable programs of 5 MFC applications:
- "
DialogWiz
" - Simple MFC application without views (only dialogs) created with VS 2005 wizard - "
DialogEditor
" - DIY vector and dialog editor by Johan Rosengren (http://www.codeproject.com/) - "
UMLEditor
" - Revisiting the vector editor by Johan Rosengren (http://www.codeproject.com/) - "
Mesh
" - A small VRML viewer using OpenGL and MFC (http://www.codeproject.com/) - "
SimpleCtrl
" - MFC simple ofCLayoutView
,CView
Hosted in WinForms:
- FormsDemo_UmlEditor.exe - Container with hosted MFC applications "
UmlEditor
" and "DialogEditor
" - MdiFormsDemo.exe - Full version of the hosted "
UmlEditor
" - SdiFormsDemo.exe - Full version of the hosted "
DialogEditor
" - FormDemo_SimpleCtrl.exe - Simple test for the
CScrollView,
CListCtrl
- FormsDemo_DialogWiz.exe - hosted "
DialogWiz
"
Hosted in WPF:
- WpfDemo_UmlEditor.exe - Container with hosted MFC applications "
UmlEditor
" and "DialogEditor
" - WpfPageDemo_UmlEditor.exe - WPF
FlowDocument
. Container with hosted MFC applications "UmlEditor
" and "DialogEditor
" - WpfPageDemo_WrlViewer.exe - hosted VRML viewer
Simply run the necessary application.
Source Code
NOTE: MfcAdapter
3.0 demo supports only one configuration: x86 Debug, Unicode, .NET 4.0-4.7
After unzip, we have one VS 2019 solution - \Samples\Src\Samples.sln with all demo projects. All projects build outputs will be copied in the common execute directory \Samples\Src\x86\Debug with necessary MfcAdapter DLLs.
By reference error, simply repeat a build.
I tried to keep the encapsulated MFC code as short as possible, so I have not fixed some of the native problems of this code, for example, compiler warnings, an unhandled exception when opening a recent file that does not exist and others.
Host Your MFC Application
For hosting your MFC application, use - x86 Debug Unicode .NET 4.0-4.7 version of MfcAdapter
:
\Debug:
- MfcAppAdapter.dll, ViewControl.dll, ViewFrameworkElement.dll
\Include:
- HostApp.h, LayoutView.h
Help:
- MfcAdapterAPI.chm, ProgrammingGuide.htm
Points of Interest
I assume that this article's solution - the MFC extension DLL with the CWinApp
derived class - allows you to host MFC applications in any (not only managed WinForms/WPF) Win32 no MFC Framework by changing the MDI classes and using a suitable wrapper. Otherwise, this may be a useful sample for Visual Studio designer programming in a WinForms component (UmlEditorComponent
) as it supports setting of the Host in the designer generated code.
History
- 30th December, 2005: Initial post
- 31st May, 2022: Article updated