다음을 통해 공유


연습: Win32에서 WPF 콘텐츠 호스팅

WPF(Windows Presentation Foundation)는 애플리케이션을 만들기 위한 풍부한 환경을 제공합니다. 그러나 Win32 코드에 상당한 투자를 한 경우 원본 코드를 다시 작성하는 대신 애플리케이션에 WPF 기능을 추가하는 것이 더 효과적일 수 있습니다. WPF는 Win32 창에서 WPF 콘텐츠를 호스트하기 위한 간단한 메커니즘을 제공합니다.

이 자습서에서는 Win32 창에서 WPF 콘텐츠를 호스트하는 샘플 애플리케이션인 Win32 창에서 WPF 콘텐츠 호스팅 샘플을 작성하는 방법을 설명합니다. 모든 Win32 창을 호스트하도록 이 샘플을 확장할 수 있습니다. 관리 코드와 비관리 코드를 혼합해야 하므로 애플리케이션은 C++/CLI로 작성됩니다.

요구 사항

이 자습서에서는 독자가 WPF 및 Win32 프로그래밍에 대한 기본 지식을 가지고 있다고 가정합니다. WPF 프로그래밍에 대한 기본적인 소개는 시작을 참조하세요. Win32 프로그래밍 소개는 해당 주제와 관련된 많은 서적, 특히 Charles Petzold가 저술한 Programming Windows를 참조해야 합니다.

이 자습서와 함께 제공되는 샘플은 C++/CLI로 구현되었으므로 이 자습서에서는 독자가 C++를 사용하여 Windows API를 프로그래밍하는 방법을 알고 관리 코드 프로그래밍에 대한 기본적인 지식이 있다고 가정합니다. C++/CLI에 대한 지식이 있으면 도움이 되지만 필수 사항은 아닙니다.

참고

이 자습서에는 관련 샘플의 많은 코드 예제가 포함되어 있습니다. 그러나 가독성을 위해 전체 샘플 코드를 포함하지는 않습니다. 전체 샘플 코드는 Win32 창에서 WPF 콘텐츠 호스팅 샘플을 참조하세요.

기본 절차

이 섹션에서는 Win32 창에서 WPF 콘텐츠를 호스트하는 데 사용하는 기본 절차를 간략하게 설명합니다. 나머지 섹션에서는 각 단계를 자세히 설명합니다.

Win32 창에서 WPF 콘텐츠를 호스팅하기 위한 핵심 요소는 HwndSource 클래스입니다. 이 클래스는 Win32 창에서 WPF 콘텐츠를 래핑하여 사용자 인터페이스(UI)에 자식 창으로 통합될 수 있도록 합니다. 다음 접근 방식은 Win32 및 WPF를 단일 애플리케이션에 결합합니다.

  1. WPF 콘텐츠를 관리 클래스로 구현합니다.

  2. C++/CLI를 사용하여 Windows 애플리케이션을 구현합니다. 기존 애플리케이션과 비관리 C++ 코드로 시작하는 경우 일반적으로 /clr 컴파일러 플래그를 포함하도록 프로젝트 설정을 변경하여 관리 코드를 호출할 수 있게 할 수 있습니다.

  3. 스레딩 모델을 STA(단일 스레드 아파트)로 설정합니다.

  4. 창 프로시저에서 WM_CREATE 알림을 처리하고 다음을 수행합니다.

    1. 부모 창을 해당 HwndSource 매개 변수로 사용하여 새 parent 개체를 만듭니다.

    2. WPF 콘텐츠 클래스의 인스턴스를 만듭니다.

    3. HwndSourceRootVisual 속성에 WPF 콘텐츠 개체에 대한 참조를 할당합니다.

    4. 콘텐츠에 대한 HWND를 가져옵니다. Handle 개체의 HwndSource 속성에는 창 핸들(HWND)이 포함됩니다. 애플리케이션의 관리되지 않는 부분에서 사용할 수 있는 HWND를 가져오려면 Handle.ToPointer()를 HWND로 캐스팅합니다.

  5. WPF 콘텐츠에 대한 참조를 보유할 정적 필드가 포함된 관리 클래스를 구현합니다. 이 클래스를 통해 Win32 코드에서 WPF 콘텐츠에 대한 참조를 가져올 수 있습니다.

  6. 정적 필드에 WPF 콘텐츠를 할당합니다.

  7. 처리기를 하나 이상의 WPF 이벤트에 연결하여 WPF 콘텐츠에서 알림을 받습니다.

  8. 정적 필드에 저장된 참조를 통해 속성 등을 설정하여 WPF 콘텐츠와 통신합니다.

참고

WPF 콘텐츠도 사용할 수 있습니다. 그러나 WPF 콘텐츠를 DLL(동적 연결 라이브러리)로 별도로 컴파일하고 Win32 애플리케이션에서 이 DLL을 참조해야 합니다. 프로시저의 나머지 부분은 위에서 설명한 것과 비슷합니다.

호스트 애플리케이션 구현

이 섹션에서는 기본 Win32 애플리케이션에서 WPF 콘텐츠를 호스트하는 방법을 설명합니다. 콘텐츠 자체는 C++/CLI에서 관리 클래스로 구현됩니다. 대부분은 간단한 WPF 프로그래밍입니다. 콘텐츠 구현의 주요 측면은 WPF 콘텐츠 구현에서 설명합니다.

기본 애플리케이션

호스트 애플리케이션의 시작점은 Visual Studio 2005 템플릿을 만드는 것이었습니다.

  1. Visual Studio 2005를 열고 파일 메뉴에서 새 프로젝트를 선택합니다.

  2. Visual C++ 프로젝트 형식 목록에서 Win32를 선택합니다. 기본 언어가 C++가 아닌 경우 이러한 프로젝트 형식은 다른 언어 아래에 있습니다.

  3. Win32 프로젝트 템플릿을 선택하고 프로젝트에 이름을 할당한 다음 확인을 클릭하여 Win32 애플리케이션 마법사를 시작합니다.

  4. 마법사의 기본 설정을 적용하고 마침을 클릭하여 프로젝트를 시작합니다.

템플릿은 다음을 포함하여 기본 Win32 애플리케이션을 만듭니다.

  • 애플리케이션에 대한 진입점

  • 연결된 창 프로시저(WndProc)가 있는 창

  • 파일도움말 머리글이 있는 메뉴. 파일 메뉴에는 애플리케이션을 닫는 끝내기 항목이 있습니다. 도움말 메뉴에는 간단한 대화 상자를 시작하는 정보 항목이 있습니다.

WPF 콘텐츠를 호스트할 코드 작성을 시작하기 전에 기본 템플릿에서 다음 두 가지를 수정해야 합니다.

첫 번째는 프로젝트를 관리 코드로 컴파일하는 것입니다. 기본적으로 프로젝트는 비관리 코드로 컴파일됩니다. 그러나 WPF가 관리 코드에서 구현되므로 프로젝트도 그에 따라 컴파일해야 합니다.

  1. 솔루션 탐색기에서 프로젝트 이름을 마우스 오른쪽 단추로 클릭하고 상황에 맞는 메뉴에서 속성을 선택하여 속성 페이지 대화 상자를 시작합니다.

  2. 왼쪽 창의 트리 뷰에서 구성 속성을 선택합니다.

  3. 오른쪽 창의 프로젝트 기본값 목록에서 공용 언어 런타임 지원을 선택합니다.

  4. 드롭다운 목록 상자에서 공용 언어 런타임 지원(/clr)을 선택합니다.

참고

이 컴파일러 플래그를 통해 애플리케이션에서 관리 코드를 사용할 수 있지만 비관리 코드는 계속 이전처럼 컴파일됩니다.

WPF는 STA(단일 스레드 아파트) 스레딩 모델을 사용합니다. WPF 콘텐츠 코드에서 제대로 작동하려면 진입점에 특성을 적용하여 애플리케이션의 스레딩 모델을 STA로 설정해야 합니다.

[System::STAThreadAttribute] //Needs to be an STA thread to play nicely with WPF
int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{

WPF 콘텐츠 호스트

WPF 콘텐츠는 간단한 주소 입력 애플리케이션입니다. 사용자 이름, 주소 등을 가져오는 여러 개의 TextBox 컨트롤로 구성됩니다. 두 개의 Button 컨트롤인 확인취소도 있습니다. 사용자가 확인을 클릭하면 단추의 Click 이벤트 처리기가 TextBox 컨트롤에서 데이터를 수집하고 해당 속성에 할당한 다음 사용자 지정 이벤트 OnButtonClicked를 발생시킵니다. 사용자가 취소를 클릭하면 처리기에서 OnButtonClicked를 발생시키기만 합니다. OnButtonClicked에 대한 이벤트 인수 개체에는 클릭된 단추를 나타내는 부울 필드가 포함됩니다.

WPF 콘텐츠를 호스트하는 코드는 호스트 창에서 WM_CREATE 알림의 처리기에서 구현됩니다.

case WM_CREATE :
  GetClientRect(hWnd, &rect);
  wpfHwnd = GetHwnd(hWnd, rect.right-375, 0, 375, 250);
  CreateDataDisplay(hWnd, 275, rect.right-375, 375);
  CreateRadioButtons(hWnd);
break;

GetHwnd 메서드는 크기 및 위치 정보와 부모 창 핸들을 가져오고 호스트된 WPF 콘텐츠의 창 핸들을 반환합니다.

참고

#using 네임스페이스에는 System::Windows::Interop 지시문을 사용할 수 없습니다. 사용할 경우 해당 네임스페이스의 MSG 구조와 winuser.h에 선언된 MSG 구조 간에 이름 충돌이 생깁니다. 대신 정규화된 이름을 사용하여 해당 네임스페이스의 내용에 액세스해야 합니다.

HWND GetHwnd(HWND parent, int x, int y, int width, int height)
{
    System::Windows::Interop::HwndSourceParameters^ sourceParams = gcnew System::Windows::Interop::HwndSourceParameters(
    "hi" // NAME
    );
    sourceParams->PositionX = x;
    sourceParams->PositionY = y;
    sourceParams->Height = height;
    sourceParams->Width = width;
    sourceParams->ParentWindow = IntPtr(parent);
    sourceParams->WindowStyle = WS_VISIBLE | WS_CHILD; // style
    System::Windows::Interop::HwndSource^ source = gcnew System::Windows::Interop::HwndSource(*sourceParams);
    WPFPage ^myPage = gcnew WPFPage(width, height);
    //Assign a reference to the WPF page and a set of UI properties to a set of static properties in a class
    //that is designed for that purpose.
    WPFPageHost::hostedPage = myPage;
    WPFPageHost::initBackBrush = myPage->Background;
    WPFPageHost::initFontFamily = myPage->DefaultFontFamily;
    WPFPageHost::initFontSize = myPage->DefaultFontSize;
    WPFPageHost::initFontStyle = myPage->DefaultFontStyle;
    WPFPageHost::initFontWeight = myPage->DefaultFontWeight;
    WPFPageHost::initForeBrush = myPage->DefaultForeBrush;
    myPage->OnButtonClicked += gcnew WPFPage::ButtonClickHandler(WPFButtonClicked);
    source->RootVisual = myPage;
    return (HWND) source->Handle.ToPointer();
}

애플리케이션 창에서 직접 WPF 콘텐츠를 호스트할 수는 없습니다. WPF 콘텐츠를 줄 바꿈할 HwndSource 개체를 먼저 만듭니다. 이 개체는 기본적으로 WPF 콘텐츠를 호스트하도록 설계된 창입니다. 애플리케이션의 일부인 Win32 창의 자식으로 만들어 부모 창에서 HwndSource 개체를 호스트합니다. HwndSource 생성자 매개 변수에는 Win32 자식 창을 만들 때 CreateWindow에 전달하는 것과 동일한 정보가 포함됩니다.

다음에는 WPF 콘텐츠 개체의 인스턴스를 만듭니다. 이 경우 WPF 콘텐츠는 C++/CLI를 사용하여 별도 클래스 WPFPage로 구현됩니다. XAML을 사용하여 WPF 콘텐츠를 구현할 수도 있습니다. 그러나 이렇게 하려면 별도의 프로젝트를 설정하고 WPF 콘텐츠를 DLL로 빌드해야 합니다. 이 DLL에 대한 참조를 프로젝트에 추가하고 해당 참조를 사용하여 WPF 콘텐츠의 인스턴스를 만들 수 있습니다.

HwndSourceRootVisual 속성에 WPF 콘텐츠에 대한 참조를 할당하여 자식 창에 WPF 콘텐츠를 표시합니다.

코드의 다음 줄은 이벤트 처리기 WPFButtonClicked를 WPF 콘텐츠 OnButtonClicked 이벤트에 연결합니다. 이 처리기는 사용자가 확인 또는 취소 단추를 클릭할 때 호출됩니다. 이 이벤트 처리기에 대한 자세한 내용은 communicating_with_the_WPF content를 참조하세요.

표시된 코드의 마지막 줄에서는 HwndSource 개체와 연결된 창 핸들(HWND)을 반환합니다. 샘플에서는 수행하지 않지만 Win32 코드에서 이 핸들을 사용하여 호스트된 창에 메시지를 보낼 수 있습니다. HwndSource 개체는 메시지를 받을 때마다 이벤트를 발생시킵니다. 메시지를 처리하려면 AddHook 메서드를 호출하여 메시지 처리기를 연결한 다음 해당 처리기에서 메시지를 처리합니다.

WPF 콘텐츠에 대한 참조 보유

대부분의 애플리케이션에서는 나중에 WPF 콘텐츠와 통신하려고 합니다. 예를 들어 WPF 콘텐츠 속성을 수정하거나 HwndSource 개체가 다른 WPF 콘텐츠를 호스트하게 할 수 있습니다. 이렇게 하려면 HwndSource 개체 또는 WPF 콘텐츠에 대한 참조가 필요합니다. HwndSource 개체 및 연결된 WPF 콘텐츠는 창 핸들을 삭제할 때까지 메모리에 남아 있습니다. 그러나 HwndSource 개체에 할당하는 변수는 창 프로시저에서 반환되는 즉시 범위를 벗어납니다. Win32 애플리케이션에서 이 문제를 처리하는 일반적인 방법은 정적 변수나 전역 변수를 사용하는 것입니다. 이러한 유형의 변수에는 관리되는 개체를 할당할 수 없습니다. HwndSource 개체와 연결된 창 핸들을 전역 변수나 정적 변수에 할당할 수 있지만 개체 자체에 대한 액세스는 제공되지 않습니다.

이 문제에 대한 가장 간단한 해결 방법은 액세스해야 하는 관리되는 개체에 대한 참조를 보유할 정적 필드 집합이 포함된 관리되는 클래스를 구현하는 것입니다. 샘플에서는 WPFPageHost 클래스를 사용하여 WPF 콘텐츠에 대한 참조 및 나중에 사용자가 변경할 수 있는 여러 해당 속성의 초기 값을 보유합니다. 이는 헤더에서 정의됩니다.

public ref class WPFPageHost
{
public:
  WPFPageHost();
  static WPFPage^ hostedPage;
  //initial property settings
  static System::Windows::Media::Brush^ initBackBrush;
  static System::Windows::Media::Brush^ initForeBrush;
  static System::Windows::Media::FontFamily^ initFontFamily;
  static System::Windows::FontStyle initFontStyle;
  static System::Windows::FontWeight initFontWeight;
  static double initFontSize;
};

GetHwnd 함수의 뒷부분에서는 나중에 myPage가 범위 내에 있는 동안 사용하기 위해 해당 필드에 값을 할당합니다.

WPF 콘텐츠와 통신

WPF 콘텐츠와의 통신에는 두 가지 유형이 있습니다. 사용자가 확인 또는 취소 단추를 클릭하면 애플리케이션이 WPF 콘텐츠에서 정보를 받습니다. 애플리케이션에는 사용자가 배경색 또는 기본 글꼴 크기와 같은 다양한 WPF 콘텐츠 속성을 변경할 수 있는 UI도 있습니다.

위에서 설명했듯이 사용자가 단추 중 하나를 클릭하면 WPF 콘텐츠가 OnButtonClicked 이벤트를 발생시킵니다. 애플리케이션은 이 이벤트에 처리기를 연결하여 이러한 알림을 수신합니다. 확인 단추를 클릭한 경우 처리기는 WPF 콘텐츠에서 사용자 정보를 가져오고 정적 컨트롤 집합에 표시합니다.

void WPFButtonClicked(Object ^sender, MyPageEventArgs ^args)
{
    if(args->IsOK) //display data if OK button was clicked
    {
        WPFPage ^myPage = WPFPageHost::hostedPage;
        LPCWSTR userName = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("Name: " + myPage->EnteredName).ToPointer();
        SetWindowText(nameLabel, userName);
        LPCWSTR userAddress = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("Address: " + myPage->EnteredAddress).ToPointer();
        SetWindowText(addressLabel, userAddress);
        LPCWSTR userCity = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("City: " + myPage->EnteredCity).ToPointer();
        SetWindowText(cityLabel, userCity);
        LPCWSTR userState = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("State: " + myPage->EnteredState).ToPointer();
        SetWindowText(stateLabel, userState);
        LPCWSTR userZip = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("Zip: " + myPage->EnteredZip).ToPointer();
        SetWindowText(zipLabel, userZip);
    }
    else
    {
        SetWindowText(nameLabel, L"Name: ");
        SetWindowText(addressLabel, L"Address: ");
        SetWindowText(cityLabel, L"City: ");
        SetWindowText(stateLabel, L"State: ");
        SetWindowText(zipLabel, L"Zip: ");
    }
}

처리기는 WPF 콘텐츠로부터 사용자 지정 이벤트 인수 개체 MyPageEventArgs를 받습니다. 확인 단추를 클릭한 경우 개체의 IsOK 속성이 true로 설정되고, 취소 단추를 클릭한 경우 false로 설정됩니다.

확인 단추를 클릭한 경우 처리기는 컨테이너 클래스에서 WPF 콘텐츠에 대한 참조를 가져옵니다. 그런 다음 연결된 WPF 속성에 보유된 사용자 정보를 수집하고 정적 컨트롤을 사용하여 부모 창에 정보를 표시합니다. WPF 콘텐츠 데이터는 관리 문자열 형식이므로 Win32 컨트롤에서 사용하기 위해 마샬링해야 합니다. 취소 단추를 클릭한 경우 처리기는 정적 컨트롤에서 데이터를 지웁니다.

애플리케이션 UI는 사용자가 WPF 콘텐츠의 배경색 및 여러 글꼴 관련 속성을 수정할 수 있는 라디오 단추 집합을 제공합니다. 다음 예제는 애플리케이션의 창 프로시저(WndProc) 및 각 메시지에서 배경색을 비롯한 다양한 속성을 설정하는 해당 메시지 처리에서 발췌한 내용입니다. 다른 예제도 비슷하며 표시되지는 않습니다. 자세한 내용과 컨텍스트는 전체 샘플을 참조하세요.

case WM_COMMAND:
  wmId    = LOWORD(wParam);
  wmEvent = HIWORD(wParam);

  switch (wmId)
  {
  //Menu selections
    case IDM_ABOUT:
      DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
    break;
    case IDM_EXIT:
      DestroyWindow(hWnd);
    break;
    //RadioButtons
    case IDC_ORIGINALBACKGROUND :
      WPFPageHost::hostedPage->Background = WPFPageHost::initBackBrush;
    break;
    case IDC_LIGHTGREENBACKGROUND :
      WPFPageHost::hostedPage->Background = gcnew SolidColorBrush(Colors::LightGreen);
    break;
    case IDC_LIGHTSALMONBACKGROUND :
      WPFPageHost::hostedPage->Background = gcnew SolidColorBrush(Colors::LightSalmon);
    break;

배경색을 설정하려면 WPFPageHost에서 WPF 콘텐츠(hostedPage)에 대한 참조를 가져오고 배경색 속성을 적절한 색으로 설정합니다. 샘플에서는 세 가지 색 옵션(원래 색, 연한 녹색 또는 연한 연어살색)을 사용합니다. 원래 배경색은 WPFPageHost 클래스에 정적 필드로 저장되어 있습니다. 다른 두 색을 설정하기 위해 SolidColorBrush 개체를 새로 만들고 생성자에 Colors 개체의 정적 색 값을 전달합니다.

WPF 페이지 구현

실제 구현에 대한 지식 없이 WPF 콘텐츠를 호스트하고 사용할 수 있습니다. WPF 콘텐츠가 별도의 DLL로 패키지된 경우 임의의 CLR(공용 언어 런타임) 언어로 빌드할 수 있습니다. 다음은 샘플에서 사용되는 C++/CLI 구현에 대한 간략한 연습입니다. 이 섹션에는 다음 하위 섹션이 포함되어 있습니다.

레이아웃

WPF 콘텐츠의 UI 요소는 연결된 Label 컨트롤이 있는 TextBox 컨트롤 5개(Name, Address, City, State 및 Zip)로 구성되어 있습니다. 두 개의 Button 컨트롤인 확인취소도 있습니다.

WPF 콘텐츠는 WPFPage 클래스에서 구현됩니다. 레이아웃은 Grid 레이아웃 요소에서 처리됩니다. 클래스는 Grid에서 상속되며 효과적으로 WPF 콘텐츠 루트 요소로 설정됩니다.

WPF 콘텐츠 생성자는 필요한 너비 및 높이를 가져오고 Grid 크기를 적절하게 조정합니다. 그런 다음 ColumnDefinitionRowDefinition 개체 집합을 만들어 각각 Grid 개체 기본 ColumnDefinitionsRowDefinitions 컬렉션에 추가하여 기본 레이아웃을 정의합니다. 그러면 크기가 셀의 내용에 따라 결정되는 5개의 행과 7개의 열로 구성된 표가 정의됩니다.

WPFPage::WPFPage(int allottedWidth, int allotedHeight)
{
  array<ColumnDefinition ^> ^ columnDef = gcnew array<ColumnDefinition ^> (4);
  array<RowDefinition ^> ^ rowDef = gcnew array<RowDefinition ^> (6);

  this->Height = allotedHeight;
  this->Width = allottedWidth;
  this->Background = gcnew SolidColorBrush(Colors::LightGray);
  
  //Set up the Grid's row and column definitions
  for(int i=0; i<4; i++)
  {
    columnDef[i] = gcnew ColumnDefinition();
    columnDef[i]->Width = GridLength(1, GridUnitType::Auto);
    this->ColumnDefinitions->Add(columnDef[i]);
  }
  for(int i=0; i<6; i++)
  {
    rowDef[i] = gcnew RowDefinition();
    rowDef[i]->Height = GridLength(1, GridUnitType::Auto);
    this->RowDefinitions->Add(rowDef[i]);
  }

그런 다음 생성자가 Grid에 UI 요소를 추가합니다. 첫 번째 요소는 표의 첫 번째 행 가운데에 표시되는 Label 컨트롤인 제목 텍스트입니다.

//Add the title
titleText = gcnew Label();
titleText->Content = "Simple WPF Control";
titleText->HorizontalAlignment = System::Windows::HorizontalAlignment::Center;
titleText->Margin = Thickness(10, 5, 10, 0);
titleText->FontWeight = FontWeights::Bold;
titleText->FontSize = 14;
Grid::SetColumn(titleText, 0);
Grid::SetRow(titleText, 0);
Grid::SetColumnSpan(titleText, 4);
this->Children->Add(titleText);

다음 행에는 Name Label 컨트롤 및 연결된 TextBox 컨트롤이 포함됩니다. 동일한 코드가 각 레이블/텍스트 상자 쌍에 사용되므로 private 메서드 쌍에 배치되고 레이블/텍스트 상자 쌍 5개에 모두 사용됩니다. 메서드는 적절한 컨트롤을 만들고 Grid 클래스의 정적 SetColumnSetRow 메서드를 호출하여 컨트롤을 적절한 셀에 배치합니다. 컨트롤이 만들어진 후 샘플에서는 AddChildren 속성에 대해 Grid 메서드를 호출하여 표에 컨트롤을 추가합니다. 나머지 레이블/텍스트 상자 쌍을 추가하는 코드도 비슷합니다. 자세한 내용은 샘플 코드를 참조하세요.

//Add the Name Label and TextBox
nameLabel = CreateLabel(0, 1, "Name");
this->Children->Add(nameLabel);
nameTextBox = CreateTextBox(1, 1, 3);
this->Children->Add(nameTextBox);

두 메서드의 구현은 다음과 같습니다.

Label ^WPFPage::CreateLabel(int column, int row, String ^ text)
{
  Label ^ newLabel = gcnew Label();
  newLabel->Content = text;
  newLabel->Margin = Thickness(10, 5, 10, 0);
  newLabel->FontWeight = FontWeights::Normal;
  newLabel->FontSize = 12;
  Grid::SetColumn(newLabel, column);
  Grid::SetRow(newLabel, row);
  return newLabel;
}
TextBox ^WPFPage::CreateTextBox(int column, int row, int span)
{
  TextBox ^newTextBox = gcnew TextBox();
  newTextBox->Margin = Thickness(10, 5, 10, 0);
  Grid::SetColumn(newTextBox, column);
  Grid::SetRow(newTextBox, row);
  Grid::SetColumnSpan(newTextBox, span);
  return newTextBox;
}

끝으로 샘플은 확인취소 단추를 추가하고 해당 Click 이벤트에 이벤트 처리기를 연결합니다.

//Add the Buttons and atttach event handlers
okButton = CreateButton(0, 5, "OK");
cancelButton = CreateButton(1, 5, "Cancel");
this->Children->Add(okButton);
this->Children->Add(cancelButton);
okButton->Click += gcnew RoutedEventHandler(this, &WPFPage::ButtonClicked);
cancelButton->Click += gcnew RoutedEventHandler(this, &WPFPage::ButtonClicked);

호스트 창에 데이터 반환

단추 중 하나를 클릭하면 해당 Click 이벤트가 발생합니다. 호스트 창은 단순히 이러한 이벤트에 처리기를 연결하고 TextBox 컨트롤에서 직접 데이터를 가져올 수 있습니다. 샘플에서는 상대적으로 덜 직접적인 접근 방식을 사용합니다. WPF 콘텐츠 내에서 Click을 처리한 다음 사용자 지정 이벤트 OnButtonClicked를 발생시켜 WPF 콘텐츠에 알립니다. 이렇게 하면 호스트에 알리기 전에 WPF 콘텐츠가 일부 매개 변수 유효성 검사를 수행할 수 있습니다. 처리기는 TextBox 컨트롤에서 텍스트를 가져와 호스트가 정보를 검색할 수 있는 public 속성에 할당합니다.

WPFPage.h의 이벤트 선언:

public:
  delegate void ButtonClickHandler(Object ^, MyPageEventArgs ^);
  WPFPage();
  WPFPage(int height, int width);
  event ButtonClickHandler ^OnButtonClicked;

WPFPage.cpp의 Click 이벤트 처리기:

void WPFPage::ButtonClicked(Object ^sender, RoutedEventArgs ^args)
{

  //TODO: validate input data
  bool okClicked = true;
  if(sender == cancelButton)
    okClicked = false;
  EnteredName = nameTextBox->Text;
  EnteredAddress = addressTextBox->Text;
  EnteredCity = cityTextBox->Text;
  EnteredState = stateTextBox->Text;
  EnteredZip = zipTextBox->Text;
  OnButtonClicked(this, gcnew MyPageEventArgs(okClicked));
}

WPF 속성 설정

Win32 호스트를 통해 사용자는 여러 개의 WPF 콘텐츠 속성을 변경할 수 있습니다. Win32 쪽에서는 단순히 속성을 변경하면 됩니다. WPF 콘텐츠 클래스의 구현은 모든 컨트롤의 글꼴을 제어하는 단일 전역 속성이 없기 때문에 다소 복잡합니다. 대신, 각 컨트롤에 적절한 속성이 속성의 set 접근자에서 변경됩니다. 다음 예제는 DefaultFontFamily 속성의 코드를 보여 줍니다. 속성을 설정하면 private 메서드가 호출되고, 다시 다양한 컨트롤에 대한 FontFamily 속성이 설정됩니다.

WPFPage.h에서:

property FontFamily^ DefaultFontFamily
{
  FontFamily^ get() {return _defaultFontFamily;}
  void set(FontFamily^ value) {SetFontFamily(value);}
};

WPFPage.cpp에서:

void WPFPage::SetFontFamily(FontFamily^ newFontFamily)
{
  _defaultFontFamily = newFontFamily;
  titleText->FontFamily = newFontFamily;
  nameLabel->FontFamily = newFontFamily;
  addressLabel->FontFamily = newFontFamily;
  cityLabel->FontFamily = newFontFamily;
  stateLabel->FontFamily = newFontFamily;
  zipLabel->FontFamily = newFontFamily;
}

참고 항목