Interoperabilidade entre Windows Forms e WPF

Bruno Sonnino
Microsoft MVP
Dezembro 2008

Tecnologias: WPF e Windows Forms                    
Sumário: Esse artigo mostra como utilizar novas funcionalidades do Windows Presentation Foundation (WPF) em suas aplicações Windows Forms e vice-versa.
Clique aqui para baixar o código fonte utilizado no artigo

arrow_px_down.gif Introdução
arrow_px_down.gif Usando janelas WPF em aplicações WinForms
arrow_px_down.gif Usando janelas Winforms em aplicações WPF
arrow_px_down.gif Incluindo controles Winforms em aplicações WPF
arrow_px_down.gif Incluindo controles WPF em aplicações Winforms
arrow_px_down.gif Conclusões
arrow_px_down.gif Links úteis
arrow_px_down.gif Sobre o Autor

Introdução

Você tem um projeto já escrito em Windows Forms e quer usar as novas funcionalidades do WPF, mas não quer reescrever todo o projeto, quer fazer esta conversão aos poucos, convertendo partes das telas ou mesmo telas inteiras para WPF até finalizar a conversão da interface do usuário.

A conversão das telas não é uma questão de tudo ou nada, WPF ou WinForms: você não precisa converter todo o seu projeto, pode convertê-lo aos poucos. Neste artigo, mostraremos as técnicas de interoperabilidade entre as duas plataformas, mostrando com fazer esta conversão.

Usando janelas WPF em aplicações WinForms

Uma das maneiras mais simples de misturar as duas plataformas é criar uma janela WPF e chamá-la a partir de uma aplicação WinForms. Para isso, no Visual Studio, crie uma nova aplicação WinForms e chame-a de WinFormsInteropWPF. Na janela principal, coloque um botão e mude sua propriedade Text para Chama WPF.

Em seguida, criaremos nossa janela WPF que será chamada a partir do clique do botão. Poderíamos adicionar esta janela ao projeto. Porém,  por questões de organização, colocaremos a nova janela em uma biblioteca de classes. Na solution, adicione uma nova WPF User Control Library e chame-a de PaginasWPF. Não precisaremos do UserControl que foi criado para nós, assim remova UserControl1.xaml do projeto.

Adicione um novo item ao projeto, do tipo Window (WPF). Chame-o de JanelaWPF. No XAML da janela, coloque o seguinte código dentro da grid:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <TextBlock Text="Item 1" VerticalAlignment="Center" Margin="10,0" Grid.Row="0"/>
    <TextBlock Text="Item 2" VerticalAlignment="Center" Margin="10,0" Grid.Row="1"/>
    <TextBlock Text="Item 3" VerticalAlignment="Center" Margin="10,0" Grid.Row="2"/>
    <TextBlock Text="Item 4" VerticalAlignment="Center" Margin="10,0" Grid.Row="3"/>
    <TextBlock Text="Item 5" VerticalAlignment="Center" Margin="10,0" Grid.Row="4"/>
    <TextBox VerticalAlignment="Center" Margin="10,0" Grid.Row="0" Grid.Column="1"/>
    <TextBox VerticalAlignment="Center" Margin="10,0" Grid.Row="1" Grid.Column="1"/>
    <TextBox VerticalAlignment="Center" Margin="10,0" Grid.Row="2" Grid.Column="1"/>
    <TextBox VerticalAlignment="Center" Margin="10,0" Grid.Row="3" Grid.Column="1"/>
    <TextBox VerticalAlignment="Center" Margin="10,0" Grid.Row="4" Grid.Column="1"/>
</Grid>

Nossa janela está pronta. Agora, voltaremos ao projeto principal para chamar a janela. Para que o projeto WinForms chame a janela WPF, devemos adicionar algumas referências ao projeto. Clique com o botão direito do mouse no projeto WinForms e selecione Add Reference . Vá para a aba Projects e selecione o projeto PaginasWPF. Clique OK e selecione novamente a opção Add Reference no menu de contexto do projeto e, na aba .NET, adicione as referências aos assemblies PresentationCore, PresentationFramework e WindowsBase.

No manipulador do evento Click do botão, coloque o seguinte código:

private void button1_Click(object sender, EventArgs e)
{
    JanelaWPF jan = new JanelaWPF();
    jan.Show();
}

Inclua o namespace PaginasWPF na lista de namespaces usados. Ao executar o programa e dar um clique no botão, a janela WPF é mostrada. Note que, quando você tenta teclar algo na janela WPF, nada aparece. Isto é devido ao fato que o processamento do teclado é feito de maneiras diferentes entre as aplicações WPF e WinForms. Para solucionar este problema, devemos chamar ElementHost.EnableModelessKeyboardInterop antes de mostrar nossa janela. No manipulador do clique do botão, altere o código para:

private void button1_Click(object sender, EventArgs e)
{
    JanelaWPF jan = new JanelaWPF();
    ElementHost.EnableModelessKeyboardInterop(jan);
    jan.Show();
}

Isto deve ser feito para cada janela WPF que é chamada na aplicação WinForms. . Para chamar esta função, devemos adicionar a referência ao assembly WindowsFormsIntegration.dll e adicionar o namespace System.Windows.Forms.Integration à lista de namespaces usados. Execute o programa e verifique que a entrada de dados passa a ser processada normalmente.

arrow_px_up.gif Início da Página

Usando janelas WinForms em Aplicações WPF

Muitas vezes, você tem janelas WinForms que ainda não foram convertidas e que devem ser chamadas de sua janela principal, já convertida para WPF. O processo é muito semelhante ao que mostramos anteriormente. Crie uma nova aplicação WPF e chame-a de WPFInteropWinForms.

No XAML da janela principal, coloque o seguinte elemento:

<Button Content="Chama WinForms"
    VerticalAlignment="Center" HorizontalAlignment="Center" />

Este botão irá chamar nossa janela WinForms. Na solução, crie um novo projeto do tipo ClassLibrary e dê o nome de PaginasWinForms. Remova Class1.cs da biblioteca e adicione ao projeto uma Windows Form, dando o nome de JanelaWinForms. Na janela, coloque três TextBoxes.

Para chamar esta janela a partir do nosso projeto WPF, precisamos adicionar a referência  ao projeto WinForms e às bibliotecas. Selecione o menu Add Reference do projeto WPF e adicione o projeto PaginasWinForms às referências do projeto. Adicione ainda a referência a System.Windows.Forms ao projeto.

Volte ao Xaml e dê um duplo clique no botão (você precisa estar com o Visual Studio 2008 SP1 instalado para que isso funcione. Se não estiver, coloque Click= e adicione Add new event handler). No manipulador do evento Click, coloque o seguinte código:

private void Button_Click(object sender, RoutedEventArgs e)
{
    JanelaWinForms jan = new JanelaWinForms();
    jan.Show();
}

Desta maneira, ao clicar no botão da janela WPF, chamamos a janela WinForms.  Quando você executa o projeto e tenta mudar de uma caixa de edição a outra usando a tecla Tab, verifica que isso não é possível. Para solucionar este problema devemos acrescentar a linha

WindowsFormsHost.EnableWindowsFormsInterop();

Na inicialização da janela. Do mesmo modo que fizemos anteriormente, devemos adicionar a referência ao assembly WindowsFormsIntegration.dll e adicionar o namespace System.Windows.Forms.Integration à lista de namespaces usados. Após fazermos estas mudanças, as janelas funcionam corretamente

Como podemos ver, chamar uma janela WinForms dentro de uma aplicação WPF ou vice versa é bastante simples:  basta adicionar as referências aos assemblies requeridos pela janela (PresentationCore, PresentationFramework e WindowsBase para uma janela WPF ou System.Windows.Forms, para uma janela WinForms) e temos a possibilidade de usar tanto janelas WPF quanto WinForms em nossa aplicação. O único cuidado que devemos ter é com o teclado, chamando ElementHost.EnableModelessKeyboardInterop em aplicações WinForms que chamam janelas WPF e WindowsFormsHost.EnableWindowsFormsInterop em aplicações WPF que chamam janelas WinForms.

arrow_px_up.gif Início da Página

Incluindo controles WinForms em janelas WPF

Muitas vezes, não queremos usar uma janela, mas sim adicionar controles de outra plataforma em nossa janela.  Muitos controles WinForms não estão disponíveis para WPF (embora isto esteja sendo corrigido – há uma DataGrid e diversos novos controles para WPF em beta, veja os links no final), como por exemplo o MaskedTextBox ou o NumericUpDown. Por outro lado, podemos querer adicionar algum controle com as novas funcionalidades do WPF, como animações ou 3D em uma janela WinForms.

Para isso, devemos usar os controles de interoperabilidade introduzidos pelo .net Framework 3.5. Entretanto quando queremos usar um controle WinForms em uma página WPF, nem sempre necessitamos usar algo especial. Por exemplo, quando queremos usar um NotifyIcon ou um BackgroundWorker, podemos usá-los diretamente.  Para executar uma tarefa em plano de fundo numa página WPF, podemos fazer algo como:

{
    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
    // tarefa executada em background
}

Não há necessidade de incluir nada especial em nossa janela. Porém, quando queremos colocar algum componente visual, devemos incluir um componente WindowsFormsHost. Ele permite hospedar um controle Windows Forms. A partir do momento que colocamos um WindowsFormsHost e o controle Windows Forms, podemos configurar suas propriedades diretamente no código XAML.

Crie um novo projeto WPF e coloque na grid o seguinte código XAML:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="3*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <ListBox x:Name="listBox1" Grid.Row="1" Margin="5" />
</Grid>

Em seguida, arraste um WindowsFormsHost para a janela do designer. Isto faz com que o namespace ligado à interoperabilidade entre WnForms e WPF seja colocado :

<my:WindowsFormsHost Grid.ColumnSpan="2" Margin="5"
    Name="windowsFormsHost1"
    xmlns:my="clr-namespace:System.Windows.Forms.Integration;
       assembly=WindowsFormsIntegration" />

Podemos então colocar nosso controle  WinForms dentro do WindowsFormsHost. Antes de fazer isso, devemos declarar o namespace onde está o controle,  em nosso código XAML. Abaixo do último namespace declarado no XAML, coloque o seguinte código:

xmlns:wf="clr-namespace:System.Windows.Forms;    assembly=System.Windows.Forms"

Desta maneira, podemos incluir o componente que queremos e mudar suas propriedades no XAML:

<my:WindowsFormsHost Grid.ColumnSpan="2" Margin="5"
       Name="windowsFormsHost1"
       xmlns:my="clr-namespace:System.Windows.Forms.Integration;
           assembly=WindowsFormsIntegration">
    <wf:PropertyGrid BackColor="LightGray" PropertySort="Alphabetical"
           Dock="Fill" Text="Propriedades"/>
</my:WindowsFormsHost>

Estamos incluindo uma PropertyGrid que conterá as propriedades do item selecionado da ListBox. No arquivo de code behind, devemos incluir o seguinte código no construtor da classe:

public Window1()
{
    InitializeComponent();
    var query = from d in new DirectoryInfo(
         @"c:\program files").GetDirectories()
                from f in d.GetFiles()
                select f;
    listBox1.ItemsSource = query;
}

Estamos fazendo uma query LINQ para recuperar todos os arquivos nos subdiretórios de c:/program files. Finalmente, atribuímos o resultado desta consulta à lista de itens da ListBox. Você deve acrescentar o namespace System.IO à lista de namespaces usados.

Para configurar a PropertyGrid, podemos usar o evento SelectionChanged da ListBox.  No markup da LIstBox, acrescente o seguinte:

<ListBox x:Name="listBox1" Grid.Row="1" Margin="5"

    SelectionChanged="listBox1_SelectionChanged" />

No códe behind, insira o manipulador para o evento:

private void listBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    ((PropertyGrid)windowsFormsHost1.Child).SelectedObject = listBox1.SelectedItem;
}

Aqui estamos atribuíndo à propriedade SelectedObjec da PropertyGrid o valor do item selecionado da Listbox. Acrescente o namespace System.Windows.Forms à lista de namespaces. Ao executar o programa, as propriedades do item selecionado da ListBox são mostradas na PropertyGrid, como mostra a Figura 1.

Figura 1- PropertyGrid mostrando as propriedades do arquivo selecionado na ListBox

arrow_px_up.gif Início da Página

Incluindo controles WPF em janelas WinForms

Da mesma maneira que incluímos controles WinForms em janelas WPF, podemos incluir controles WPF em janelas WinForms. Podemos interagir com eles da mesma maneira, mudando propriedades ou manipulando seus eventos.  Para isso, devemos colocar um componente ElementHost  na janela e, dentro dele, podemos colocar um controle WPF. Se quisermos colocar mais de um controle, devemos criar um UserControl WPF e incluí-lo no ElementHost.

Crie um novo projeto WinForms e coloque na janela um ElementHost na parte superior, 4 botões  e um label na parte inferior. Mude a propriedade Anchor do ElementHost para  ancorar nos quatro lados. Mude a propriedade Text dos botões para Abre, Inicia, Pausa e Pára. Ancore os botões à esquerda e em baixo. Limpe a propriedade Text do label e mude a propriedade Font/Size para 14. Ancore o label à esquerda e abaixo.  Adicione também um OpenFileDialog e um Timer. Mude a propriedade Filter do OpenFileDialog para Arquivos wmv|*.wmv e a propridade Interval do Timer para 500. Você deve ter algo semelhante à Figura 2.

Figura 2 – Visual Studio após a inclusão dos componentes

Em seguida, acrescente um MediaElement no código da janela:

MediaElement media = new MediaElement();

Você precisa incluir o namespace System.Windows.Controls à lista de namespaces. No construtor da janela, coloque o seguinte código:

public Form1()
{
    InitializeComponent();
    media.LoadedBehavior = MediaState.Manual;
    media.MediaEnded += new RoutedEventHandler(media_MediaEnded);
    elementHost1.Child = media;
}

Desta maneira, incluímos o MediaElement na janela. Note que estamos criando um manipulador para o evento MediaEnded do MediaElement. O código deste manipulador é o seguinte:

void media_MediaEnded(object sender, RoutedEventArgs e)
{
    timer1.Enabled = false;
    label1.Text = "Terminado";
}

Quando o vídeo terminar de tocar, o Label irá mostrar a palavra Terminado. Em seguida, inclua os seguintes manipuladores para os eventos dos botões:

private void button1_Click(object sender, EventArgs e)
{
    if (openFileDialog1.ShowDialog() == DialogResult.OK)
    {
        media.Source = new Uri(openFileDialog1.FileName);
    }
}
private void button2_Click(object sender, EventArgs e)
{
    media.Play();
    timer1.Enabled = true;
}
private void button3_Click(object sender, EventArgs e)
{
    media.Pause();
    timer1.Enabled = false;
}
private void button4_Click(object sender, EventArgs e)
{
    media.Stop();
    timer1.Enabled = false;
}

No primeiro botão, selecionamos um arquivo para visualizar e configuramos a propriedade Source apontando para o arquivo selecionado. Nos outros botões, usamos os métodos Play, Pause e Stop do MediaElement para manipular o vídeo. O manipulador do evento Tick do Timer é o seguinte:

private void timer1_Tick(object sender, EventArgs e)
{
    TimeSpan posicao = media.Position;
    TimeSpan duracao = media.NaturalDuration.TimeSpan;
    label1.Text = String.Format("{0:d2}:{1:d2}/{2:d2}:{3:d2}",
      posicao.Minutes, posicao.Seconds, duracao.Minutes,
      duracao.Seconds);
}

Aqui mostramos a posição e a duração do vídeo no Label. Ao executar o programa, vemos que controlamos o MediaElement com os botões, e também podemos capturar os eventos do componente WPF como se fossem de um componente WinForms. A Figura 3 mostra o projeto em execução.

Figura 3 – Programa WinForms controlando um MediaElement

arrow_px_up.gif Início da Página

Conclusões

Como pudemos ver, a interoperabilidade entre WinForms e WPF é bastante simples e permite muita flexibilidade no que se refere a usar as duas plataformas em conjunto. Desta maneira, não precisamos converter nossas aplicações WinForms para WPF de uma vez, podemos fazer esta conversão aos poucos. Por outro lado, se algum componente não está disponível no WPF e sua elaboração é complicada, podemos usar um componente WinForms que o substitua.

DataGrid, DatePicker, Calendar, Ribbon para WPF: https://www.codeplex.com/wpf

Sobre o Autor

Bruno Sonnino é um consultor independente e desenvolvedor Windows Desktop usando as tecnologias Win32, WinForms e WPF, com mais de 20 anos de experiência.
Ele é o autor de 5 livros publicados em Português pela editora Pearson Education Brazil e escreve artigos para revistas brasileiras e americanas, tendo escrito mais de 20 utilitários para PcMag.com.
Blog: http://msmvps.com/blogs/bsonnino/default.aspx

arrow_px_up.gif Início da Página