cpp
// MfcEmbedWpf.cpp : Defines the class behaviors for the application.
//
#include "stdafx.h"
#include "MfcEmbedWpf.h"
#include "MfcEmbedWpfDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CMfcEmbedWpfApp
BEGIN_MESSAGE_MAP(CMfcEmbedWpfApp, CWinApp)
ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()
// CMfcEmbedWpfApp construction
CMfcEmbedWpfApp::CMfcEmbedWpfApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}
// The one and only CMfcEmbedWpfApp object
CMfcEmbedWpfApp theApp;
// CMfcEmbedWpfApp initialization
BOOL CMfcEmbedWpfApp::InitInstance()
{
// InitCommonControlsEx() is required on Windows XP if an application
// manifest specifies use of ComCtl32.dll version 6 or later to enable
// visual styles. Otherwise, any window creation will fail.
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
// Set this to include all the common control classes you want to use
// in your application.
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
CWinApp::InitInstance();
// Create the shell manager, in case the dialog contains
// any shell tree view or shell list view controls.
CShellManager *pShellManager = new CShellManager;
// Activate "Windows Native" visual manager for enabling themes in MFC controls
CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need
// Change the registry key under which our settings are stored
// TODO: You should modify this string to be something appropriate
// such as the name of your company or organization
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
CMfcEmbedWpfDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
else if (nResponse == -1)
{
TRACE(traceAppMsg, 0, "Warning: dialog creation failed, so application is terminating unexpectedly.\n");
TRACE(traceAppMsg, 0, "Warning: if you are using MFC controls on the dialog, you cannot #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS.\n");
}
// Delete the shell manager created above.
if (pShellManager != NULL)
{
delete pShellManager;
}
#ifndef _AFXDLL
ControlBarCleanUp();
#endif
// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}
int CMfcEmbedWpfApp::ExitInstance()
{
CoUninitialize();
return CWinApp::ExitInstance();
}
// MfcEmbedWpfDlg.cpp : implementation file
//
#include "stdafx.h"
#include "MfcEmbedWpf.h"
#include "MfcEmbedWpfDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
#import "UserControlLib.tlb" raw_interfaces_only
#define WM_WPF_BUTTON_CLICK (0x0400 + 100)
namespace UserControlLib
{
struct IXamlControl;
};
class CHostCallback : public UserControlLib::IHostCallback
{
private:
ULONG m_ref;
CMfcEmbedWpfDlg* m_pDlg;
public:
CHostCallback(CMfcEmbedWpfDlg* dlg) : m_ref(1),m_pDlg(dlg)
{
}
CHostCallback() : m_ref(1), m_pDlg(NULL)
{
}
STDMETHOD(QueryInterface)(REFIID riid, void** ppv)
{
if (ppv == NULL)
return E_POINTER;
*ppv = NULL;
if (riid == IID_IUnknown || riid == __uuidof(UserControlLib::IHostCallback))
{
*ppv = this;
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHOD_(ULONG, AddRef)()
{
return ++m_ref;
}
STDMETHOD_(ULONG, Release)()
{
ULONG n = --m_ref;
if (n == 0)
delete this;
return n;
}
STDMETHOD(UpdateContent)(BSTR data)
{
CString str(data);
CString msg;
msg.Format(L"Callback from WPF:%s",str);
AfxMessageBox(msg);
//if (m_pDlg)
// m_pDlg->DoSomething();
return S_OK;
}
};
CMfcEmbedWpfDlg::CMfcEmbedWpfDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(IDD_MFCEMBEDWPF_DIALOG, pParent)
{
m_spCtrl = NULL;
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
CMfcEmbedWpfDlg::~CMfcEmbedWpfDlg()
{
if (m_spCtrl)
{
m_spCtrl->Release();
m_spCtrl = NULL;
}
}
void CMfcEmbedWpfDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CMfcEmbedWpfDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_SIZE()
ON_BN_CLICKED(IDOK, &CMfcEmbedWpfDlg::OnBnClickedOk)
ON_MESSAGE(WM_WPF_BUTTON_CLICK, &CMfcEmbedWpfDlg::OnWpfButtonClick)
END_MESSAGE_MAP()
// CMfcEmbedWpfDlg message handlers
BOOL CMfcEmbedWpfDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
UserControlLib::IXamlControlPtr Ctrl;
HRESULT hr = E_FAIL;
hr = Ctrl.CreateInstance(__uuidof(UserControlLib::XamlControlCom));
//UserControlLib::IXamlControl* pp = Ctrl.GetInterfacePtr();
m_spCtrl = Ctrl.GetInterfacePtr();
if (FAILED(hr) || m_spCtrl == NULL)
{
m_spCtrl = NULL;
return TRUE;
}
m_spCtrl->AddRef();
CWnd* pHost = GetDlgItem(IDC_WPF_HOST);
CRect rc;
pHost->GetClientRect(&rc);
long long hWpf = 0;
hr = m_spCtrl->Create((long long)pHost->GetSafeHwnd(),rc.Width(), rc.Height(), &hWpf);
m_spCtrl->SetNotifyHwnd((long long)GetSafeHwnd());
hwndWpf = (HWND)hWpf;
GetClientRect(&m_rcDlgInit);
GetDlgItem(IDC_WPF_HOST)->GetWindowRect(&m_rcHostInit);
ScreenToClient(&m_rcHostInit);
m_pCallback = new CHostCallback(this);
m_spCtrl->SetCallback(m_pCallback);
return TRUE;
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CMfcEmbedWpfDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
// The system calls this function to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CMfcEmbedWpfDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CMfcEmbedWpfDlg::OnBnClickedOk()
{
}
void CMfcEmbedWpfDlg::OnSize(UINT nType, int cx, int cy)
{
CDialogEx::OnSize(nType, cx, cy);
CWnd* pHost = GetDlgItem(IDC_WPF_HOST);
if (hwndWpf && pHost && ::IsWindow(pHost->GetSafeHwnd()))
{
int left = m_rcHostInit.left;
int top = m_rcHostInit.top;
int rightMargin = m_rcDlgInit.right - m_rcHostInit.right;
int bottomMargin = m_rcDlgInit.bottom - m_rcHostInit.bottom;
pHost->MoveWindow(left, top, cx - left - rightMargin, cy - top - bottomMargin);
//if (m_spCtrl)
// m_spCtrl->Resize( rc.Width(), rc.Height());
::MoveWindow(hwndWpf, 0, 0, cx - left - rightMargin, cy - top - bottomMargin, TRUE);
}
}
LRESULT CMfcEmbedWpfDlg::OnWpfButtonClick(WPARAM, LPARAM)
{
CString msg;
msg.Format(L"OnWpfButtonClick %d", 0);
AfxMessageBox(msg);
return 0;
}
//IXamlControl.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace UserControlLib
{
[ComVisible(true)]
[Guid("691D8E3F-5E64-435D-8B6E-87A9FD8941AF")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IHostCallback
{
void UpdateContent(string data);//Send Data to MFC
}
[ComVisible(true)]
[Guid("2A8686A4-69AA-4890-87B6-2E49FE55F857")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IXamlControl
{
long Create(long parent, int width, int height);
void SetText(string text);//Receive Data From MFC.
void SetCallback(IHostCallback cb);
void SetNotifyHwnd(long hwnd);
}
}
//XamlControlCom.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Windows.Interop;
namespace UserControlLib
{
[ComVisible(true)]
[Guid("CF274B54-4E1A-4833-80C1-D789A28CA568")]
[ClassInterface(ClassInterfaceType.None)]
public class XamlControlCom : IXamlControl
{
private HwndSource source;
private UserControl1 control;
public long Create(long parent, int width, int height)
{
System.Diagnostics.Debug.Assert(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA);
control = new UserControl1();
HwndSourceParameters p = new HwndSourceParameters("WpfHost", width, height);
p.ParentWindow = (IntPtr)parent;
p.WindowStyle =
0x40000000 | // WS_CHILD
0x10000000; // WS_VISIBLE
source = new HwndSource(p);
source.RootVisual = control;
control.SetHostWnd(parent);//Default notify window
return (long)source.Handle;
}
public void SetText(string text)
{
if (control != null)
control.SetTitle(text);
}
public void SetCallback(IHostCallback callback)
{
if (control != null)
control.SetCallback(callback);
}
public void SetNotifyHwnd(long hwnd)
{
if (control != null)
control.SetHostWnd(hwnd);
}
}
}
//UserControl1.xaml
<UserControl x:Class="UserControlLib.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:UserControlLib"
mc:Ignorable="d">
<!-- 背景改成绿色 -->
<Grid Background="LightGreen">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock x:Name="txtTitle"
FontSize="20"
Margin="10"
HorizontalAlignment="Center"
Text="Hello WPF"/>
<Button x:Name="btnTest"
Width="120"
Height="40"
Content="点击我"
Click="btnTest_Click"/>
</StackPanel>
</Grid>
</UserControl>
//UserControl1.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Runtime.InteropServices;
namespace UserControlLib
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
[DllImport("user32.dll")]
private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
private long lHostWnd = 0;
private const int WM_WPF_BUTTON_CLICK = 0x0400 + 100;
private IHostCallback _callback;
public UserControl1()
{
InitializeComponent();
}
public void SetTitle(string text)
{
txtTitle.Text = text;
}
public void SetHostWnd(long hWnd)
{
lHostWnd = hWnd;
}
public void SetCallback(IHostCallback callback)
{
_callback = callback;
}
private void btnTest_Click(
object sender,
RoutedEventArgs e)
{
if(lHostWnd != 0)
{
PostMessage((IntPtr)lHostWnd, WM_WPF_BUTTON_CLICK,IntPtr.Zero, IntPtr.Zero);
}
txtTitle.Text = "按钮点击时间:" + DateTime.Now.ToString("HH:mm:ss");
if(_callback != null)
{
_callback.UpdateContent(txtTitle.Text)
}
}
}
}
注1:上面的WPF基于.Net Framework 4.8 构建,如果是比较新的.Net Core,则不支持TlbExp导出类型表,需根据COM组件反写出接口定义文件xxx.idl,再借助midl生成tlb类型表和接口头文件(称之为.NET Core COM Host方案)。基于.Net Framework COM的话,就比较方便:
TlbExp.exe UserControlLib.dll /out:UserControlLib.tlb # 生成.Net Com 库类型表信息
regasm UserControlLib.dll /regfile:UserControlLib.reg # 生成.Net Com 库的注册信息,可以将注册键HKEY_CLASSES_ROOT改为HKEY_CURRENT_USER\Software\Classes,执行reg import UserControlLib.reg将信息注册到当前用户,避免需要管理员权限。(基于.Net Framework创建的COM库,无法使用manifest进行免注册,因为clrCalss无法被WinSxS正确解析)。
注2:使用.Net Framework占用的资源比.Net Core小。因此在Windows下尽量使用.Net Framework。