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.
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>
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>
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>
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();
}
}
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);
}
}
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;
}
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.