Compartilhar via


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

Para colocar o WPF dentro de aplicativos 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, você diz sobre o HwndSource conteúdo do WPF que deseja dentro dele. Finalmente, você obtém o HWND do HwndSource. Este passo a passo ilustra como criar um WPF misto dentro do aplicativo Win32 que reimplementa a caixa de diálogo Propriedades de Data e Hora do sistema operacional.

Pré-requisitos

Consulte Interoperação Win32 e WPF.

Como usar esse tutorial

Este tutorial mostra as etapas importantes de produção de um aplicativo de interoperação. O tutorial é com um exemplo, Exemplo de interoperação de relógio do Win32, mas esse exemplo reflete o 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 o Exemplo de interoperação de relógio do Win32.

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

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

Screenshot that shows the Date and Time Properties dialog box.

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

Recreated Date and Time Properties dialog box

(Você não precisa usar o Visual Studio para usar o , e você não precisa usar HwndSourceC++ para escrever programas Win32, mas essa é uma maneira bastante típica de fazer isso 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 em uma DLL separada.

  3. Coloque esse WPFPage dentro de um HwndSourcearquivo .

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

  5. Use 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 possa chamar código gerenciado. Use a opção de compilador /clr, que vinculará à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 Geral (o padrão), altere o suporte a Common Language Runtime para /clr.

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

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

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

  3. Clique em Adicionar Nova Referência, clique na guia Procurar, digite C:\Arquivos de Programa\Assemblies de Referência\Microsoft\Framework\v3.0\PresentationCore.dll e clique em OK.

  4. Repita para PresentationFramework.dll: C:\Arquivos de Programa\Assemblies de Referência\Microsoft\Framework\v3.0\PresentationFramework.dll.

  5. Repita para WindowsBase.dll: C:\Arquivos de Programa\Assemblies de Referência\Microsoft\Framework\v3.0\WindowsBase.dll.

  6. Repita para UIAutomationTypes.dll: C:\Arquivos de Programa\Assemblies de Referência\Microsoft\Framework\v3.0\UIAutomationTypes.dll.

  7. Repita para UIAutomationProvider.dll: C:\Arquivos de Programa\Assemblies de Referência\Microsoft\Framework\v3.0\UIAutomationProvider.dll.

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

  9. Clique em OK para sair das páginas de propriedades do win32clock para adicionar referências.

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

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

Esse atributo informa ao CLR (Common Language Runtime) que, quando ele inicializa o COM (Component Object Model), ele deve usar um STA (modelo de apartamento encadeado) único, que é necessário para o WPF (e o 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 WPF como um aplicativo autônomo e escrever e depurar a parte do WPFPage dessa maneira. Depois disso, o projeto pode ser convertido em uma DLL clicando com o botão direito do mouse no projeto, clicando em Propriedades, indo até o Aplicativo e alterando o tipo de saída para a 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 Nova Referência.

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

  4. Clique em OK para sair das páginas de propriedades do win32clock para adicionar referências.

HwndSource

Em seguida, você usa HwndSource para fazer o WPFPage parecer 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();
    }
}
}

Esse é uma parte grande do código que poderia ser explicada. 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 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 localização para a GetHwnd função que você definiu anteriormente. Mas você usou um arquivo de recurso para definir a caixa de diálogo, então não tem muita certeza sobre onde os HWNDs estão posicionados. Você pode usar o editor de diálogo do Visual Studio para colocar um controle Win32 STATIC onde você deseja que o relógio vá ("Inserir relógio aqui") e usá-lo para posicionar o relógio WPF.

Onde você manipula WM_INITDIALOG, você usa GetDlgItem para recuperar o HWND para o espaço reservado STATIC:

HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);

Em seguida, calcule o tamanho e a posição desse espaço reservado STATIC, para que você possa colocar o relógio WPF nesse lugar:

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, oculte 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 para produzir um relógio WPF real, você precisará criar um controle de relógio WPF neste ponto. Você pode fazer isso basicamente 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. Fique à vontade para testar este 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 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 se parece com:

Final result Date and Time Properties dialog box

Para comparar seu resultado final com o código que produziu a captura de tela, consulte Exemplo de interoperação de relógio do Win32.

Confira também