Data Binding com WPF

Publicado em: 14 de dezembro de 2006
*

Introdução

Dentre as muitas novidades trazidas pelo WPF, uma das mais poderosas é o Data Binding (ligação de dados), que permite ligar dados provenientes de diversas fontes sem que seja necessária a utilização de código. Neste quarto artigo da série sobre WPF iremos falar sobre a ligação de propriedades dos objetos a diversas fontes de dados.

Início da páginaInício da página

Introdução ao Data Binding

O Data Binding permite ligar propriedades de um objeto a diversos tipos de fontes de dados, provenientes tanto do próprio programa, como no caso de propriedades de outros objetos, quanto de dados externos, como arquivos XML ou tabelas de bancos de dados.

Esta ligação é feita sem que seja necessária a criação de código e pode ser unidirecional (onde apenas a propriedade do objeto é alterada) ou bidirecional (onde a alteração da propriedade altera a fonte de dados).

Um exemplo de Data Binding pode ser a ligação da propriedade Text de uma TextBox a um campo de uma tabela de um banco de dados. O conteúdo da TextBox é inicializado com o conteúdo do campo e, quando alteramos o valor da TextBox, esta alteração é propagada para o banco de dados. Todo este processo é gerenciado internamente pelo WPF, sem necessidade de criação de código específico para isso.

No artigo sobre estilos já mostramos um tipo de Data Binding, o Template Binding, aonde determinadas propriedades do modelo eram ligadas às propriedades do objeto onde ele era aplicado, como quando ligamos a propriedade Fill da elipse que desenhava o botão à propriedade Background deste botão. Aqui iremos mostrar novos tipos de ligação a dados e como isso pode ser usado para tornar os programas mais poderosos.

Início da páginaInício da página

Data Binding entre objetos do programa

A maneira mais simples de ligação de dados é aquela que é feita entre propriedades de dois controles: basta dizer, na propriedade que se quer ligar, qual o controle e qual propriedade que fornecerá os dados. Por exemplo, para ligar a propriedade Value de uma ProgressBar à propriedade Value de um Slider podemos fazer algo como:

<ProgressBar Value="{Binding ElementName=Slider, Path=Value}" Width="150" Height="30"/>
			

O ElementName irá indicar o nome do controle que será a fonte de dados e o Path indica a propriedade que será usada para preencher a propriedade Value da ProgressBar. Note que o elemento é referenciado pelo seu atributo Name. Assim, o Slider precisa ser definido da seguinte maneira:

<Slider Name="Slider" Width="150" Height="30" Maximum="100" />
			

Como vimos, a sintaxe do Binding é bem simples:

<Elemento Atributo="{Tipo_de_binding Modificador1=Valor1,   Modificador2=Valor2}"/>
			

Podemos então ligar os dados de um elemento a vários outros simultaneamente. Com o código XAML a seguir, ligamos o valor do slider a uma ProgressBar, a um TextBox e ao tamanho do fonte de um Label:

<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
  <Slider Name="Slider" Width="150" Height="30" Maximum="100" />
  <ProgressBar Value="{Binding ElementName=Slider, Path=Value}" Width="150" 
      Height="30"/>
  <TextBox Text="{Binding ElementName=Slider, Path=Value}" Width="150" 
      Height="20"/>
  <Label Content="Data Binding" FontSize="{Binding ElementName=Slider, 
      Path=Value}" Width="300" Height="300"/>
</StackPanel>
			

Se você digitar este código no XAMLPad, verá que, ao mover a posição do cursor do Slider, irá alterar a posição da ProgressBar, o conteúdo da TextBox e o tamanho do fonte do Label, como mostra a Figura 1.

Figura 1 - Data Binding entre elementos de uma janela

Esta ligação é de uma via, isto é, ao alterarmos o valor do Slider, as propriedades dos outros elementos são alteradas, mas quando alteramos o conteúdo da TextBox, a posição do Slider não se altera. Para que isso aconteça, devemos especificar para a TextBox o modo de ligação TwoWay (em duas vias). Alterando a TextBox para

<TextBox Text="{Binding ElementName=Slider, Path=Value, Mode=TwoWay}" Width="150" Height="20"/>
			

Permite que, quando a caixa de texto tiver seu conteúdo alterado, o Slider seja mudado. Se você fizer esta alteração no XamlPad, verá que ao alterar o texto da TextBox e dar um <Tab> para sair da caixa, o dado se altera. Para que a alteração seja feita à medida que mudamos o conteúdo, devemos incluir o modificador UpdateSourceTrigger. Se especificarmos PropertyChanged, a alteração é imediata. O código da TextBox para isso é:

<TextBox Text="{Binding ElementName=Slider, Path=Value, Mode=TwoWay, 
			UpdateSourceTrigger=PropertyChanged}" Width="150" Height="20"/>
			
Início da páginaInício da página

Convertendo valores

O código XAML que utilizamos converte os dados entre os diversos tipos, à medida do possível: o valor da posição do Slider (um double) é convertido para string, de maneira a ser inserido na propriedade Text da TextBox . Muitas vezes, isso não é possível ou a converão não é feita da maneira desejada.

Por exemplo, se quisermos que seja mostrado apenas um inteiro arredondado na TextBox, devemos escrever um conversor de dados para ser usado no código XAML. Este conversor implementa a interface IValueConverter, que tem dois métodos, Convert e ConvertBack.

No VisualStudio, crie um novo projeto chamado Converter. No código XAML da janela principal, coloque o seguinte:

<Window x:Class="Converter.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Converter" Height="300" Width="300"
    >	
  <StackPanel>
	<Slider Name="Slider" Width="150" Height="30" Maximum="100" />
  	<ProgressBar Value="{Binding ElementName=Slider, Path=Value}" 
         Width="150" Height="30"/>
	<TextBox Text="{Binding ElementName=Slider, Path=Value, Mode=TwoWay, 
         UpdateSourceTrigger=PropertyChanged}" Width="150" Height="20"/>
	<Label Content="Data Binding" FontSize="{Binding ElementName=Slider, 
         Path=Value}" Width="300" Height="300"/>
  </StackPanel>
</Window>
			

Se você executar o projeto, irá obter uma janela que executa da mesma maneira que no XAMLPad. No arquivo Window1.xaml.cs iremos criar a classe que converte o valor double para um string com apenas a parte inteira do valor. Digite o código:

public class ConverteDoubleparaString : IValueConverter
			

e clique com o botão direito do mouse em IvalueConverter, selecionando a opção Implement Interface. São criados os corpos para os métodos da interface. O método Convert irá converter os valores recebidos do binding para ser apresntados na caixa de edição:

public object Convert(object value, Type targetType, object parameter, 
    System.Globalization.CultureInfo culture)
{
     return Math.Round((double)value).ToString();
}
			

Este método arredonda o valor recebido e converte-o para string. O método ConvertBack só é usado quando é necessário fazer a conversão TwoWay. Ele irá converter os valores digitados na caixa de edição para valores do tipo double, para ser usados no Slider:

public object ConvertBack(object value, Type targetType, object parameter, 
    System.Globalization.CultureInfo culture)
{
    int result;
    if (Int32.TryParse((string)value, out result))
       return (double)result;
    else
       return 0;
}
			

Neste caso, testamos o valor da caixa de edição, com o método TryParse. Se ela estiver preenchida com um valor inválido retornamos o valor 0.

Uma vez definida a classe de conversão, devemos especificar em nosso código XAML que ela será usada para converter os valores do DataBinding entre a caixa de edição e o Slider. Inicialmente, devemos declarar o namespace onde a classe está:

			<Window x:Class="Converter.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:src="clr-namespace:Converter" 
    Title="Converter" Height="300" Width="300"
    >
			

Definindo o alias src apontando para o namespace onde está localizada a nossa classe, criamos um recurso da janela apontando para a classe conversora:

<Window.Resources>
	<src:ConverteDoubleparaString x:Key="Conversor" />
</Window.Resources>
			

Finalmente, usamos o modificador Converter na declaração da TextBox, usando o conversor:

<TextBox Text="{Binding ElementName=Slider, Path=Value, Mode=TwoWay, 
     UpdateSourceTrigger=PropertyChanged,
     Converter={StaticResource Conversor}}" Width="150" Height="20"/>
			

Ao executarmos o nosso projeto, o DataBinding é feito da maneira que desejamos, convertendo os valores para inteiros. Como a ligação é feita em duas vias, podemos digitar na caixa de edição um número inteiro e este valor é colocado no Slider. Se digitarmos um valor inválido, o Slider é posicionado em 0.

Figura 2 - Executável com conversor de dados

Início da páginaInício da página

Binding com elementos visuais

Além de poder fazer binding com propriedades numéricas ou textuais dos objetos, é muito simples e poderoso fazer a ligação com propriedades visuais de elementos, de maneira que o elemento possa ser "projetado" sobre outro. Para isso, basta fazer o Binding da propriedade Visual de um brush (ex. Background de um elemento) com um elemento qualquer. Como exemplo, temos:

<StackPanel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
	<TextBox Text="Binding visual no WPF é muito fácil" Width="200" Height="50" Name="Texto" />
	<Canvas Width="400" Height="100">
		<Canvas.Background>
			<VisualBrush Visual="{Binding ElementName=Texto}" />
		</Canvas.Background>
	</Canvas>
</StackPanel>
	

Neste caso, o Canvas vai servir como uma "lupa" da TextBox, projetando-a com tamanho duas vezes maior:

Figura 3 - Data Binding com elementos visuais

Início da páginaInício da página

DataBinding com classes do programa

Uma outra maneira de se fazer DataBinding com WPF é usando as classes definidas no código do programa ou mesmo com classes do .net Framework. Para isso, basta que sejam definidas propriedades nas classes e elas podem ser usadas como fontes para os dados dos controles WPF. Um exemplo simples, usando apenas XAML, permite listar as fontes do sistema em uma ListBox, mostrando um exemplo numa TextBox ao lado.

Para isso, devemos declarar o namespace <formatting role="bold">System.Collections</formatting> no assembly <formatting role="bold">mscorlib</formatting>. Em seguida, definimos duas colunas numa Grid e colocamos uma ListBox na primeira coluna e uma TextBox na segunda:

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:src="clr-namespace:System.Collections;assembly=mscorlib">
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="50*"/>
    <ColumnDefinition Width="50*"/>
  </Grid.ColumnDefinitions>

  <ListBox Grid.Row="0" Grid.Column="0" x:Name="ListaFontes" />
  <TextBox Grid.Row="0" Grid.Column="1" />
</Grid>
	

Para a ListBox apresentar a lista de fontes do sistema, basta fazer um DataBinding com a propriedade <formatting role="bold">SystemFontFamilies</formatting> da classe <formatting role="bold">Fonts</formatting> com a propriedade <formatting role="bold">ItemsSource</formatting>:

<ListBox Grid.Row="0" Grid.Column="0" x:Name="ListaFontes" 
 ItemsSource="{Binding Source={x:Static Fonts.SystemFontFamilies}}" />
	

Em seguida, basta ligar o nome da fonte da TextBox ao valor do item selecionado na ListBox:

  <TextBox Grid.Row="0" Grid.Column="1" FontSize="24" 
         TextAlignment="Center" TextWrapping="Wrap" 
         Text="The quick brown fox jumps over the lazy dog" 
         FontFamily="{Binding SelectedValue, ElementName=ListaFontes}"/>
	

Com isto, está pronta nossa lista de fonts, com exemplos e sem a necessidade de escrever código, como mostra a Figura 4.

Figura 4 - XAMLPad com lista de fontes

Porém esta não é a única maneira de fazer DataBinding com código externo: podemos também ligar os dados às classes do programa. Para isto, basta definir uma propriedade na classe e ligar os dados a ela. Para exemplificar isto, iremos criar uma lista de processos em execução na máquina. Crie um novo projeto WPF no Visual Studio e adicione uma ListBox na janela:

<Window x:Class="ListaProcessos.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Lista Processos" Height="300" Width="300"
    >
    <Grid>
	<ListBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/> 
    </Grid>
</Window>
	

Em seguida, em Window1.xaml.cs, iremos criar a classe <formatting role="bold">ProcList</formatting>, com uma propriedade somente leitura chamada <formatting role="bold">Processos</formatting>, com tipo vetor de <formatting role="bold">Process</formatting>. A classe <formatting role="bold">Process</formatting> está definida no namespace System.Diagnostics, que deve ser incluído na lista de namespaces utilizados:

public class ProcList
{
    public static Process[] Processos
    {
        get { return Process.GetProcesses(); }
    }
}
	

O passo seguinte é fazer a ligação dos dados da classe aos itens da ListBox:

<ListBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
   ItemsSource="{Binding Source={x:Static src:ProcList.Processos}}"/>
	

Se você executar este projeto, ele irá mostrar uma ListBox com uma ScrollBar e todos os itens em branco. Isto é devido ao fato que cada item da ListBox é uma classe do tipo <formatting role="bold">Process</formatting> e o WPF não encontra uma conversão para mostrar os itens. Poderíamos escrever um conversor para esta classe, porém uma maneira mais simples de fazer isso é criar um modelo de apresentação dos itens:

<ListBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
     ItemsSource="{Binding Source={x:Static src:ProcList.Processos}}">
  <ListBox.ItemTemplate>
    <DataTemplate>
	<StackPanel>
	  <TextBlock Text="{Binding Path=MainModule.ModuleName}" />
	  <TextBlock Text="{Binding Path=MainModule.FileName}" />
	  <TextBlock Text="{Binding Path=MainWindowTitle}" />
	</StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>
	

Este modelo faz que cada item da ListBox ocupe três linhas: a primeira mostra o nome do módulo, a segunda, o nome do executável principal e a terceira o título da janela principal. Com isto, podemos executar nosso projeto, que lista os processos em execução na máquina.

Figura 5 - Lista de processos em execução

Início da páginaInício da página

DataBinding com fontes externas de dados

Além de fazer ligação com fontes internas de dados, o WPF permite ligar as propriedades dos objetos a fontes externas de dados. Por exemplo, podemos criar um leitor de RSS sem a necessidade de criar código para isso.

No XAMLPad, insira uma Grid com duas linhas e duas colunas. Na primeira linha coloque um Label e na segunda linha, uma ListBox e uma TextBox:

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid.RowDefinitions>
    <RowDefinition Height="40px"/>
    <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="50*"/>
    <ColumnDefinition Width="50*"/>
  </Grid.ColumnDefinitions>
  <Label Grid.Row="0" Grid.ColumnSpan="2" Margin="5" FontSize="18" />
  <ListBox Grid.Row="1" Grid.Column="0" Margin="5" x:Name="ListaFeeds"/>
  <TextBox Grid.Row="1" Grid.Column="1" Margin="5" TextWrapping="Wrap"/>
</Grid>
			

Em seguida, devemos definir nossa fonte de dados, usando um XmlDataProvider:

<Grid.Resources>
    <XmlDataProvider x:Key="RSSFeed"
       Source="http://www.virtualdreams.com.br/blog/feed" />
  </Grid.Resources>
			

O passo seguinte é ligar o conteúdo do Label à descrição do RSS:

  <Label Grid.Row="0" Grid.ColumnSpan="2" Margin="5" 
     FontSize="18" Content="{Binding Source={StaticResource RSSFeed},
     XPath=/rss/channel/title}" />
			

Ligamos a propriedade ItemsSource da ListBox aos itens do RSS:

<ListBox Grid.Row="1" Grid.Column="0" Margin="5" x:Name="ListaFeeds"
     ItemsSource="{Binding Source={StaticResource RSSFeed}, 
     XPath=/rss/channel/item}"/>
			

Ao fazermos isso, notamos que todo o item é trazido, mas queremos apenas o título de cada item: a descrição será mostrada na TextBox. Para mostrar apenas o título, criamos um modelo de apresentação dos itens:

<ListBox Grid.Row="1" Grid.Column="0" Margin="5" x:Name="ListaFeeds"
     ItemsSource="{Binding Source={StaticResource RSSFeed}, 
     XPath=/rss/channel/item}">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <TextBlock Text="{Binding XPath=title}" />
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
			

Finalmente, ligamos os dados à TextBox. Devemos dizer que o contexto dos dados refere-se ao item selecionado da TextBox e que o texto virá da descrição do item:

<TextBox Grid.Row="1" Grid.Column="1" Margin="5"  TextWrapping="Wrap"
     DataContext="{Binding ElementName=ListaFeeds, Path=SelectedItem}"
     Text="{Binding XPath=description}"/>
			

O código final é o seguinte, e o resultado está mostrado na Figura 6:

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid.RowDefinitions>
    <RowDefinition Height="40px"/>
    <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="50*"/>
    <ColumnDefinition Width="50*"/>
  </Grid.ColumnDefinitions>
  <Grid.Resources>
    <XmlDataProvider x:Key="RSSFeed" 
        Source="http://www.virtualdreams.com.br/blog/feed" />
  </Grid.Resources>
  <Label Grid.Row="0" Grid.ColumnSpan="2" Margin="5" 
     FontSize="18" Content="{Binding Source={StaticResource RSSFeed}, 
     XPath=/rss/channel/title}" />
  <ListBox Grid.Row="1" Grid.Column="0" Margin="5" x:Name="ListaFeeds"
     ItemsSource="{Binding Source={StaticResource RSSFeed}, 
     XPath=/rss/channel/item}">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <TextBlock Text="{Binding XPath=title}" />
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
  <TextBox Grid.Row="1" Grid.Column="1" Margin="5" TextWrapping="Wrap" 
     DataContext="{Binding ElementName=ListaFeeds, Path=SelectedItem}"
     Text="{Binding XPath=description}"/>
</Grid>
			

Figura 6 - Leitor de RSS

Início da páginaInício da página

Conclusões

Como pudemos ver, o WPF traz muitas facilidades na ligação de dados. Podemos ligar dados de diversas fontes às propriedades dos objetos, sem que haja necessidade de criar código para isso, podendo, inclusive, fazer que os dados sejam alterados à medida que se muda algo na interface.

No próximo artigo da série iremos mostrar o desenho de gráficos com WPF, até lá!


Início da páginaInício da página