연습: Win32 응용 프로그램에서 Windows Presentation Foundation 콘텐츠 호스팅
업데이트: 2007년 11월
WPF(Windows Presentation Foundation)에서는 응용 프로그램을 만들기 위한 다양한 환경을 제공합니다. 그러나 Win32 코드를 작성하는 데 많은 시간과 노력을 기울인 경우 원래 코드를 다시 작성하는 대신 응용 프로그램에 WPF 기능을 추가하는 것이 더 효율적일 수 있습니다. WPF는 Win32 창에서 WPF 콘텐츠를 호스팅하는 간단한 메커니즘을 제공합니다.
이 자습서에서는 Win32 창에서 WPF 콘텐츠를 호스팅하는 샘플 응용 프로그램(Win32 Window에서 Windows Presentation Foundation 콘텐츠 호스팅 샘플)을 작성하는 방법을 보여 줍니다. 필요한 Win32 창을 호스팅하도록 이 샘플을 확장할 수 있습니다. 이 샘플에는 관리 코드와 비관리 코드가 혼합되어 있기 때문에 응용 프로그램이 C++/CLI로 작성되어 있습니다.
이 항목에는 다음 단원이 포함되어 있습니다.
- 요구 사항
- 기본 절차
- 호스트 응용 프로그램 구현
- WPF 페이지 구현
- 관련 항목
요구 사항
이 자습서에서는 사용자가 WPF 및 Win32 프로그래밍의 기본 사항에 대해 잘 알고 있다고 가정합니다. WPF 프로그래밍에 대한 소개는 시작(WPF)을 참조하십시오. Win32 프로그래밍에 대한 소개는 관련된 수많은 서적이 있지만, 특히 Charles Petzold의 Programming Windows가 도움이 될 것입니다.
이 자습서와 함께 제공되는 샘플은 C++/CLI에서 구현되기 때문에 이 자습서에서는 사용자가 C++를 사용하여 Win32 API를 프로그래밍하는 것에 익숙하고 관리 코드 프로그래밍을 이해하고 있다고 가정합니다. C++/CLI에 대해 알고 있으면 도움이 되겠지만 알지 못해도 상관없습니다.
참고
이 자습서에는 관련 샘플에 있는 많은 코드 예제가 포함되어 있지만 편의상 전체 샘플 코드는 제공하지 않습니다. 전체 샘플 코드를 보려면 Win32 Window에서 Windows Presentation Foundation 콘텐츠 호스팅 샘플을 참조하십시오.
기본 절차
이 단원에서는 Win32 창에서 WPF 콘텐츠를 호스팅하는 데 사용되는 기본 절차에 대해 간략히 소개합니다. 나머지 단원에서는 각 단계에 대해 자세히 설명합니다.
Win32 창에서 WPF 콘텐츠를 호스팅하는 데 핵심적인 역할을 하는 요소는 HwndSource 클래스입니다. 이 클래스를 사용하면 Win32 창에서 WPF 콘텐츠를 래핑하여 UI(사용자 인터페이스)에 자식 창으로 통합할 수 있습니다. 다음에 나오는 방법에서는 Win32와 WPF를 단일 응용 프로그램에 통합합니다.
WPF 콘텐츠를 관리되는 클래스로 구현합니다.
C++/CLI를 사용하여 Win32 응용 프로그램을 구현합니다. 기존 응용 프로그램과 관리되지 않는 C++ 코드로 시작하는 경우 일반적으로 /clr 컴파일러 플래그를 포함하도록 프로젝트 설정을 변경하여 응용 프로그램에서 관리 코드를 호출하도록 할 수 있습니다.
스레딩 모델을 STA(단일 스레드 아파트)로 설정합니다.
창 프로시저에서 WM_CREATE 알림을 처리하고 다음을 수행합니다.
부모 창을 parent 매개 변수로 사용하여 새 HwndSource 개체를 만듭니다.
WPF 콘텐츠 클래스의 인스턴스를 만듭니다.
WPF 콘텐츠 개체에 대한 참조를 HwndSource의 RootVisual 속성에 할당합니다.
콘텐츠의 HWND를 가져옵니다. HwndSource 개체의 Handle 속성에는 창 핸들(HWND)이 포함됩니다. 응용 프로그램의 관리되지 않는 부분에서 사용할 수 있는 HWND를 가져오려면 Handle.ToPointer()를 HWND로 캐스팅합니다.
WPF 콘텐츠에 대한 참조를 보유하는 정적 필드가 포함된 관리되는 클래스를 구현합니다. 이 클래스를 사용하면 Win32 코드에서 WPF 콘텐츠에 대한 참조를 가져올 수 있습니다.
WPF 콘텐츠를 정적 필드에 할당합니다.
하나 이상의 WPF 이벤트에 처리기를 연결하여 WPF 콘텐츠에서 알림을 받습니다.
정적 필드에 저장되어 있는 참조를 사용하여 WPF 콘텐츠와 통신하여 속성 설정 등의 작업을 수행합니다.
참고
XAML(Extensible Application Markup Language)을 사용하여 WPF 콘텐츠를 구현할 수도 있습니다. 하지만 이 경우 콘텐츠를 따로 DLL(동적 연결 라이브러리)로 컴파일하고 Win32 응용 프로그램에서 해당 DLL을 참조해야 합니다. 절차의 나머지 단계는 앞에 설명된 단계와 비슷합니다.
호스트 응용 프로그램 구현
이 단원에서는 기본 Win32 응용 프로그램에서 WPF 콘텐츠를 호스팅하는 방법을 설명합니다. 콘텐츠 자체는 C++/CLI에서 관리되는 클래스로 구현됩니다. 대부분의 경우 이것은 간단한 WPF 프로그래밍입니다. 콘텐츠 구현의 주요 사항에 대해서는 WPF 콘텐츠 구현을 참조하십시오.
기본 응용 프로그램
WPF 콘텐츠 호스팅
WPF 콘텐츠에 대한 참조 유지
WPF 콘텐츠와 통신
기본 응용 프로그램
호스트 응용 프로그램의 시작 지점은 Microsoft Visual Studio 2005 템플릿을 만드는 것입니다.
Visual Studio 2005를 열고 파일 메뉴에서 새 프로젝트를 선택합니다.
Visual C++ 프로젝트 형식 목록에서 Win32를 선택합니다. 기본 언어가 C++가 아닌 경우 다른 언어에서 이 프로젝트 형식을 찾을 수 있습니다.
Win32 프로젝트 템플릿을 선택하고 프로젝트에 이름을 지정한 다음 OK를 클릭하여 Win32 응용 프로그램 마법사를 시작합니다.
마법사의 기본 설정을 그대로 두고 마침을 클릭하여 프로젝트를 시작합니다.
템플릿은 다음을 비롯한 기본 Win32 응용 프로그램을 만듭니다.
응용 프로그램의 진입점
창 및 창과 연결된 창 프로시저(WndProc)
파일 및 도움말 머리글이 있는 메뉴. 파일 메뉴에는 응용 프로그램을 닫는 끝내기 항목이 있습니다. 도움말 메뉴에는 간단한 대화 상자를 시작하는 정보 항목이 있습니다.
WPF 콘텐츠를 호스팅하는 코드를 작성하기 전에 기본 템플릿에서 두 가지를 수정해야 합니다.
첫 번째는 프로젝트를 관리 코드로 컴파일하는 것입니다. 기본적으로 프로젝트는 비관리 코드로 컴파일됩니다. 그러나 WPF는 관리 코드로 구현되므로 프로젝트를 관리 코드로 컴파일해야 합니다.
솔루션 탐색기에서 프로젝트 이름을 마우스 오른쪽 단추로 클릭하고 상황에 맞는 메뉴에서 속성을 선택하여 속성 페이지 대화 상자를 엽니다.
왼쪽 창의 트리 뷰에서 구성 속성을 선택합니다.
오른쪽 창의 프로젝트 기본값 목록에서 공용 언어 런타임 지원을 선택합니다.
드롭다운 목록 상자에서 공용 언어 런타임 지원(/clr)을 선택합니다.
참고
이 컴파일러 플래그를 사용하면 응용 프로그램에서 관리 코드를 사용할 수 있지만 비관리 코드는 이전과 마찬가지로 컴파일됩니다.
WPF에서는 STA(단일 스레드 아파트) 스레딩 모델을 사용합니다. WPF 콘텐츠 코드와 올바르게 작동하도록 하려면 진입점에 특성을 적용하여 응용 프로그램의 스레딩 모델을 STA로 설정해야 합니다.
WPF 콘텐츠 호스팅
WPF 콘텐츠는 간단한 주소 입력 응용 프로그램입니다. 이 응용 프로그램은 사용자 이름, 주소 등을 사용하는 몇 가지 TextBox 컨트롤로 구성되어 있습니다. 또한 OK 및 Cancel이라는 Button 컨트롤 두 개가 있습니다. 사용자가 OK를 클릭하면 단추의 Click 이벤트 처리기가 TextBox 컨트롤에서 데이터를 수집하여 해당 속성에 할당한 다음 사용자 지정 이벤트인 OnButtonClicked를 발생시킵니다. 사용자가 Cancel을 클릭하면 처리기는 단순히 OnButtonClicked를 발생시킵니다. OnButtonClicked에 대한 이벤트 인수 개체에는 어떤 단추를 클릭했는지 나타내는 부울 필드가 있습니다.
WPF 콘텐츠를 호스팅하는 코드는 호스트 창의 WM_CREATE 알림에 대한 처리기에서 구현됩니다.
GetHwnd 메서드는 크기 및 위치 정보와 부모 창 핸들을 가져오고 호스팅되는 WPF 콘텐츠의 창 핸들을 반환합니다.
참고
System::Windows::Interop 네임스페이스에 대한 #using 지시문을 사용할 수 없습니다. 그렇게 하면 해당 네임스페이스의 MSG 구조체와 winuser.h에 선언된 MSG 구조체 간에 이름 충돌이 발생합니다. 대신 정규화된 이름을 사용하여 해당 네임스페이스의 콘텐츠에 액세스합니다.
응용 프로그램 창에서 직접 WPF 콘텐츠를 호스팅할 수 없습니다. 대신 HwndSource 개체를 먼저 만들어 WPF 콘텐츠를 래핑합니다. 이 개체는 기본적으로 WPF 콘텐츠를 호스팅하도록 디자인된 창입니다. HwndSource 개체를 응용 프로그램의 일부인 Win32 창의 자식으로 생성하여 부모 창에서 호스팅합니다. HwndSource 생성자 매개 변수는 Win32 자식 창을 만들 때 CreateWindow로 전달할 수 있는 정보의 대부분을 포함합니다.
다음으로 WPF 콘텐츠 개체의 인스턴스를 생성합니다. 이 경우 WPF 콘텐츠는 C++/CLI를 사용하여 별도의 클래스인 WPFPage로 구현됩니다. XAML로 WPF 콘텐츠를 구현할 수도 있습니다. 하지만 그렇게 하려면 별도의 프로젝트를 설정하고 WPF 콘텐츠를 DLL로 빌드해야 합니다. 해당 DLL에 대한 참조를 프로젝트에 추가하고 해당 참조를 사용하여 WPF 콘텐츠의 인스턴스를 만들 수 있습니다.
WPF 콘텐츠에 대한 참조를 HwndSource의 RootVisual 속성에 할당하여 자식 창에서 WPF 콘텐츠를 표시합니다.
코드의 다음 줄에서 이벤트 처리기인 WPFButtonClicked를 WPF 콘텐츠인 OnButtonClicked 이벤트에 연결합니다. 이 처리기는 사용자가 OK 또는 Cancel 단추를 클릭하면 호출됩니다. 이 이벤트 처리기에 대한 자세한 내용은 WPF 콘텐츠와 통신을 참조하십시오.
여기에 표시된 코드의 마지막 줄에서는 HwndSource 개체와 연결된 창 핸들(HWND)을 반환합니다. 이 샘플에서는 그러한 작업을 하지 않지만 Win32 코드의 이 핸들을 사용하여 호스팅되는 창에 메시지를 보낼 수도 있습니다. HwndSource 개체는 메시지를 받을 때마다 이벤트를 발생시킵니다. 메시지를 처리하려면 AddHook 메서드를 호출하여 메시지 처리기를 연결한 다음 해당 처리기에서 메시지를 처리합니다.
WPF 콘텐츠에 대한 참조 유지
많은 응용 프로그램에서 나중에 WPF 콘텐츠와 통신하도록 할 수 있습니다. 예를 들어 WPF 콘텐츠 속성을 수정하거나 HwndSource 개체가 다른 WPF 콘텐츠를 호스팅하게 하려는 경우가 있습니다. 이렇게 하려면 HwndSource 개체나 WPF 콘텐츠에 대한 참조가 필요합니다. HwndSource 개체 및 그와 연결된 WPF 콘텐츠는 사용자가 창 핸들을 소멸시키기 전까지 메모리에 유지됩니다. 하지만 HwndSource 개체에 할당하는 변수는 창 프로시저에서 반환되는 즉시 범위를 벗어납니다. Win32 응용 프로그램에서 이 문제를 처리하는 일반적인 방법은 정적 변수나 전역 변수를 사용하는 것입니다. 그러나 이러한 변수 형식에는 관리되는 개체를 할당할 수 없습니다. HwndSource 개체와 연결된 창 핸들을 전역 변수나 정적 변수에 할당할 수 있지만 그렇게 하더라도 개체 자체에 액세스할 수 있는 것은 아닙니다.
이 문제를 해결하는 가장 간단한 방법은 액세스해야 하는 모든 관리되는 개체에 대한 참조를 유지하는 정적 필드 집합이 포함된 관리되는 클래스를 구현하는 것입니다. 샘플에서는 WPFPageHost 클래스를 사용하여 WPF 콘텐츠에 대한 참조와 나중에 사용자가 변경할 수 있는 여러 속성의 초기 값을 유지합니다. 이것은 헤더에 정의되어 있습니다.
GetHwnd 함수의 뒷부분에서는 myPage가 범위에 있는 동안 나중에 사용할 수 있도록 이러한 필드에 값을 할당합니다.
WPF 콘텐츠와 통신
WPF 콘텐츠와의 통신에는 두 가지 형식이 있습니다. 사용자가 OK 또는 Cancel 단추를 클릭할 때 응용 프로그램이 WPF 콘텐츠에서 정보를 받습니다. 또한 응용 프로그램에는 사용자가 배경색이나 기본 글꼴 크기와 같은 다양한 WPF 콘텐츠 속성을 변경할 수 있는 UI가 있습니다.
위에서 설명한 것처럼 사용자가 두 단추 중 하나를 클릭하면 WPF 콘텐츠가 OnButtonClicked 이벤트를 발생시킵니다. 응용 프로그램에서는 처리기를 이 이벤트에 연결하여 알림을 받습니다. 사용자가 OK 단추를 클릭하면 처리기가 WPF 콘텐츠에서 사용자 정보를 가져와서 일련의 정적 컨트롤에 표시합니다.
처리기는 WPF 콘텐츠에서 사용자 지정 이벤트 인수 개체인 MyPageEventArgs를 받습니다. OK 단추를 클릭하면 이 개체의 IsOK 속성이 true로 설정되고 Cancel 단추를 클릭하면 해당 속성이 false로 설정됩니다.
OK 단추를 클릭하면 처리기가 컨테이너 클래스에서 WPF 콘텐츠에 대한 참조를 가져옵니다. 그런 다음 연결된 WPF 콘텐츠 속성에 유지되는 사용자 정보를 수집하고 정적 컨트롤을 사용하여 부모 창에 이 정보를 표시합니다. WPF 콘텐츠 데이터는 관리되는 문자열의 형식이므로 Win32 컨트롤에서 사용할 수 있도록 마샬링되어야 합니다. Cancel 단추를 클릭하면 처리기가 정적 컨트롤에서 데이터를 지웁니다.
응용 프로그램 UI에는 사용자가 WPF 콘텐츠의 배경색과 몇 가지 글꼴 관련 속성을 수정할 수 있는 라디오 단추 집합이 있습니다. 다음 예제에서는 응용 프로그램의 창 프로시저(WndProc) 및 서로 다른 메시지에서 배경색을 비롯한 다양한 속성을 설정하는 해당 메시지 처리의 일부분을 보여 줍니다. 다른 부분은 비슷하므로 표시하지 않았습니다. 자세한 내용과 컨텍스트는 전체 샘플을 참조하십시오.
배경색을 설정하려면 WPFPageHost에서 WPF 콘텐츠(hostedPage)에 대한 참조를 가져와서 배경색 속성을 적절한 색으로 설정합니다. 샘플에서는 원래 색, 연한 녹색 또는 연한 주황색의 세 가지 색 옵션을 사용합니다. 원래 배경색은 WPFPageHost 클래스에 정적 필드로 저장되어 있습니다. 다른 두 색을 설정하기 위해 새 SolidColorBrush 개체를 만들고 생성자에 Colors 개체의 정적 색 값을 전달합니다.
WPF 페이지 구현
실제 구현 지식이 없어도 WPF 콘텐츠를 호스팅하고 사용할 수 있습니다. WPF 콘텐츠가 개별 DLL에 패키지되어 있으면 모든 CLR(공용 언어 런타임) 언어에서 해당 콘텐츠를 빌드할 수 있습니다. 다음은 샘플에 사용된 간단한 C++/CLI의 구현 연습입니다. 이 단원에는 다음과 같은 하위 단원이 있습니다.
레이아웃
호스트 창으로 데이터 반환
WPF 속성 설정
레이아웃
WPF 콘텐츠의 UI 요소는 다섯 개의 TextBox 컨트롤 및 연관된 Label 컨트롤인 Name, Address, City, State 및 Zip으로 구성되어 있습니다. 또한 Button 컨트롤인 OK 및 Cancel이 있습니다.
WPF 콘텐츠는 WPFPage 클래스에 구현됩니다. 레이아웃은 Grid 레이아웃 요소로 처리됩니다. 이 클래스는 Grid에서 상속되므로 쉽게 WPF 콘텐츠 루트 요소로 만들 수 있습니다.
WPF 콘텐츠 생성자는 필요한 너비와 높이를 가져와서 그에 따라 Grid의 크기를 조정합니다. 그런 다음 ColumnDefinition 및 RowDefinition 개체 집합을 만들고 각 개체 집합을 Grid 개체의 기본 형식인 ColumnDefinitions 및 RowDefinitions 컬렉션에 추가하여 기본 레이아웃을 정의합니다. 이렇게 하면 셀의 내용에 따라 크기가 결정되는 5개의 행과 7개의 열로 구성된 표가 정의됩니다.
다음으로 생성자는 UI 요소를 Grid에 추가합니다. 첫 번째 요소는 제목 텍스트입니다. 이것은 표의 첫 번째 행 가운데에 나타나는 Label 컨트롤입니다.
다음 행에는 Name Label 컨트롤 및 이와 연결된 TextBox 컨트롤이 들어 있습니다. 각 레이블/텍스트 상자 쌍에 동일한 코드가 사용되므로 해당 코드는 전용 메서드 쌍에 배치되고 레이블/텍스트 상자 쌍 다섯 개 모두에 사용됩니다. 이러한 메서드는 적절한 컨트롤을 만들고 Grid 클래스의 정적 SetColumn 및 SetRow 메서드를 호출하여 적절한 셀에 컨트롤을 배치합니다. 컨트롤을 만든 후 샘플에서는 Grid의 Children 속성에서 Add 메서드를 호출하여 표에 컨트롤을 추가합니다. 나머지 레이블/텍스트 상자 쌍을 추가하는 코드도 비슷합니다. 자세한 내용은 샘플 코드를 참조하십시오.
두 메서드 구현은 다음과 같습니다.
마지막으로 샘플에서는 OK 및 Cancel 단추를 추가하고 이벤트 처리기를 단추의 Click 이벤트에 연결합니다.
호스트 창으로 데이터 반환
두 단추 중 하나를 클릭하면 해당 Click 이벤트가 발생합니다. 호스트 창에서는 단순히 처리기를 이러한 이벤트에 연결하고 TextBox 컨트롤에서 직접 데이터를 가져옵니다. 샘플에서는 다소 직접적이지 않은 방식을 사용합니다. 즉, WPF 콘텐츠 내에서 Click을 처리한 다음 사용자 지정 이벤트 OnButtonClicked를 발생시켜 WPF 콘텐츠에 알립니다. 따라서 WPF 콘텐츠가 호스트에 알리기 전에 몇 가지 매개 변수 유효성 검사를 수행할 수 있습니다. 처리기는 TextBox 컨트롤에서 텍스트를 가져와서 호스트가 정보를 검색할 수 있는 공용 속성에 해당 텍스트를 할당합니다.
WPFPage.h의 이벤트 선언:
WPFPage.cpp의 Click 이벤트 처리기:
WPF 속성 설정
Win32 호스트를 사용하면 사용자가 몇 가지 WPF 콘텐츠 속성을 변경할 수 있습니다. Win32 측에서 이것은 단순히 속성을 변경하는 것입니다. 하지만 모든 컨트롤의 글꼴을 제어하는 단일 전역 속성이 없기 때문에 WPF 콘텐츠 클래스에서 이러한 구현은 다소 복잡합니다. 따라서 속성의 set 접근자에서 각 컨트롤에 대한 적절한 속성을 변경합니다. 다음 예제에서는 DefaultFontFamily 속성의 코드를 보여 줍니다. 이 속성을 설정하면 다양한 컨트롤에 대한 FontFamily 속성을 설정하는 전용 메서드가 호출됩니다.
WPFPage.h에서:
WPFPage.cpp에서: