Partilhar via


Demonstra Passo a passo: Hospedando um relógio WPF no Win32

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

Pré-requisitos

See WPF e a interoperação de Win32.

How to Use This Tutorial

This tutorial concentrates on the important steps of producing an interoperation application. O tutorial é feito por uma amostra, Exemplo de interoperação de relógio do Win32, mas esse exemplo é o reflexo do produto final. 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 WPF to your application. Você pode comparar o seu produto final com Exemplo de interoperação de relógio do Win32.

A Walkthrough of Windows Presentation Framework Inside Win32 (HwndSource)

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

Caixa de diálogo Propriedades de Data e Hora

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

Caixa de diálogo Propriedades de Data e Hora

(You do not need to use Microsoft Visual Studio to use 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 WPF clock into the dialog:

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

  2. Criar um WPF Page em uma DLL separada.

  3. Put that WPF Page inside an HwndSource.

  4. Get an HWND for that Page using the Handle 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. Você usar a opção de compilador /clr, que fará um link para as DLLs necessárias que você deseja usar e ajustar o método Main para uso com o WPF.

Para ativar o uso do código gerenciado dentro do projeto C++: Clique com o botão direito no projeto de win32clock e selecione Propriedades. On the General property page (the default), change Common Language Runtime support to /clr.

Em seguida, adicione referências para as DLLs necessárias para WPF: PresentationCore. dll, PresentationFramework.dll, System. dll, WindowsBase. dll, UIAutomationProvider e UIAutomationTypes. (Seguindo instruções assumem que o sistema operacional está instalado na unidade c: unidade).

  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. Repita para PresentationFramework.dll: C:\Program Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll de arquivos.

  5. Repita para WindowsBase. dll: C:\Program Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll de arquivos.

  6. Repita para UIAutomationTypes: C:\Program Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll de arquivos.

  7. Repita para UIAutomationProvider: C:\Program Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll de arquivos.

  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 WPF:

[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 WPF (and Windows Forms).

Create a Windows Presentation Framework Page

Next, you create a DLL that defines a WPF Page. It’s often easiest to create the WPF Page as a standalone application, and write and debug the WPF 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.

O WPF o projeto de dll pode ser combinado com o Win32 (uma solução que contém dois projetos) – do projeto com o botão direito sobre a solução, selecione Add\Existing projeto.

Usar que WPF dll a partir de Win32 projeto, você precisa adicionar uma referência:

  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

Em seguida, use HwndSource para fazer a WPF Page parecer um 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 WPF content, puts an HwndSource around it, and returns the HWND:

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

First you create an 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 WPF content class by calling its constructor:

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

You then connect the page to the HwndSource:

        source->RootVisual = page;

And in the final line, return the HWND for the HwndSource:

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

Positioning the Hwnd

Now that you have an HWND that contains the WPF 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. Você pode usar o Microsoft Visual Studio o editor de diálogo para colocar um Win32 estático controlar onde você deseja que o relógio para ir ("Inserir relógio aqui") e use-o para posicionar o WPF relógio.

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 WPF 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 WPF 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 WPF clock, you will need to create a WPF 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 WPF 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:

<Page x:Class="WPFClock.Clock"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    >
    <Grid>
        <Grid.Background>
            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
              <GradientStop Color="#fcfcfe" Offset="0" />
              <GradientStop Color="#f6f4f0" Offset="1.0" />
            </LinearGradientBrush>
        </Grid.Background>

        <Grid Name="PodClock" HorizontalAlignment="Center" VerticalAlignment="Center">
            <Grid.Resources>
                <Storyboard x:Key="sb">
                    <DoubleAnimation From="0" To="360" Duration="12:00:00" RepeatBehavior="Forever"
                        Storyboard.TargetName="HourHand"
                        Storyboard.TargetProperty="(Rectangle.RenderTransform).(RotateTransform.Angle)" 
                        />
                    <DoubleAnimation From="0" To="360" Duration="01:00:00" RepeatBehavior="Forever"
                        Storyboard.TargetName="MinuteHand"  
                        Storyboard.TargetProperty="(Rectangle.RenderTransform).(RotateTransform.Angle)"
                        />
                    <DoubleAnimation From="0" To="360" Duration="0:1:00" RepeatBehavior="Forever"
                        Storyboard.TargetName="SecondHand"  
                        Storyboard.TargetProperty="(Rectangle.RenderTransform).(RotateTransform.Angle)"
                        />
                </Storyboard>
            </Grid.Resources>

          <Ellipse Width="108" Height="108" StrokeThickness="3">
            <Ellipse.Stroke>
              <LinearGradientBrush>
                <GradientStop Color="LightBlue" Offset="0" />
                <GradientStop Color="DarkBlue" Offset="1" />
              </LinearGradientBrush>
            </Ellipse.Stroke>
          </Ellipse>
          <Ellipse VerticalAlignment="Center" HorizontalAlignment="Center" Width="104" Height="104" Fill="LightBlue" StrokeThickness="3">
            <Ellipse.Stroke>
              <LinearGradientBrush>
                <GradientStop Color="DarkBlue" Offset="0" />
                <GradientStop Color="LightBlue" Offset="1" />
              </LinearGradientBrush>
            </Ellipse.Stroke>          
          </Ellipse>
            <Border BorderThickness="1" BorderBrush="Black" Background="White" Margin="20" HorizontalAlignment="Right" VerticalAlignment="Center">
                <TextBlock Name="MonthDay" Text="{Binding}"/>
            </Border>
            <Canvas Width="102" Height="102">
                <Ellipse Width="8" Height="8" Fill="Black" Canvas.Top="46" Canvas.Left="46" />
                <Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="0" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="30" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="60" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="90" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="120" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="150" />
                      </Rectangle.RenderTransform>
                    </Rectangle>
                    <Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
                      <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="180" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="210" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="240" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="48" Fill="Black" Width="4" Height="8">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="270" />
                      </Rectangle.RenderTransform>
                    </Rectangle>
                    <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                      <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="300" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle Canvas.Top="5" Canvas.Left="49" Fill="Black" Width="2" Height="6">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="2" CenterY="46" Angle="330" />
                    </Rectangle.RenderTransform>
                </Rectangle>


                <Rectangle x:Name="HourHand" Canvas.Top="21" Canvas.Left="48" 
                            Fill="Black" Width="4" Height="30">
                    <Rectangle.RenderTransform>
                        <RotateTransform x:Name="HourHand2" CenterX="2" CenterY="30" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle x:Name="MinuteHand" Canvas.Top="6" Canvas.Left="49" 
                        Fill="Black" Width="2" Height="45">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="1" CenterY="45" />
                    </Rectangle.RenderTransform>
                </Rectangle>
                <Rectangle x:Name="SecondHand" Canvas.Top="4" Canvas.Left="49" 
                        Fill="Red" Width="1" Height="47">
                    <Rectangle.RenderTransform>
                        <RotateTransform CenterX="0.5" CenterY="47" />
                    </Rectangle.RenderTransform>
                </Rectangle>
            </Canvas>
        </Grid>
    </Grid>
</Page>

And here is the accompanying code-behind:

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Windows.Threading;
    
    namespace WPFClock
    {
        /// <summary>
        /// Interaction logic for Clock.xaml
        /// </summary>
        public partial class Clock : Page
        {
            private DispatcherTimer _dayTimer;
    
            public Clock()
            {
                InitializeComponent();
                this.Loaded += new RoutedEventHandler(Clock_Loaded);
    
            }
    
            void Clock_Loaded(object sender, RoutedEventArgs e) {
                // set the datacontext to be today's date
                DateTime now = DateTime.Now;
                DataContext = now.Day.ToString();
    
                // then set up a timer to fire at the start of tomorrow, so that we can update
                // the datacontext
                _dayTimer = new DispatcherTimer();
                _dayTimer.Interval = new TimeSpan(1, 0, 0, 0) - now.TimeOfDay;
                _dayTimer.Tick += new EventHandler(OnDayChange);
                _dayTimer.Start();
    
                // finally, seek the timeline, which assumes a beginning at midnight, to the appropriate
                // offset
                Storyboard sb = (Storyboard)PodClock.FindResource("sb");
                sb.Begin(PodClock, HandoffBehavior.SnapshotAndReplace, true);
                sb.Seek(PodClock, now.TimeOfDay, TimeSeekOrigin.BeginTime);
            }
    
            private void OnDayChange(object sender, EventArgs e)
            {
                // date has changed, update the datacontext to reflect today's date
                DateTime now = DateTime.Now;
                DataContext = now.Day.ToString();
                _dayTimer.Interval = new TimeSpan(1, 0, 0, 0);
            }
        }
    }

The final result looks like:

![Caixa de diálogo Propriedades de Data e Hora](images/Aa970266.InteropArch08(pt-br,VS.100).png "Caixa de diálogo Propriedades de Data e Hora")

Para comparar seu resultado final para o código que produziu a captura de tela, consulte [Exemplo de interoperação de relógio do Win32](https://go.microsoft.com/fwlink/?linkid=160051).

## Consulte também

#### Referência

[HwndSource](https://msdn.microsoft.com/pt-br/library/ms595653\(v=vs.100\))

#### Conceitos

[WPF e a interoperação de Win32](ms742522\(v=vs.100\).md)

#### Outros recursos

[Exemplo de interoperação de relógio do Win32](https://go.microsoft.com/fwlink/?linkid=160051)