VS2022 MFC U/I - WPF U/I 붙이기 - 3
MFC 애플리케이션은 기존처럼 CView, CDialog 기반으로 유지하면서, 그 내부에 WPF 컨트롤을 child 형태로 삽입(hosting) 하는 방식입니다. 이를 좀 더 구조적으로 설명드리면 다음과 같습니다:
기본 구조
[MFC 메인 윈도우 또는 다이얼로그]
└─ [WPF 컨트롤을 호스팅하는 C++/CLI HwndHost 클래스]
└─ [WPF UserControl (XAML UI)]
- MFC에서 HWND를 통해 자식 윈도우처럼 공간을 만들고
- C++/CLI의 HwndHost를 사용해 WPF 컨트롤을 MFC 영역 안에 마운트
- 즉, WPF는 child dialog 또는 embedded panel처럼 작동합니다.
왜 이렇게 구성하는가?
기존 MFC 구조 유지 | 전체 재작성 없이도 WPF 기능 확장 가능 |
강력한 UI 표현력 확보 | 그래픽, 애니메이션, 바인딩 등 WPF 장점 활용 |
Interop 구조 단순화 | HWND 기반으로 명확한 창 구조 가능 |
확장성 | 기존 기능 위에만 선택적 WPF UI 추가 가능 (설정창, 뷰어 등) |
UI 구조 예시
만약 MFC 메인 창이 CDialogEx라면:
BOOL CMainDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// WPF UserControl 호스팅
m_wpfControl = gcnew WpfHostBridge::WpfHostControl();
m_wpfControl->OnButtonClicked += gcnew WpfHostBridge::ButtonClickedHandler(this, &CMainDlg::OnWpfButtonClicked);
m_wpfControl->CreateHandle();
HWND hWpf = (HWND)m_wpfControl->Handle.ToPointer();
::SetParent(hWpf, this->m_hWnd);
::MoveWindow(hWpf, 20, 20, 300, 200, TRUE);
return TRUE;
}
이때 hWpf는 MFC 다이얼로그의 자식 윈도우(WS_CHILD)처럼 동작하며, 마치 기존 CStatic, CEdit 등을 붙이듯 배치 가능합니다.
어떤 상황에 유리한가?
- 기존 MFC 프로젝트에 부분적으로만 WPF 도입하고 싶은 경우
- 신규 기능(그래프, 애니메이션 UI, 대화형 설정 등)을 신속히 구현하고자 할 때
- 단계적 현대화 전략을 취할 때 (기존 시스템 유지하면서 UI를 개선)
결론
"기존 MFC 애플리케이션 내부에, WPF를 child dialog 형태로 임베딩한다."
< 샘플 코드 >
/*
- Sample Visual Studio Solution: MfcWpfDialogSample
- Projects:
-
- WpfControlLibrary (.NET Framework 4.8 Class Library)
-
- WpfHostBridge (C++/CLI Class Library)
-
- MfcHostApp (MFC Dialog-based Application)
*/
- MfcHostApp (MFC Dialog-based Application)
// ----------------------------------------------------
// Project: WpfControlLibrary (Class Library)
// File: MyWpfControl.xaml
// ----------------------------------------------------
// ----------------------------------------------------
// File: MyWpfControl.xaml.cs
// ----------------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
namespace WpfControlLibrary
{
public partial class MyWpfControl : UserControl
{
// Event exposed to host
public event EventHandler Clicked;
public MyWpfControl()
{
InitializeComponent();
}
private void btnClick_Click(object sender, RoutedEventArgs e)
{
Clicked?.Invoke(this, EventArgs.Empty);
}
}
}
// ----------------------------------------------------
// Project: WpfHostBridge (C++/CLI Class Library)
// File: WpfHostControl.h
// ----------------------------------------------------
#pragma once
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
namespace WpfHostBridge {
// Delegate to notify host
public delegate void ButtonClickedHandler();
// HwndHost-derived control for embedding WPF
public ref class WpfHostControl : HwndHost
{
private:
HwndSource^ _source;
WpfControlLibrary::MyWpfControl^ _wpf;
public:
// Exposed event
event ButtonClickedHandler^ OnButtonClicked;
WpfHostControl() {}
protected:
// Build the WPF child
virtual IntPtr BuildWindowCore(HandleRef hwndParent) override
{
_wpf = gcnew WpfControlLibrary::MyWpfControl();
_wpf->Clicked += gcnew EventHandler(this, &WpfHostControl::WpfClicked);
HwndSourceParameters^ params = gcnew HwndSourceParameters("WPFHost");
params->PositionX = 0;
params->PositionY = 0;
params->Height = 200;
params->Width = 300;
params->ParentWindow = hwndParent.Handle;
params->WindowStyle = static_cast<int>(WindowStyles::Child) | static_cast<int>(WindowStyles::Visible);
_source = gcnew HwndSource(*params);
_source->RootVisual = _wpf;
return IntPtr(_source->Handle);
}
virtual void DestroyWindowCore(HandleRef hwnd) override
{
delete _source;
}
private:
void WpfClicked(Object^ s, EventArgs^ e)
{
// Forward to host
OnButtonClicked();
}
};
}
// ----------------------------------------------------
// Project: MfcHostApp (MFC Dialog-based Application)
// File: MainDlg.h
// ----------------------------------------------------
#pragma once
#include <afxext.h>
#include <vcclr.h>
// Import the C++/CLI assembly reference
#using <WpfHostBridge.dll>
// Message for WPF event (optional)
#define WM_WPF_BUTTON_CLICKED (WM_USER + 101)
class CMainDlg : public CDialogEx
{
public:
enum { IDD = IDD_MAINDLG };
CMainDlg(CWnd* pParent = nullptr);
protected:
virtual BOOL OnInitDialog();
afx_msg void OnDestroy();
// Handler for WPF button click
void OnWpfButtonClicked();
DECLARE_MESSAGE_MAP()
private:
// Managed handle to the WPF host control
gcrootWpfHostBridge::WpfHostControl^ m_wpfControl;
HWND m_hWpf;
};
// ----------------------------------------------------
// File: MainDlg.cpp
// ----------------------------------------------------
#include "pch.h"
#include "MainDlg.h"
#include "afxdialogex.h"
BEGIN_MESSAGE_MAP(CMainDlg, CDialogEx)
ON_WM_DESTROY()
END_MESSAGE_MAP()
CMainDlg::CMainDlg(CWnd* pParent)
: CDialogEx(IDD_MAINDLG, pParent)
{
}
BOOL CMainDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Enable Visual Styles for CLR
System::Windows::Forms::Application::EnableVisualStyles();
// Create WPF host via C++/CLI
m_wpfControl = gcnew WpfHostBridge::WpfHostControl();
m_wpfControl->OnButtonClicked += gcnew WpfHostBridge::ButtonClickedHandler(this, &CMainDlg::OnWpfButtonClicked);
// Build the HWND
IntPtr handle = m_wpfControl->Handle;
m_hWpf = static_cast<HWND>(handle.ToPointer());
// Parent to MFC dialog and position
::SetParent(m_hWpf, this->GetSafeHwnd());
::MoveWindow(m_hWpf, 20, 20, 300, 200, TRUE);
return TRUE;
}
void CMainDlg::OnWpfButtonClicked()
{
// Called when WPF button is clicked
AfxMessageBox(_T("WPF 버튼이 클릭되었습니다!"));
}
void CMainDlg::OnDestroy()
{
CDialogEx::OnDestroy();
// Clean up
m_wpfControl = nullptr;
}