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
Introdução
Usando
janelas WPF em aplicações WinForms
Usando
janelas Winforms em aplicações WPF
Incluindo controles Winforms em aplicações
WPF
Incluindo controles WPF em aplicações
Winforms
Conclusões
Links
úteis
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.
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.
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.
.jpg)
Figura
1- PropertyGrid mostrando as propriedades do arquivo selecionado na ListBox
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.
.jpg)
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.
.jpg)
Figura
3 – Programa WinForms controlando um MediaElement
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.
Links úteis
DataGrid, DatePicker, Calendar, Ribbon para
WPF: http://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
Início da
Página