Partilhar via


Passo a passo: Hospedar um relógio WPF no Win32

Para colocar o WPF dentro de aplicações Win32, use HwndSource, que fornece o HWND que contém o conteúdo do WPF. Primeiro você cria o HwndSource, dando-lhe parâmetros semelhantes a CreateWindow. Em seguida, tu dizes ao HwndSource qual o conteúdo do WPF que queres incluir nele. Finalmente, você tira o HWND do HwndSource. Este passo a passo ilustra como criar um WPF misto dentro do aplicativo Win32 que reimplementa o sistema operacional caixa de diálogo Propriedades de Data e Hora.

Pré-requisitos

Consulte WPF e Win32 Interoperation.

Como usar este tutorial

Este tutorial concentra-se nas etapas importantes da produção de um aplicativo de interoperação. O tutorial é apoiado por um exemplo, Win32 Clock Interoperation Sample, mas esse exemplo é representativo do produto final. Este tutorial documenta as etapas como se você estivesse começando com um projeto Win32 existente de sua preferência, talvez um projeto pré-existente, e você estivesse adicionando um WPF hospedado ao seu aplicativo. Você pode comparar o seu produto final com Exemplo de Interoperação de Relógio Win32.

Um passo a passo do Windows Presentation Framework dentro do Win32 (HwndSource)

O gráfico a seguir mostra o produto final pretendido deste tutorial:

Captura de tela que mostra a caixa de diálogo Propriedades de Data e Hora.

Você pode recriar essa caixa de diálogo criando um projeto C++ Win32 no Visual Studio e usando o editor de diálogo para criar o seguinte:

caixa de diálogo Propriedades de Data e Hora Recriadas

(Você não precisa usar o Visual Studio para usar HwndSource, e você não precisa usar C++ para escrever programas Win32, mas esta é uma maneira bastante típica de fazê-lo, e se presta bem a uma explicação tutorial passo a passo).

Você precisa realizar cinco subetapas específicas para colocar um relógio WPF na caixa de diálogo:

  1. Habilite seu projeto Win32 para chamar código gerenciado (/clr) alterando as configurações do projeto no Visual Studio.

  2. Crie um WPFPage numa DLL separada.

  3. Coloque esse WPFPage dentro de um HwndSource.

  4. Obtenha um HWND para esse Page usando a propriedade Handle.

  5. Use o Win32 para decidir onde colocar o HWND dentro do aplicativo Win32 maior

/clr

A primeira etapa é transformar esse projeto Win32 não gerenciado em um que pode chamar código gerenciado. Use a opção de compilador /clr, que será vinculada às DLLs necessárias que você deseja usar, e ajuste o método Main para uso com WPF.

Para habilitar o uso de código gerenciado dentro do projeto C++: Clique com o botão direito do mouse no projeto win32clock e selecione Propriedades. Na página de propriedades General (o padrão), altere o suporte ao Common Language Runtime para /clr.

Em seguida, adicione referências às DLLs necessárias para WPF: PresentationCore.dll, PresentationFramework.dll, System.dll, WindowsBase.dll, UIAutomationProvider.dlle UIAutomationTypes.dll. (As instruções a seguir pressupõem que o sistema operacional esteja instalado na unidade C:.)

  1. Clique com o botão direito do mouse no projeto win32clock e selecione Referências...e dentro dessa caixa de diálogo:

  2. Clique com o botão direito do mouse no projeto win32clock e selecione Referências....

  3. Clique Adicionar Nova Referência, clique no separador Procurar, introduza C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dlle clique em OK.

  4. Repita para PresentationFramework.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll.

  5. Repita para WindowsBase.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll.

  6. Repita para UIAutomationTypes.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationTypes.dll.

  7. Repita para UIAutomationProvider.dll: C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationProvider.dll

  8. Clique Adicionar Nova Referência, selecione System.dlle clique em OK.

  9. Clique OK para sair das Páginas de Propriedades do win32clock para adicionar referências.

Finalmente, adicione o STAThreadAttribute ao método _tWinMain para uso com o WPF:

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

Esse atributo indica ao Common Language Runtime (CLR) que, quando inicializa o Modelo de Objeto Componente (COM), deve utilizar um modelo de apartamento com um único encadeamento (STA), que é necessário para WPF (e Windows Forms).

Criar uma página do Windows Presentation Framework

Em seguida, você cria uma DLL que define um WPFPage. Muitas vezes, é mais fácil criar o WPFPage como um aplicativo autônomo e escrever e depurar a parte do WPF dessa maneira. Uma vez feito, esse projeto pode ser transformado em uma DLL clicando com o botão direito do mouse no projeto, clicando em Propriedades, indo para o Aplicativo e alterando o tipo de saída para Biblioteca de Classes do Windows.

O projeto dll WPF pode então ser combinado com o projeto Win32 (uma solução que contém dois projetos) - clique com o botão direito do mouse na solução, selecione Add\Existing Project.

Para usar essa dll WPF do projeto Win32, você precisa adicionar uma referência:

  1. Clique com o botão direito do mouse no projeto win32clock e selecione Referências....

  2. Clique em Adicionar referência.

  3. Clique na guia Projetos. Selecione WPFClock, clique em OK.

  4. Clique OK para sair das Páginas de Propriedades do win32clock para adicionar referências.

HwndSource

Em seguida, use HwndSource para fazer com que o WPFPage se pareça com um HWND. Você adiciona este bloco de código a um arquivo C++:

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();
    }
}
}

Este é um longo pedaço de código que poderia usar alguma explicação. A primeira parte é composta por várias cláusulas para que você não precise qualificar totalmente todas as chamadas:

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

Em seguida, você define uma função que cria o conteúdo WPF, coloca um HwndSource em torno dele e retorna o HWND:

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

Primeiro, você cria um HwndSource, cujos parâmetros são semelhantes a 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
);

Em seguida, você cria a classe de conteúdo WPF chamando seu construtor:

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

Em seguida, conecte a página ao HwndSource:

source->RootVisual = page;

E na linha final, retorne o HWND para o HwndSource:

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

Posicionamento do Hwnd

Agora que você tem um HWND que contém o relógio WPF, você precisa colocar esse HWND dentro da caixa de diálogo Win32. Se você soubesse exatamente onde colocar o HWND, você simplesmente passaria esse tamanho e local para a função GetHwnd que você definiu anteriormente. Mas você usou um arquivo de recurso para definir a caixa de diálogo, portanto, não tem certeza exata de onde qualquer um dos HWNDs está posicionado. Você pode usar o editor de diálogo do Visual Studio para colocar um controle STATIC Win32 onde você deseja que o relógio vá ("Inserir relógio aqui") e usá-lo para posicionar o relógio WPF.

Onde se lida com WM_INITDIALOG, usa GetDlgItem para recuperar o HWND para o espaço reservado STATIC.

HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);

Depois, calcule o tamanho e a posição desse espaço reservado "STATIC", para colocar o relógio WPF lá.

retângulo RECT;

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);

Em seguida, você oculta o espaço reservado STATIC:

ShowWindow(placeholder, SW_HIDE);

E crie o relógio WPF HWND nesse local:

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

Para tornar o tutorial interessante e produzir um relógio WPF real, você precisará criar um controle de relógio WPF neste ponto. Você pode fazer isso principalmente na marcação, com apenas alguns manipuladores de eventos no code-behind. Como este tutorial é sobre interoperação e não sobre design de controle, o código completo para o relógio WPF é fornecido aqui como um bloco de código, sem instruções discretas para construí-lo ou o que cada parte significa. Sinta-se à vontade para experimentar esse código para alterar a aparência ou a funcionalidade do controle.

Aqui está a marcação:

<Page x:Class="WPFClock.Clock"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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>

E aqui está o "code-behind" que o acompanha:

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);
        }
    }
}

O resultado final é semelhante a:

caixa de diálogo Propriedades de Data e Hora do resultado final

Para comparar o seu resultado final ao código que produziu esta captura de ecrã, consulte Win32 Clock Interoperation Sample.

Ver também