Compartilhar via


Tutorial: Criar um aplicativo Win32 Hosting conteúdo WPF

Para colocar WPF dentro de aplicativos Win32, use HwndSource, que fornece o HWND que contém seu conteúdo de WPF. Primeiro você cria o HwndSource, fornecendo a ele parâmetros semelhante à CreateWindow. Em seguida, você diz o HwndSource sobre o conteúdo de WPF que você deseja dentro dele. Finalmente, você obtém a HWND de saída de HwndSource. Essa explicação passo a passo mostra como criar um WPF misto dentro do aplicativo Win32 que reimplementa o diálogo do sistema operacional Date and Time Properties.

Pré-requisitos

Consulte Visão geral sobre interoperabilidade entre WPF e Win32.

Como usar este Tutorial

Este tutorial concentra nas etapas importantes de produzir um aplicativo de interoperação. O tutorial é feito sobre 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 iniciando com um de seus próprios projetos Win32 existente, talvez um projeto pré-existente, e você estava adicionando um WPF hospedado ao seu aplicativo. Você pode comparar o seu produto final com Exemplo de interoperação de relógio do Win32.

Um guia passo a passo de Windows Presentation Framework Inside Win32 (HwndSource)

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

Caixa de diálogo Propriedades de Data e Hora

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

Caixa de diálogo Propriedades de Data e Hora

(Você não precisará usar Microsoft Visual Studio para usar HwndSource, e você não precisará usar C++ para escrever programas Win32, mas isso é uma maneira bastante comum de fazê-lo, e presta se bem para uma explicação do tutorial em passos).

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

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

  2. Crie um WPF Page em uma DLL separada.

  3. Coloque esse WPF Page em um HwndSource.

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

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

/clr

A primeira etapa é transformar este projeto Win32 não gerenciado em um que pode chamar código gerenciado. Use a opção de compilador /CLR, a qual vinculará as DLLs necessárias que você deseja usar, e ajusta o método principal (Main) para uso com WPF.

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

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

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

  2. Clique com o botão direito do mouse no projeto win32clock e selecione References... .

  3. Clique em Add New Reference, clique na guia Browse, digite C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationCore.dll, e clique em OK.

  4. Repetir para PresentationFramework.dll: C:\programa arquivos Assemblies\Microsoft\estrutura\v3.0\PresentationFramework.dll.

  5. Repetir para WindowsBase.dll: C:\programa arquivos Assemblies\Microsoft\estrutura\v3.0\WindowsBase.dll.

  6. Repetir para UIAutomationTypes.dll: C:\programa arquivos Assemblies\Microsoft\estrutura\v3.0\UIAutomationTypes.dll.

  7. Repetir para UIAutomationProvider.dll: C:\programa arquivos Assemblies\Microsoft\estrutura\v3.0\UIAutomationProvider.dll.

  8. Clique em Add New Reference,selecione System.dll e clique em OK .

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

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

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

Esse atributo instrui o common language runtime (CLR) que quando ele inicializa Modelo de objeto componente (COM),ele deve usar um modelo de single threaded apartment (STA), que é necessário para WPF (e Windows Forms).

Crie uma Windows Presentation Framework Page

Em seguida, crie uma DLL que define um WPF Page. É geralmente mais fácil criar o WPF Page como um aplicativo autônomo e escrever e depurar a parte WPF dessa maneira. Depois disso, o projeto pode ser convertido em uma DLL, clicando com o botão direito do mouse no projeto, clicando em Properties,indo para Application e alterando o Output type (tipo de saída) para Windows Class Library (biblioteca de classes do Windows).

O projeto de DLL WPF pode 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ê precisará adicionar uma referência:

  1. Clique com o botão direito do mouse no projeto win32clock e selecione References... .

  2. Clique em Add New Reference.

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

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

HwndSource

Em seguida, você usa HwndSource para fazer a WPF Page aparentar como 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();
    }
}
}

Isso é um pedaço de código grande que poderia usar alguma explicação. A primeira parte é composta de várias cláusulas de modo que você não precisa 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 ao redor 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 às 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ê criar a classe de conteúdo WPF chamando seu construtor:

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

Você então conecta a página ao HwndSource:

        source->RootVisual = page;

E na linha final, retorne o HWND para HwndSource:

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

Posicionando o 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 apenas onde colocar a HWND, você apenas passariam o 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, então você não tem certeza de onde qualquer dos HWNDs estão posicionados. Você pode usar o Editor de diálogo Microsoft Visual Studio para colocar um controle estático Win32 onde deseja que o relógio fique (“Inserir relógio aqui” - “Insert clock here”) e use isso para posicionar o relógio WPF.

Onde você lida WM_INITDIALOG, você usa GetDlgItem para recuperar o HWND para o espaço reservado estático:

HWND placeholder = GetDlgItem(hDlg, IDC_CLOCK);

Em seguida, você calcula o tamanho e posição do que espaço reservado STATIC, tal que você pode colocar o relógio WPF naquele local:

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 cria o relógio WPF HWND naquele local:

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

Para tornar o tutorial interessantes e para produzir um relógio WPF real, você precisará criar um controle de relógio WPF neste momento. Você pode fazer isso basicamente na marcação, com apenas alguns manipuladores de eventos no código de apoio. Como este tutorial diz respeito a interoperação e não sobre design de controle, código completo para o relógio WPF é fornecido aqui como um bloco de código, sem distintas instruções para compilá-lo ou do significado de cada parte. Sinta-se à vontade para experimentar este código, para alterar a aparência e estilo ou funcionalidade do controle.

Aqui está a marcação:

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

E aqui está o código de apoio que a 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 aparenta como:

Caixa de diálogo Propriedades de Data e Hora

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

Consulte também

Tarefas

Exemplo de interoperação de relógio do Win32

Conceitos

Visão geral sobre interoperabilidade entre WPF e Win32

Referência

HwndSource