Opening Modal Managed Windows from MFC

Many native applications today are mixed with .NET extensions, either for business logic or user interface. In this article I will talk about the case when you want to display a modal managed window in an MFC application, with the problems that might appear and the solutions for them. I will go through both windows forms and WPF. However, I will not use a mixed-mode DLL, but expose .NET components to COM and use that from C++.

Creating Class Libraries

I will assume the following projects are created:

Windows Forms

  • Create a new Windows Form Control Library called WinFormsLib
  • Add a simple windows form called SampleForm; the form should look like this:

Windows Presentation Foundation

  • Create a new WPF User Control Library called WpfControlLib
  • Add a simple WPF window called SampleWpfWindow; the window show look like this:

What we want to do is opening both of these managed windows as modal dialogs from an MFC application. To expose them to the native world, I will create a .NET class library project registered for COM interop. That would allow us to use the .NET code without building a mixed-mode DLL in C++/CLI.

The details for registering for COM interop are beyond the scope of this article. In a few words one needs to do the following:

  • Mark each interface exposed to COM with attributes GuidAttribute and InterfaceTypeAttribute
  • Mark each interface method exposed to COM with attribute DispIdAttribute
  • Mark each class exposed to COM with attributes GuidAttribute, ClassInterfaceAttribute and ProgIdAttribute
  • Set the ComVisibleAttribute to true in AssemblyInfo.cs, or use the ComVisibleAttribute with true for every type you want to expose to COM
  • From the Project Properties > Build, check the Register for COM interop option

Creating a Class Library Consumed from MFC

The next step is to create a project called DotNetDemoLib, and add references to the two projects created earlier, WinFormsLib and WpfControlLib.
I will add an interface called IWindows to this project, with two methods. Since this needs to be exposed to COM it must be decorated with the attributes mentioned earlier. The interface looks like this:

namespace DotNetDemoLib
{
   [Guid("B3E12DC9-817F-4cba-BE4D-035D2D5A5EC9")]
   [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
   public interface IWindows
   {
      [DispId(1)]
      void StartWpfWindow();

      [DispId(2)]
      void StartForm();
   }
}

Method StartWpfWindow would open the window from the WpfControlLib project, and StartForm the windows form from the WinFormsLib project.

The implementation for this interface would like this:

namespace DotNetDemoLib
{
   [Guid("262E0AF6-2546-4284-8426-4C62B3341584")]
   [ClassInterface(ClassInterfaceType.None)]
   [ProgId("DotNetDemoLib.ManagedWindows")]
   public class ManagedWindows : IWindows
   {
      SampleWpfWindow m_window;
      SampleForm m_form;

      #region IWindows Members

      public void StartWpfWindow()
      {
         m_window = new SampleWpfWindow();
         m_window.ShowDialog();
      }

      public void StartForm()
      {
         m_form = new SampleForm();
         m_form.ShowDialog();
      }

      #endregion
   }
}

After building this project, with the “Register to COM interop” option, a type library file (.tlb) is generated. This file must be imported in the C++ project in order to be able to use these COM components.

An MPF Demo Project

To demonstrate the usage of the IWindows interface, we need an MFC project. For the sake of simplicity this can be a dialog base application. Let’s call it MfcDemoApp. The dialog should have two buttons, one for opening the Windows Forms window and one for opening the WPF window.

In order to be able to use the IWindows interface we need to import the TLB file (of course the actual #import directive must use the correct path to the file).

#import "DotNetDemoLib.tlb"
using namespace DotNetDemoLib;

With that done, the handlers for the button click messages can look like this:

void CMfcDemoAppDlg::OnBnClickedButtonOpenWpf()
{
   IWindowsPtr pWindows(__uuidof(ManagedWindows));

   try
   {
      pWindows->StartWpfWindow();
   }
   catch(_com_error& ce)
   {
      AfxMessageBox((LPCTSTR)ce.ErrorMessage());
   }
}

void CMfcDemoAppDlg::OnBnClickedButtonOpenWinforms()
{
   IWindowsPtr pWindows(__uuidof(ManagedWindows));

   try
   {
      pWindows->StartForm();
   }
   catch(_com_error& ce)
   {
      AfxMessageBox((LPCTSTR)ce.ErrorMessage());
   }
}

Problems and Solutions

Windows Forms

After putting all this together we can run the MFC application and open the two windows. Let’s start with the Windows Form window. The image below shows how it looks.

You can notice that the MFC dialog is not accessible when the form is opened, so indeed we have the behavior of a modal dialog. But you can also notice the taskbar shows two applications, MfcDemoApp and SampleForm, even though we have only one. This is because, by default, the form is set to appear in taskbar, because the window has the WS_EX_APPWINDOW window style set. This style forces a top-level window into the taskbar when the window is visible.

To prevent the form from showing in the taskbar we can set the ShowInTaskbar property to false.

public void StartForm(long parentWindow)
{
   m_form = new SampleForm();
   m_form.ShowInTaskbar = false;
   m_form.ShowDialog();
}

Windows Presentation Foundation

Let’s try opening the WPF window. The window shows up correctly, but there are two problems:

  • There are again two applications in the taskbar
  • The WPF window does not behave like a modal dialog, because you can select from the taskbar any of the two and have it shown separately, even though you cannot access the parent, MFC dialog.

    In this image only the parent, MFC dialog, is visible, but you can see the WPF window in the taskbar, so you can click on it and bring it to foreground.

    In this image only the chid, WPF window, is visible, but you can see the MFC dialog in the taskbar.

    This is because the WPF window does not have the owner set.

To solve these problems we have to do the following:

  • Set the ShowInTaskbar property of the Window to false, just like for the windows form.
  • Set the owner for the WPF window.

The first task is pretty trivial. The second is a little bit more complicated. For that we need to use a helper class called WindowInteropHelper that assists interoperability between WPF and Win32. This class has two properties:

  • Handle, that only returns the handle of the WPF window used to create the object;
  • Owner, that gets or sets the handle of the owner window.

We need to use this later property, but we must pass the handle to the owner window from C++. You might be tempted to add an IntPtr argument to the StartWpfWindow(), but the COM doesn’t seem to be able to marshal that. When the method is called it triggers a COM exception saying that the method does not exist. The workaround is to pass an integer that can be used to construct an IntPtr in C#.

The interface method should look like this:

[DispId(1)]
void StartWpfWindow(long parentWindow);

The implementation changes to:

public void StartWpfWindow(long parentWindow)
{
   m_window = new SampleWpfWindow();
   WindowInteropHelper wnd = new WindowInteropHelper(m_window);
   wnd.Owner = new IntPtr(parentWindow);
   m_window.ShowInTaskbar = false;

   m_window.ShowDialog();
}

And the usage from VC++ also changes to:

void CMfcDemoAppDlg::OnBnClickedButtonOpenWpf()
{
   IWindowsPtr pWindows(__uuidof(ManagedWindows));

   try
   {
      long handle = reinterpret_cast<long>(m_hWnd);
      pWindows->StartWpfWindow(handle);
   }
   catch(_com_error& ce)
   {
      AfxMessageBox((LPCTSTR)ce.ErrorMessage());
   }
}

Now, if you run the application again you can see that the WPF window acts like a true modal window.

Back to Windows Forms

What if you want to set the parent for a Windows Form window? You’d need to override the CreateParams property that provides the required parameters for creating a control. Its type is CreateParams (same name). One of the members of this type is property Parent, which you can set with a handle passed from C++. However, since we cannot change the code for the SampleForm class, we can create a new form, derived from SampleForm that overrides CreateParams and sets the parent. This class can look like this:

namespace DotNetDemoLib
{
   class WrapperForm : SampleForm
   {
      IntPtr m_hwndParent;

      public WrapperForm(IntPtr parent)
      {
         m_hwndParent = parent;
      }

      protected override CreateParams CreateParams
      {
         get
         {
            CreateParams cp = base.CreateParams;

            cp.Parent = m_hwndParent;

            return cp;
         }
      }
   }
}

The interface method should change to:

[DispId(2)]
void StartForm(long parentWindow);

The implementation for the interface method becomes:

public void StartForm(long parentWindow)
{
   m_form = new WrapperForm(new IntPtr(parentWindow));
   m_form.ShowInTaskbar = false;
   m_form.ShowDialog();
}

And the usage from VC++ changes to:

void CMfcDemoAppDlg::OnBnClickedButtonOpenWinforms()
{
   IWindowsPtr pWindows(__uuidof(ManagedWindows));

   try
   {
      long handle = reinterpret_cast<long>(m_hWnd);
      pWindows->StartForm(handle);
   }
   catch(_com_error& ce)
   {
      AfxMessageBox((LPCTSTR)ce.ErrorMessage());
   }
}

Conclusions

Displaying managed modal windows from native code is pretty easy, but you should set the owner of the managed window and if you don’t want the window to show up in task bar then also set the ShowInTaskbar (a property of both System.Windows.Form and System.Windows.Window) to false.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read