WPF fornece uma maneira simples e poderosa para atualização automática de dados entre o modelo de negócio e interface do usuário. Esse mecanismo é chamado de ligação de dados. Sempre quando há alteração em seus modelos de negócios ele reflete automaticamente as atualizações de interface do usuário e vice-versa. Este é o método preferido na WPF para colocar os dados para a interface do utilizador.

Ver também: Introdução ao Data Binding no WPF - Revista Easy Net 16

Ligação de dados pode ser unidirecional (fonte -> destino ou destino <- fonte) ou bidirecional (fonte <-> alvo).

A fonte de uma ligação de dados pode ser uma propriedade. NET normal ou um DependencyProperty. A propriedade de destino da ligação deve ser um DependencyProperty, como mostra a Figura 1.

Esquema de funcionamento do DataBinding
Figura 1. Esquema de funcionamento do DataBinding

Para fazer corretamente databinding, os dois lados de uma ligação devem ter uma forma de notificação de alteração que notifica a ligação quando é para atualizar o valor de destino. Nas propriedades normais do .NET isto é feito usando evento PropertyChanged da interface INotifyPropertyChanged. Em DependencyProperties é feito pelo retorno da chamada PropertyChanged do metadados da propriedade.

Ligação de dados é normalmente feita em XAML usando a extensão de marcação {Binding}. O exemplo da Listagem 1 mostra uma ligação simples entre o texto de uma TextBox e um Label, que reflete o valor digitado.

<stackpanel>
    <textbox x:name="txtSeuTxt" />
    <label content="{binding text, elementname= txtseutxt, 
                     updatesourcetrigger=propertychanged}" />
</stackpanel>
Listagem 1. Ligação entre TextBox e Label

DataContext

Cada controle WPF que é herdado de FrameworkElement tem uma propriedade DataContext. Esta propriedade é destinada a ser usada para carregar o conteúdo do seu objeto.

A propriedade DataContext pode ser visualizada por elementos filhos do objeto pai. Assim, você pode definir o DataContext em um contêiner de layout superior e seu valor é herdado a todos os elementos filhos. Isto é muito útil se você quiser construir um formulário que está vinculado a mesma BindingModel, como mostra a Listagem 2.


<stackpanel datacontext="{staticresource listapessoa}">
    <textbox text="{binding primeironome}"/>
      <textbox text="{binding sobrenome}"/>
      <textbox text="{binding endereco}"/>
      <textbox text="{binding numero}"/>
      <textbox text="{binding bairro}"/>
      <textbox text="{binding cidade}"/>
      <textbox text="{binding estado}"/>
  
</stackpanel>
Listagem 2. Propriedade DataContext do contêiner herdada pelos elementos filhos

ValueConverters

Se você deseja ligar duas propriedades de diferentes tipos juntos, você precisa usar um ValueConverter. O ValueConverter converte o valor de um tipo de fonte para um tipo de destino e vice-versa. WPF já inclui alguns conversores de valor, mas na maioria dos casos você terá que escrever o seu próprio conversor de tipo, implementando a interface IValueConverter.

Um exemplo típico é o de vincular um membro booleano (Boolean) para a propriedade de visibilidade (Visibility). Uma vez que a visibilidade é um valor de enumeração que pode ser Visible, Collapsed ou Hidden, para isso você terá que usar um conversor de valor, como na Listagem 3.

<stackpanel>    
     <stackpanel.resources>
          <booleantovisibilityconverter x:key="booltovis" />
      </stackpanel.resources>
    
      <checkbox x:name="chkshowdetails" content="show details" />
      <stackpanel x:name="detailspanel"
                  visibility="{binding ischecked, elementname=chkshowdetails, 
                               converter={staticresource booltovis}}">
      </stackpanel>
  </stackpanel>
Listagem 3. Usando conversor de valor booleano

O exemplo da Listagem 4 mostra um conversor que converte um simples boleano (Boolean) a uma propriedade de visibilidade (Visibility). Note-se que tal um conversor já faz parte do quadro. NET.


public class BooleanToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is Boolean)
        {
            return ((bool)value) ? Visibility.Visible : Visibility.Collapsed;
        }
 
        return value;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, 
                              CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
Listagem 4. Conversor de Boolean para Visibility
Nota: Você pode derivar o seu conversor de valor de MarkupExtension e retornar a sua própria instância na substituição ProviderValue. Então você pode usá-lo diretamente, sem referência a ele a partir dos recursos.
Nota: Quando você receber o erro “Não existem construtores sem parâmetros”, você precisa adicionar um construtor sem parâmetros, ele é necessário apenas para uso do designer WPF que por não ter esse construtor pode gerar erro em tempo de execução.

Uma forma muito comum e popular para sincronizar dados entre o modelo de dados e a interface do usuário no WPF é usar DataBinding. O valor do modelo é transferido para interface do usuário, uma vez, quando a ligação é feita. Mas para cada alteração posterior, o modelo deve notificar a propriedade que está bindeada para transferir o valor novamente. Isto é feito através da implementação da interface INotifyPropertyChanged no BindingModel (Classe que representa o modelo de negócio).

No setter de cada propriedade acoplada deve haver algum código para notificar o evento PropertyChanged, passando o nome da propriedade modificada como uma string. Isso tem algumas desvantagens:

  • Você não pode usar Auto-propriedades
  • O nome da propriedade é passada como uma string. Isso pode gerar erros se você esquecer de alterar a string de notificação quando um nome de propriedade é alterado.

Propriedades de Notificação

Uma forma básica de implementar Notificação pode ser vista na Listagem 5.

private string _nome;
 
public string Nome
{
   get { return _nome; }
   set 
   {
      _nome = value;
      PropertyChanged("Nome");
   }
}
 
private void PropertyChanged(string prop)
{
   if( PropertyChanged != null )
   {
      PropertyChanged(this, new PropertyChangedEventArgs(prop);
   }
}
Listagem 5. Implementando notificação de forma básica

Já na Listagem 6 temos uma forma mais elegante de implementar Notificação. A implementação básica requer duas linhas de código no setter e o nome da propriedade é passada como uma string, o que não é muito eficaz. Uma solução mais elegante é usar o poder de árvores de expressão para obter o nome da propriedade de uma expressão lambda.


private string _name;
public string Name
{
   get { return _name; }
   set { PropertyChanged.ChangeAndNotify(ref _name, value, () => Name); }
}


public static bool ChangeAndNotify<T>(this PropertyChangedEventHandler handler, 
     ref T field, T value, Expression<Func<T>> memberExpression)
{
    if (memberExpression == null)
    {
        throw new ArgumentNullException("memberExpression");
    }
    var body = memberExpression.Body as MemberExpression;
    if (body == null)
    {
        throw new ArgumentException("Lambda must return a property.");
    }
    if (EqualityComparer<T>.Default.Equals(field, value))
    {
        return false;
    }
 
    var vmExpression = body.Expression as ConstantExpression;
    if (vmExpression != null)
    {
        LambdaExpression lambda = Expression.Lambda(vmExpression);
        Delegate vmFunc = lambda.Compile();
        object sender = vmFunc.DynamicInvoke();
 
        if( handler != null)
        {
            handler(sender, new PropertyChangedEventArgs(body.Member.Name));
        }
    }
 
    field = value;
    return true;
}
Listagem 6. Implementando notificação com árvores de expressão

Claro que poderemos obter o mesmo resultado usando maneiras mais simples porém temos que tomar muito mais cuidado ao modificar propriedades (ex: mudar o nome de _name para _nome).

Concluímos aqui este artigo, onde vimos os recursos do WPF para ligação de dados entre elementos da interface e a lógica de negócios.

Até a próxima.