MFC 嵌入WPF控件

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。

相关推荐
web守墓人1 小时前
【goed/ui】go开发windows原生应用之Helloworld篇
windows·ui·golang
Lysander.Jovian1 小时前
安装Windows server 2016
windows
水饺编程2 小时前
第5章,[Win32 章节] :绘制平面直角坐标系
c语言·c++·windows·visual studio
是店小二呀2 小时前
AdGuard Home 部署实战:Linux/Windows 安装、DNS 配置与固定公网管理
linux·运维·windows
fangjianj17 小时前
windows环境反弹shell
linux·windows·web安全
有味道的男人17 小时前
得物详情 API|支持实时价格、成色、鉴别服务数据
windows·python·api
老王的笔记v18 小时前
46期 Windows超级管理器 垃圾清理 预装卸载与系统优化工具箱
windows·系统架构
张人玉19 小时前
基于 C# / .NET 8 + Windows Forms + SQLite 的桌面银行业务演示系统——华夏示范银行管理系统
windows·sqlite·c#·.net
Mr-Apple1 天前
系统找不到指定的文件-CreateInstance/CreateVm/HCS/ERROR_FILE_NOT_FOUND
linux·windows·ubuntu·wsl