Files
docs-desktop/dotnet-desktop-guide/framework/wpf/advanced/walkthrough-hosting-a-wpf-clock-in-win32.md
T
Andy De George da363692ff Initial WPF content migrated (#17)
* Reset branch for WPF changes

* Convert BMP to PNG; fix link-out-of-scope err

* Add snippets for WPF... 6794 files!!!!

* Add missing snippets

* update file updated between migration

* Fix paths to include

* update breadcrumb and toc

* fix index links

* fix index links

* fix index links

* fix markdown
2020-09-04 09:46:28 -07:00

13 KiB
Raw Blame History

title, titleSuffix, ms.date, helpviewer_keywords, ms.assetid
title titleSuffix ms.date helpviewer_keywords ms.assetid
Walkthrough: Host a WPF Clock in Win32 03/30/2017
interoperability [WPF], tutorials
Win32 code [WPF], WPF interoperation
interoperability [WPF], Win32
555e55a7-0851-4ec8-b1c6-0acba7e9b648

Walkthrough: Host a WPF Clock in Win32

To put [!INCLUDETLA2#tla_winclient] inside Win32 applications, use xref:System.Windows.Interop.HwndSource, which provides the HWND that contains your [!INCLUDETLA2#tla_winclient] content. First you create the xref:System.Windows.Interop.HwndSource, giving it parameters similar to CreateWindow. Then you tell the xref:System.Windows.Interop.HwndSource about the [!INCLUDETLA2#tla_winclient] content you want inside it. Finally, you get the HWND out of the xref:System.Windows.Interop.HwndSource. This walkthrough illustrates how to create a mixed [!INCLUDETLA2#tla_winclient] inside Win32 application that reimplements the operating system Date and Time Properties dialog.

Prerequisites

See WPF and Win32 Interoperation.

How to Use This Tutorial

This tutorial concentrates on the important steps of producing an interoperation application. The tutorial is backed by a sample, Win32 Clock Interoperation Sample, but that sample is reflective of the end product. This tutorial documents the steps as if you were starting with an existing Win32 project of your own, perhaps a pre-existing project, and you were adding a hosted [!INCLUDETLA2#tla_winclient] to your application. You can compare your end product with Win32 Clock Interoperation Sample.

A Walkthrough of Windows Presentation Framework Inside Win32 (HwndSource)

The following graphic shows the intended end product of this tutorial:

Screenshot that shows the Date and Time Properties dialog box.

You can recreate this dialog by creating a C++ Win32 project in Visual Studio, and using the dialog editor to create the following:

Recreated Date and Time Properties dialog box

(You do not need to use Visual Studio to use xref:System.Windows.Interop.HwndSource, and you do not need to use C++ to write Win32 programs, but this is a fairly typical way to do it, and lends itself well to a stepwise tutorial explanation).

You need to accomplish five particular substeps in order to put a [!INCLUDETLA2#tla_winclient] clock into the dialog:

  1. Enable your Win32 project to call managed code (/clr) by changing project settings in Visual Studio.

  2. Create a [!INCLUDETLA2#tla_winclient]xref:System.Windows.Controls.Page in a separate DLL.

  3. Put that [!INCLUDETLA2#tla_winclient]xref:System.Windows.Controls.Page inside an xref:System.Windows.Interop.HwndSource.

  4. Get an HWND for that xref:System.Windows.Controls.Page using the xref:System.Windows.Interop.HwndSource.Handle%2A property.

  5. Use Win32 to decide where to place the HWND within the larger Win32 application

/clr

The first step is to turn this unmanaged Win32 project into one that can call managed code. You use the /clr compiler option, which will link to the necessary DLLs you want to use, and adjust the Main method for use with [!INCLUDETLA2#tla_winclient].

To enable the use of managed code inside the C++ project: Right-click on win32clock project and select Properties. On the General property page (the default), change Common Language Runtime support to /clr.

Next, add references to DLLs necessary for [!INCLUDETLA2#tla_winclient]: PresentationCore.dll, PresentationFramework.dll, System.dll, WindowsBase.dll, UIAutomationProvider.dll, and UIAutomationTypes.dll. (Following instructions assume the operating system is installed on C: drive.)

  1. Right-click win32clock project and select References..., and inside that dialog:

  2. Right-click win32clock project and select References....

  3. Click Add New Reference, click Browse tab, enter C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll, and click OK.

  4. Repeat for PresentationFramework.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll.

  5. Repeat for WindowsBase.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll.

  6. Repeat for UIAutomationTypes.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll.

  7. Repeat for UIAutomationProvider.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll.

  8. Click Add New Reference, select System.dll, and click OK.

  9. Click OK to exit the win32clock Property Pages for adding references.

Finally, add the STAThreadAttribute to the _tWinMain method for use with [!INCLUDETLA2#tla_winclient]:

[System::STAThreadAttribute]
int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)

This attribute tells the common language runtime (CLR) that when it initializes Component Object Model (COM), it should use a single threaded apartment model (STA), which is necessary for [!INCLUDETLA2#tla_winclient] (and Windows Forms).

Create a Windows Presentation Framework Page

Next, you create a DLL that defines a [!INCLUDETLA2#tla_winclient]xref:System.Windows.Controls.Page. Its often easiest to create the [!INCLUDETLA2#tla_winclient]xref:System.Windows.Controls.Page as a standalone application, and write and debug the [!INCLUDETLA2#tla_winclient] portion that way. Once done, that project can be turned into a DLL by right-clicking the project, clicking on Properties, going to the Application, and changing Output type to Windows Class Library.

The [!INCLUDETLA2#tla_winclient] dll project can then be combined with the Win32 project (one solution that contains two projects) right-click on the solution, select Add\Existing Project.

To use that [!INCLUDETLA2#tla_winclient] dll from the Win32 project, you need to add a reference:

  1. Right-click win32clock project and select References....

  2. Click Add New Reference.

  3. Click the Projects tab. Select WPFClock, click OK.

  4. Click OK to exit the win32clock Property Pages for adding references.

HwndSource

Next, you use xref:System.Windows.Interop.HwndSource to make the [!INCLUDETLA2#tla_winclient]xref:System.Windows.Controls.Page look like an HWND. You add this block of code to a C++ file:

namespace ManagedCode
{
    using namespace System;
    using namespace System::Windows;
    using namespace System::Windows::Interop;
    using namespace System::Windows::Media;

    HWND GetHwnd(HWND parent, int x, int y, int width, int height) {
        HwndSource^ source = gcnew HwndSource(
            0, // class style
            WS_VISIBLE | WS_CHILD, // style
            0, // exstyle
            x, y, width, height,
            "hi", // NAME
            IntPtr(parent)        // parent window
            );

        UIElement^ page = gcnew WPFClock::Clock();
        source->RootVisual = page;
        return (HWND) source->Handle.ToPointer();
    }
}
}

This is a long piece of code that could use some explanation. The first part is various clauses so that you do not need to fully qualify all the calls:

namespace ManagedCode
{
    using namespace System;
    using namespace System::Windows;
    using namespace System::Windows::Interop;
    using namespace System::Windows::Media;

Then you define a function that creates the [!INCLUDETLA2#tla_winclient] content, puts an xref:System.Windows.Interop.HwndSource around it, and returns the HWND:

HWND GetHwnd(HWND parent, int x, int y, int width, int height) {

First you create an xref:System.Windows.Interop.HwndSource, whose parameters are similar to CreateWindow:

HwndSource^ source = gcnew HwndSource(
    0, // class style
    WS_VISIBLE | WS_CHILD, // style
    0, // exstyle
    x, y, width, height,
    "hi", // NAME
    IntPtr(parent) // parent window
);

Then you create the [!INCLUDETLA2#tla_winclient] content class by calling its constructor:

UIElement^ page = gcnew WPFClock::Clock();

You then connect the page to the xref:System.Windows.Interop.HwndSource:

source->RootVisual = page;

And in the final line, return the HWND for the xref:System.Windows.Interop.HwndSource:

return (HWND) source->Handle.ToPointer();

Positioning the Hwnd

Now that you have an HWND that contains the [!INCLUDETLA2#tla_winclient] clock, you need to put that HWND inside the Win32 dialog. If you knew just where to put the HWND, you would just pass that size and location to the GetHwnd function you defined earlier. But you used a resource file to define the dialog, so you are not exactly sure where any of the HWNDs are positioned. You can use the Visual Studio dialog editor to put a Win32 STATIC control where you want the clock to go ("Insert clock here"), and use that to position the [!INCLUDETLA2#tla_winclient] clock.

Where you handle WM_INITDIALOG, you use GetDlgItem to retrieve the HWND for the placeholder STATIC:

HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);

You then calculate the size and position of that placeholder STATIC, so you can put the [!INCLUDETLA2#tla_winclient] clock in that place:

RECT rectangle;

GetWindowRect(placeholder, &rectangle);
int width = rectangle.right - rectangle.left;
int height = rectangle.bottom - rectangle.top;
POINT point;
point.x = rectangle.left;
point.y = rectangle.top;
result = MapWindowPoints(NULL, hDlg, &point, 1);

Then you hide the placeholder STATIC:

ShowWindow(placeholder, SW_HIDE);

And create the [!INCLUDETLA2#tla_winclient] clock HWND in that location:

HWND clock = ManagedCode::GetHwnd(hDlg, point.x, point.y, width, height);

To make the tutorial interesting, and to produce a real [!INCLUDETLA2#tla_winclient] clock, you will need to create a [!INCLUDETLA2#tla_winclient] clock control at this point. You can do so mostly in markup, with just a few event handlers in code-behind. Since this tutorial is about interoperation and not about control design, complete code for the [!INCLUDETLA2#tla_winclient] clock is provided here as a code block, without discrete instructions for building it up or what each part means. Feel free to experiment with this code to change the look and feel or functionality of the control.

Here is the markup:

[!code-xamlWin32Clock#AllClockXAML]

And here is the accompanying code-behind:

[!code-csharpWin32Clock#AllClockCS]

The final result looks like:

Final result Date and Time Properties dialog box

To compare your end result to the code that produced this screenshot, see Win32 Clock Interoperation Sample.

See also