Eu já tratei do assunto databinding em
aplicações SilverLight em diversos antigos
anteriores e hoje vou falar um pouco sobre um tópico muito
importante relacionado ao assunto: a validação de dados.
A validação de dados permite que
as informações gravadas no seu banco de dados tenham integridade
e confiabilidade e, dessa forma, não pode ser desprezada. Se
você não realiza validação de dados em suas aplicações, elas
podem aceitar como informações qualquer entrada que o usuário
informar, e isso não é aconselhável.
Neste artigo, eu mostro como você
pode implementar a validação e como capturar erros de
validação que ocorrem em suas aplicações SilverLight e como
fornecer ao usuário mensagens indicativas desses erros.
Para
começar, um pouco de teoria
Um dos caminhos que podemos seguir
para realizar a validação em aplicações que usam o databinding
é anexar manipuladores ao evento BindingValidationError
do controle SilverLight a ser validado e assim
ter certeza de que a vinculação está definida para levantar
eventos em exceções ocorridas nas validações.
A classe Binding está no namespace System.Windows.Data e é responsável por manter a comunicação entre a origem e o destino,
expondo uma série de propriedades que nos permite customizar o comportamento dessa comunicação. Entre as principais
propriedades, temos:
- ElementName:
define o nome do elemento que servirá como fonte. Utilize esta propriedade
quando desejar preencher uma outra propriedade com o valor de um controle
do WPF. - Mode: determina a
direção das informações. - NotifyOnSourceUpdated:
valor boleano indicando se o evento SourceUpdated é disparado
quando alguma atualização na fonte das informações ocorrer. - NotifyOnTargetUpdated:
valor boleano indicando se o evento SourceUpdated é disparado
quando alguma atualização no destino das informações ocorrer. - Path: especifica o
nome da propriedade que será exibida. - RelativeSource:
especifica uma fonte de forma relativa à posição do objeto atual. - Source: define o
nome do objeto que servirá como fonte. Utilize esta propriedade quando
desejar preencher com uma instância de um objeto. - XPath: a mesma
finalidade da propriedade Path, mas define uma expressão
XPath quando a fonte de informações for um arquivo Xml.
O subsistema de vinculação de
dados do SilverLight oferece suporte embutido
para a notificação de erros de validação. Para ativar esse
suporte, a propriedade Binding.ValidatesOnExceptions precisa
ser definida como true na vinculação, de
maneira a permitir que o framework possa capturar
qualquer exceção disparada durante a criação/definição
de uma propriedade em uma fonte de dados ou durante uma
conversão entre tipos e possa propagá-la para o seu código de
validação de erros.
A maioria dos controles na class
library que podem ser utilizados em vinculações no modo two-way
fornecem um interface embutida para exibir erros de validações
de vinculação ao usuário. Essa interface geralmente fornece um
pequeno ícone de erro exibido no controle, que por sua vez exibe
uma mensagem de erro em uma tooltip ao lado do controle.
A mensagem de erro exibida é o
valor da propriedade Exception.Message da
exceção disparada sendo que uma vez que o erro é corrigido o
controle remove de forma automática a interface de erros.
Obtendo
a informação do Erro
Em alguns casos, pode não ser
necessário simplesmente exibir a mensagem de erro e você pode
querer acessar via código a informação sobre o erro. Para
permitir esse recurso, a classe FrameworkElement (e
por herança cada controle) pode disparar o evento BindingValidationError
sempre que uma exceção se propagar como um erro de validação
ou quando um erro existente de validação for removido.
Para instruir o subsistema de
vinculação a levantar esse evento, você precisa definir a
propriedade Binding.NotifyOnValidation como true
na vinculação.
Se você tratar o evento BindingValidationError, poderá acessar a informação detalhada do erro
através do argumento do evento de tipo ValidationErrorEventArgs.
A propriedade
ValidationErrorEventArgs.Action, do tipo ValidationErrorEventAction,
possui dois valores possíveis:
- ValidationErroEventAction.Added
– indicando que um erro de validação ocorreu; - ValidationErroEventAction.Removed
– indicando que um erro foi corrigido;
A propriedade ValidationErrorEventArgs.Exception
lhe dá acesso a exceção atual que casou o erro de
validação.
Obtendo
um sumário dos erros durante a validação
Em muitas aplicações, é comum
exibir um sumário de erros que o usuário pode ter feito na
realização da entrada dos dados. Um sumário exibe os campos
onde os erros foram feitos, a natureza do erro e, em alguns
casos, ele também inclui uma navegação automática para
navegar para o campo. Essa característica também existe no
mecanismo de validação de vinculação do SilverLight e está
habilitada através do controle System.Windows.Controls.ValidationSummary
e está relacionada em classes no assembly System.Windows.Controls.Data.Input.
Uma vez que você coloca um controle
ValidationSummary em sua página, o subsistema
de vinculação automaticamente sabe como preenchê-lo com as
entradas de erros e erros de vinculação que ocorrerem. A seguir,
temos um exemplo de declaração XAML que define
este recurso:
Ex:
<input:ValidationSummary
/>
Observe também que os erros de
validação preenchem a árvore visual por porte do subsistema de
vinculação. Isso significa que o controle ValidationSummary
pode ser colocado em qualquer lugar da sua página.
Nota: As interfaces de usuário para o sumário de validação e o erro padrão podem ser customizados usando o controle de templates.
Antes de partir para a parte prática, quero falar de dois conceitos importantes que eu estou usando na aplicação e que você deve conhecer:
01. Quando você tem elementos vinculados a dados em sua interface em uma aplicação SilverLight, como podemos ativar as notificações de alteração e atualização automáticas da interface quando o aplicativo altera dados vinculados?
Basta implementar a interface System.ComponentModel.INotifyPropertyChanged em seus tipos de dados e a interface System.Collections.Specialized.INotifyCollectionChanged nos tipos da sua coleção. Depois, basta disparar os eventos definidos nessas interfaces a partir da implementação dos tipos para fornecer as notificações de mudança.
Obs: Garanta que a propriedade Mode, para cada vinculação de dados, esteja definida como BindingMode.OneWay ou BindingMode.TwoWay, para permitir atualização automática da interface do usuário.
02. Como fazer para converter um valor de origem para um tipo qualquer diferente ou valor diferente adequado que deverá ser exibido na interface do usuário em uma aplicação SilverLight em dados vinculados a uma fonte de dados?
Basta implementar a interface System.Windows.Data.IValueConverter para criar um tipo de conversor de valor e associá-lo à vinculação de dados para converter o valor de forma correta.
Este exemplo implementa a interface INotifyPropertyChanged para fornecer as notificações de mudanças ocorridas na aplicação e na interface IValueConverter para realizar a conversão de tipos.
Da teoria à prática
Depois de toda essa teoria, que tal criar algo prático para mostrar como tudo isso funciona?
Vamos criar um exemplo bem simples, no qual iremos definir um modelo de dados nas classes Contato e Endereco para exibir nome, email, telefone e endereço.
A exibição do nome e email será feita no controle ListBox e os detalhes em grid usando os controles TextBox e TextBlock.
Abaixo temos o leiaute da aplicação SilverLight em tempo de projeto:

Executando a aplicação, teremos:

Então vamos construir o projeto…
Eu vou usar como
ferramenta o
Visual Web Developer 2010 Express Edition
(VWD 2010),
que é gratuito, assim todos poderão acompanhar o exemplo.
Abra o VWD 2010 Express e clique em File -> New Project.
Selecione Visual C# -> SilverLight e o template SilverLight
Application e informe o nome Validacao_SilverLight e clique em OK.

A seguir, desmarque a opção Host the SilverLight application in a new Web Site
e selecione a versão SilverLight 4.
Dessa forma, teremos um projeto bem simples que será executado pelo
SilverLight Developer Runtime.

Será criado o projeto SilverLight com a estrutura conforme mostra a figura da
janela Solution Explorer:

No menu Project clique em Add Reference e, na janela Add Reference,
selecione a aba .NET e escolha System.Windows.Controls.Data.Input
e clique em OK.
Vamos agora criar um novo arquivo do tipo Class chamado DataClasses.cs
(o nome fica seu critério) em que iremos definir duas classes:
- A classe Contato e seus membros : Nome , Email, Telefone,
Endereco. - A classe Endereco e seus membros : Rua, Estado, Cidade, Cep.
Nessas classes, teremos a implementação da interface
INotifyPropertyChanged, na qual disparamos
os eventos definidos nessas interface a partir da implementação dos tipos para
fornecer as notificações de mudança conforme explicado na teoria.
Nessas classes, definimos as validações que desejamos
realizar. Para o exemplo temos definidos:
- O Telefone deve ter 1 ter dois caracteres alfanuméricos;
No menu Project, clique em Add Class e informe o nome
DataClasses.cs e clique em Add;
A seguir, inclua o seguinte código neste arquivo:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
namespace Validacao_SilverLight
{
public class Contato : INotifyPropertyChanged
{
// implementação da interface INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
public Contato()
{
}
private string _Nome;
public string Nome
{
get { return _Nome; }
set
{
string ValorAnterior = _Nome;
if (ValorAnterior != value)
{
_Nome = value;
RaisePropertyChanged(new PropertyChangedEventArgs("Nome"));
}
}
}//_Nome
private string _Email;
public string Email
{
get { return _Email; }
set
{
string ValorAnterior = _Email;
if (ValorAnterior != value)
{
_Email = value;
RaisePropertyChanged(new PropertyChangedEventArgs("Email"));
}
}
}//_Email
private string _Telefone;
public string Telefone
{
get { return _Telefone; }
set
{
string ValorAnterior = _Telefone;
if (value.Length != 10)
throw new Exception("O númreo do telefone deve ter 10 dígitos");
try
{
Convert.ToInt64(value);
}
catch
{
throw new Exception("O número do telefone deve ter 10 dígitos");
}
if (ValorAnterior != value)
{
_Telefone = value;
RaisePropertyChanged(new PropertyChangedEventArgs("Telefone"));
}
}
}//_Telefone
private Endereco _Endereco;
public Endereco Endereco
{
get { return _Endereco; }
set
{
Endereco ValorAnterior = _Endereco;
if (ValorAnterior != value)
{
_Endereco = value;
RaisePropertyChanged(new PropertyChangedEventArgs("Endereco"));
}
}
}//_Endereco
private bool _InError = default(bool);
public bool InError
{
get
{
return _InError;
}
set
{
if (value != _InError)
{
_InError = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("InError"));
}
}
}
}//Contato
public class Endereco : INotifyPropertyChanged
{
private static List<string> EstadoLista =
new List<string>(){ "AC","AL","AP","AM","BA","CE","DF","ES","GO","MA","MT",
"MS","MG","PA","PB","PR","PE","PI","RJ","RN","RS","RO","RR","SC","SP","SE",
"TO"};
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
private string _Rua;
public string Rua
{
get { return _Rua; }
set
{
string ValorAnterior = _Rua;
if (ValorAnterior != value)
{
_Rua = value;
RaisePropertyChanged(new PropertyChangedEventArgs("Rua"));
}
}
}//_Rua
private string _Cidade;
public string Cidade
{
get { return _Cidade; }
set
{
string ValorAnterior = _Cidade;
if (ValorAnterior != value)
{
_Cidade = value;
RaisePropertyChanged(new PropertyChangedEventArgs("Cidade"));
}
}
}//_Cidade
private string _Estado;
public string Estado
{
get { return _Estado; }
set
{
string ValorAnterior = _Estado;
//estado deve ter 2 caracteres
if (EstadoLista.Contains(value) == false)
throw new Exception(
"A sigla do estado deve possuir duas Letras"
);
if (ValorAnterior != value)
{
_Estado = value;
RaisePropertyChanged(new PropertyChangedEventArgs("Estado"));
}
}
}//_Estado
private string _Cep;
public string Cep
{
get { return _Cep; }
set
{
string ValorAnterior = _Cep;
//cep deve ter 8 caracteres
if (value.Length != 8)
{
throw new Exception("O Cep deve conter 8 dígitos numéricos");
}
try
{
Convert.ToInt32(value);
}
catch
{
throw new Exception("O Cep deve conter 8 dígitos numéricos");
}
if (ValorAnterior != value)
{
_Cep = value;
RaisePropertyChanged(new PropertyChangedEventArgs("Cep"));
}
}
}//_Cep
}//Endereco
}
Conforme o código acima, já temos as nossas classes Contato e Endereco,
que representam o nosso modelo de dados e onde definimos os membros e a
validação que iremos realizar na aplicação.
Precisamos criar também o arquivo a classe BoolToVisibilityConverter.cs, no qual
iremos implementar a interface IValueConverter
para fornecer as notificações de mudanças ocorridas na aplicação e na interface
IValueConverter para
realizar a conversão de tipos.
No menu
Project, clique em
Add Class e, a seguir, informe o
nome BoolToVisibilityConverter.cs e clique no botão
Add.
Obs: Não vou entrar em
detalhes nos conversores, irei apenas usá-los no projeto. Se você quiser saber
mais sobre eles, consulte o meu artigo:
http://www.macoratti.net/10/03/wpf_cvd1.htm
Inclua o seguinte código neste arquivo:
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
namespace Validacao_SilverLight
{
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
//verifica se os tipos de parametros estão em conformidade
if (!(value is bool) || targetType != typeof(Visibility))
return null;
return (bool)value == true ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Agora podemos definir o código XAML referente à
interface com o usuário no arquivo MainPage.xaml conforme abaixo:
<UserControl x:Class="Validacao_SilverLight.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Validacao_SilverLight"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:input=
"clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input"
mc:Ignorable="d"
Width="405"
Height="462">
<UserControl.Resources>
<local:BoolToVisibilityConverter x:Key="REF_BoolToVisibilityConverter" />
<DataTemplate x:Key="dtContato">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Nome}"/>
<TextBlock Text="{Binding Email}"
Grid.Column="1"
Grid.Row="0"
Margin="5,0,0,0" />
<TextBlock Text=" -> Error!!" Foreground="Red"
Visibility=
"{Binding InError, Converter={StaticResource REF_BoolToVisibilityConverter}}"
Grid.Column="2" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot"
Background="White"
Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
x:Name="lbx_Contatos"
ItemTemplate="{StaticResource dtContato}"
SelectionChanged="lbx_Contatos_SelectionChanged" />
<Grid x:Name="grid_NewButton"
Margin="0,2,0,0"
Grid.Row="1"
HorizontalAlignment="Right">
<Button x:Name="btn_Novo"
Click="btn_Novo_Click"
Content="Novo Contato" />
</Grid>
<input:ValidationSummary Grid.Row="2" Margin="0,10,0,5"/>
<Border Grid.Row="3"
Visibility="Collapsed"
x:Name="border_ContatoForm"
Margin="0,2,0,0"
BorderBrush="Black"
BorderThickness="1"
Padding="1,1,1,1">
<Grid x:Name="grid_ContatoForm">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.142*" />
<ColumnDefinition Width="0.379*" />
<ColumnDefinition Width="0.1*" />
<ColumnDefinition Width="0.097*" />
<ColumnDefinition Width="0.082*" />
<ColumnDefinition Width="0.2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.10*" />
<RowDefinition Height="0.15*" />
<RowDefinition Height="0.15*" />
<RowDefinition Height="0.15*" />
<RowDefinition Height="0.45*" />
</Grid.RowDefinitions>
<TextBox HorizontalAlignment="Stretch"
Margin="1,1,1,1"
x:Name="tbxNome"
VerticalAlignment="Stretch"
Text="{Binding Nome, Mode=TwoWay}"
Grid.Row="1"
Width="Auto"
Grid.RowSpan="1"
Grid.ColumnSpan="2"
Grid.Column="1" />
<TextBox HorizontalAlignment="Stretch"
Margin="1,1,1,1"
x:Name="tbxEmail"
VerticalAlignment="Stretch"
Text="{Binding Email, Mode=TwoWay}"
Grid.Row="1"
Grid.Column="3"
Width="Auto"
Grid.RowSpan="1"
Grid.ColumnSpan="3" />
<TextBlock HorizontalAlignment="Stretch"
Margin="1,1,1,1"
VerticalAlignment="Stretch"
Text="Email"
TextWrapping="Wrap"
Grid.RowSpan="1"
Grid.Column="4"
Grid.ColumnSpan="2"
Height="Auto"
Width="Auto" />
<TextBlock HorizontalAlignment="Center"
Margin="1,1,1,1"
VerticalAlignment="Stretch"
Text="Nome"
TextWrapping="Wrap"
Grid.RowSpan="1"
Grid.Row="1"
Height="Auto"
Width="Auto" />
<TextBlock HorizontalAlignment="Center"
Margin="1,1,1,1"
VerticalAlignment="Stretch"
Text="Rua"
TextWrapping="Wrap"
Grid.Row="2"
Width="Auto" />
<TextBox HorizontalAlignment="Stretch"
x:Name="tbxRua"
VerticalAlignment="Stretch"
Text="{Binding Endereco.Rua, Mode=TwoWay}"
Grid.Row="2"
Margin="1,1,1,1"
Grid.Column="1"
Grid.ColumnSpan="5"
Width="Auto" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Text="Cidade"
TextWrapping="Wrap"
Margin="1,1,1,1"
Grid.Row="3" />
<TextBlock Text="UF"
Margin="1,1,1,1"
TextWrapping="Wrap"
Grid.Column="2"
Grid.Row="3"
HorizontalAlignment="Center" />
<TextBlock Text="Cep"
Margin="1,1,1,1"
TextWrapping="Wrap"
Grid.Column="4"
Grid.Row="3"
HorizontalAlignment="Center" />
<TextBox HorizontalAlignment="Stretch"
x:Name="tbxCidade"
Margin="1,1,1,1"
VerticalAlignment="Stretch"
Text="{Binding Endereco.Cidade, Mode=TwoWay}"
Grid.Row="3"
Grid.Column="1" />
<TextBox Background="Transparent"
Grid.Column="3"
Margin="1,1,1,1"
Grid.Row="3"
Text="{Binding Endereco.Estado, Mode=TwoWay,
ValidatesOnExceptions=True,NotifyOnValidationError=True}"
x:Name="tbxEstado">
</TextBox>
<TextBox Background="Transparent"
Grid.Column="5"
Grid.Row="3"
Margin="1,1,1,1"
Text="{Binding Endereco.Cep, Mode=TwoWay ,
ValidatesOnExceptions=True,NotifyOnValidationError=True}"
x:Name="tbxCep" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Stretch"
Text="Fone"
Margin="1,1,1,1"
TextWrapping="Wrap"
Grid.Row="4" />
<TextBox Grid.Column="1"
Grid.Row="4"
Margin="1,1,1,1"
Text="{Binding Telefone, Mode=TwoWay ,
ValidatesOnExceptions=True,NotifyOnValidationError=True}"
x:Name="tbxTelefone" />
<Button Grid.Column="5"
Margin="1,1,1,1"
Grid.Row="4"
Height="30.911"
VerticalAlignment="Top"
Content="Fechar"
x:Name="btnFechar"
Click="btnFechar_Click" />
</Grid>
</Border>
</Grid>
</UserControl>
Nesse código, estamos usando o controle
ValidationSummary e definindo nas vinculações; nelas, estamos efetuando a propriedade Binding.ValidatesOnExceptions que precisa
ser definida como true na vinculação, de
maneira a permitir que o framework possa capturar
qualquer exceção disparada durante a criação/definição
de uma propriedade em uma fonte de dados ou durante uma
conversão entre tipos e possa propagá-la para o seu código de
validação de erros.
Executando o projeto, ao tentar informar valores não
válidos nos campos que estamos validando, iremos obter:

No exemplo, podemos ver o controle ValidationSummary preenchido com os
erros definidos na validação.
Eu sei que à primeira vista pode parecer um tanto complexo efetuar a
validação no SilverLight, mas com o tempo e com a prática o processo passa a ser
incorporado e quem sabe também nas próximas versões o mesmo seja mais
simplificado.
Lembrando que existem outras maneiras de realizar a validação, como usar as
interfaces IDataError e INotifyDataErrorInfo, mas isso é assunto para
outro artigo.
Nota: Muitos dos conceitos abordados neste
artigo também são validos para aplicações WPF.
Pegue o projeto completo
aqui: Validacao_SilverLight.zip
Eu sei, é apenas SilverLight,
mas eu gosto…








