Windows Phone
Vinculação de dados do Windows Phone
Praticamente, todos os aplicativos significativos do Windows Phone contêm algum tipo de dado, e a capacidade de conectar esses dados a elementos na interface do usuário (a exibição) é absolutamente essencial. Há várias maneiras de se fazer isso por meio de programação (atribuindo valores à medida que for avançando), mas um dos recursos mais poderosos e essenciais da programação em XAML é a capacidade de vincular dados a controles.
E eis as boas novas:
- Não é difícil de entender.
- Não é difícil de implementar.
Para ilustrar a vinculação de dados, você irá criar a página mostrada na Figura1. Ao ser carregada, ela será preenchida a partir de um objeto Person que você vai criar. Cada valor na interface do usuário será vinculado a uma propriedade no objeto Person e a vinculação real dos dados aos controles será automática — não há necessidade de código C#.
Figura 1 A exibição de vinculação de dados no Windows Phone
Introdução
Para começar, crie um novo aplicativo do Windows Phone em Visual Studio e chame-o de DataBinding. Comece por criar a classe que funcionará como os dados aos quais você estará vinculando (também conhecida como DataContext). Clique com o botão direito do mouse no projeto e selecione Adicionar | Nova | Classe e nomeie a classe de Person.cs.
Person irá conter, pelo menos, todas as propriedades que você deseja mostrar na exibição. A classe consiste em uma enumeração e um conjunto de propriedades automáticas, conforme mostra a Figura 2.
Figura 2 A classe Person
public class Person
{
public enum Sex
{
Male,
Female,
}
public string Name { get; set; }
public bool Moustache { get; set; }
public bool Goatee { get; set; }
public bool Beard { get; set; }
public Sex WhichSex { get; set; }
public double Height { get; set; }
public DateTime BirthDate { get; set; }
public bool Favorite { get; set; }
}
É possível ver facilmente como essas propriedades irão mapear a vários controles de entrada, como mostra a Figura 1. Os booleanos podem ser CheckBoxes ou RadioButtons (dependendo de serem ou não mutuamente exclusivos).
Criando o formulário
A próxima tarefa será criar o formulário que você irá usar para vincular os dados. Clique com o botão direito do mouse no projeto e selecione “Abrir no Expression Blend". Como regra, eu tendo a criar minha interface do usuário no Expression Blend e escrever meu código em Visual Studio.
Crie seis linhas e duas colunas na grade de conteúdo e arraste os controles de entrada apropriados. A Figura 3 mostra o XAML que você deseja produzir.
Figura 3 O XAML para criar o formulário
<Grid
x:Name="ContentPanel"
Grid.Row="1"
Margin="24,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="0.384*" />
<ColumnDefinition
Width="0.616*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition
Height="0.1*" />
<RowDefinition
Height="0.1*" />
<RowDefinition
Height="0.1*" />
<RowDefinition
Height="0.1*" />
<RowDefinition
Height="0.1*" />
<RowDefinition
Height="0.1*" />
<RowDefinition
Height="0.2*" />
</Grid.RowDefinitions>
<TextBlock
x:Name="NamePrompt"
TextWrapping="Wrap"
Text="Name"
Grid.Row="0"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<TextBlock
x:Name="SexPrompt"
Grid.Row="2"
TextWrapping="Wrap"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="Sex" />
<TextBlock
x:Name="HeightPrompt"
TextWrapping="Wrap"
Text="Height, StringFormat=F3"
HorizontalAlignment="Left"
Grid.Row="3"
d:LayoutOverrides="Height"
VerticalAlignment="Center" />
<TextBlock
x:Name="FavoritePrompt"
TextWrapping="Wrap"
Text="Favorite"
HorizontalAlignment="Left"
Grid.Row="4"
d:LayoutOverrides="Height"
VerticalAlignment="Center" />
<TextBox
x:Name="Name"
TextWrapping="Wrap"
d:LayoutOverrides="Height"
Grid.Column="1"
HorizontalAlignment="Left"
Width="200"
VerticalAlignment="Center"
Text="{Binding Name}" />
<StackPanel
x:Name="BeardStackPanel"
Grid.ColumnSpan="2"
Grid.Row="1"
Orientation="Horizontal">
<CheckBox
x:Name="Moustache"
Content="Moustache"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsChecked="{Binding Moustache}" />
<CheckBox
x:Name="Goatee"
Content="Goatee"
IsChecked="{Binding Goatee}" />
<CheckBox
Content="Beard"
IsChecked="{Binding Beard}"/>
</StackPanel>
<StackPanel
x:Name="SexStackPanel"
Grid.Column="1"
Grid.Row="2"
Orientation="Horizontal">
<RadioButton
x:Name="Male"
Content="Male"
IsChecked="True"
GroupName="Sex" />
<RadioButton
x:Name="Female"
Content="Female"
GroupName="Sex" />
</StackPanel>
<StackPanel
x:Name="HeightStackPanel"
Grid.Column="1"
Grid.Row="3"
Orientation="Horizontal">
<TextBlock
TextWrapping="Wrap"
Text="{Binding Height}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="0,0,0,0" />
<TextBlock
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="5,0,0,0"
Text="meters" />
</StackPanel>
<ToggleButton
x:Name="Favorite"
Content="Favorite"
Grid.Column="1"
Grid.Row="4"
d:LayoutOverrides="Width, Height"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsChecked="{Binding Favorite}" />
</Grid>
Associação
Cada um dos campos para inserção de texto tem agora seu valor definido usando a sintaxe de vinculação. Por exemplo, para instruir TextBox a realizar a vinculação, identifique quais de seus atributos irão necessitar dos dados — nesse caso, o atributo Text — e use a sintaxe de vinculação, como mostrado anteriormente.
Vinculações aparecem entre chaves e usam a palavra-chave Binding, geralmente seguida pelo nome da propriedade à qual você está vinculando o atributo. Por exemplo, este XAML define que o texto para a TextBox será obtido a partir de uma propriedade pública chamada Name:
<TextBox
x:Name="Name"
TextWrapping="Wrap"
d:LayoutOverrides="Height"
Grid.Column="1"
HorizontalAlignment="Left"
Width="200"
VerticalAlignment="Center"
Text="{Binding Name}" />
Do mesmo modo, no caso das caixas de seleção para pelos faciais, a propriedade IsChecked é vinculada à propriedade adequada.
<CheckBox
x:Name="Moustache"
Content="Moustache"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsChecked="{Binding Moustache}" />
Você ainda não sabe que objeto terá essas propriedades (Name e Moustache). Como observado anteriormente, o objeto que contém a propriedade capaz de ser vinculada é conhecida como DataContext. Ela pode ser praticamente qualquer coisa, mas nesse caso você irá criar um instância da classe Person e, em seguida, você irá definir o objeto Person como sendo o DataContext para toda a exibição.
Observe que você pode definir um DataContext para um contêiner, nesse caso, a página e todos os controles de exibição naquele contêiner irão compartilhar aquele DataContext — embora você tenha a liberdade de designar outros DataContexts a um ou mais controles individuais.
É possível instanciar Person no manipulador de eventos Loaded da página code-behind. O evento Loaded é chamado assim que a página é carregada e os controles são inicializados, como mostra a Figura 4.
Figura 4 O evento Loaded é chamado no momento do carregamento da página
private Person _currentPerson;
private Random _rand = new Random();
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;
}
void MainPage_Loaded( object sender, RoutedEventArgs e )
{
_currentPerson = new Person
{
Beard = false,
Favorite = true,
Goatee = false,
Height = 1.86,
Moustache = true,
Name = "Jesse",
WhichSex = Person.Sex.Male
};
}
Agora você pode definir o DataContext para cada controle no ContentPanel como sendo o objeto _currentPerson que você acabou de instanciar (no manipulador de eventos Loaded):
ContentPanel.DataContext = _currentPerson;
Uma vez conhecido seu DataContext, a TextBox pode resolver a propriedade Name e obter o valor ("Jesse") a ser exibido. O mesmo é válido para todos os outros controles, cada um vinculado a uma propriedade no novo objeto Person.
Execute o aplicativo e você deverá ver todos os campos adequadamente vinculados.
Alterando o DataContext
Para demonstrar o relacionamento entre a vinculação e a exibição, vamos criar uma quantidade de objetos Person e exibi-los individualmente. Para fazer isso, modifique MainPage.xaml.cs de modo a criar uma lista de Persons (criadas aleatoriamente) e, em seguida, faça a iteração por essa lista com um novo botão Avançar na interface do usuário, o qual você deve acrescentar à linha inferior:
<Button
Name="Next"
Content="Next"
Grid.Row="5"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
Eis o código modificado para interagir com o botão Avançar:
void MainPage_Loaded( object sender, RoutedEventArgs e )
{
SetDataContext();
Next.Click += Next_Click;
}
private void SetDataContext()
{
ContentPanel.DataContext = GeneratePerson();
}
void Next_Click( object sender, RoutedEventArgs e )
{
SetDataContext();
}
Observe que tanto o evento carregado na página como o manipulador de eventos de clique para o botão Avançar devem definir o DataContext, de modo que eu decompus isso em um método separado: SetDataContext. Esse método, por sua vez, chama o método GeneratePerson, cuja função é criar aleatoriamente uma Person.
Você pode agora fazer todas as alterações no code-behind. Primeiro, deixe de predefinir o Person atual e, em vez disso, defina-o chamando o método GeneratePerson.
Gerando um Person aleatório
Aqui está todo o método GeneratePerson; você verá que eu decompus a tarefa de escolha entre verdadeiro e falso em um método chamado FlipCoin:
private Person GeneratePerson()
{
var newPerson = new Person
{
Beard = FlipCoin(),
Favorite = FlipCoin(),
Goatee = FlipCoin(),
Height = _rand.NextDouble() + 1,
Moustache = FlipCoin(),
Name = names[_rand.Next(0, names.Count - 1)]
};
return newPerson;
}
O FlipCoin usa o gerador de números aleatórios para retornar verdadeiro em 50% das vezes:
private bool FlipCoin()
{
return _rand.Next( 1, 3 ) % 2 == 0;
}
Finalmente, para escolher um nome, crie uma lista com meia dúzia de nomes que possam ser atribuídos a homens ou mulheres e use o gerador de números aleatórios para escolher uma contrapartida na lista:
private readonly List<string> names = new List<string>()
{
"Stacey",
"Robbie",
"Jess",
"Robin",
"Syd",
"J.J.",
"Terri",
"Moonunit",
};
Execute o aplicativo, e clique no botão Avançar. Assim que um objeto Person for criado, ele será definido como o DataContext e suas propriedades serão vinculadas aos controles da interface do usuário.
INotifyPropertyChanged
O que acontece se uma das propriedades de seu objeto Person for alterada? Isso pode facilmente acontecer se o objeto estiver contido em um banco de dados e outros usuários tiverem acesso ao mesmo objeto. Seria conveniente atualizar sua interface do usuário.
Para que isso funcione, sua classe (o DataContext) deve implementar INotifyPropertyChanged — uma interface simples que permite que cada propriedade notifique a interface de usuário quando seu valor for alterado. É comum criar um método auxiliar que verifique e garanta que o evento tenha pelo menos um método a ele registrado. Nesse caso, o método auxiliar gera o evento, transmitindo o nome da propriedade que foi atualizada.
Para ver isso funcionando, acrescente um novo botão, denominado Alterar, à interface do usuário. Ao clicar no botão Alterar, altere a propriedade Name para "Jacob":
void Change_Click( object sender, RoutedEventArgs e )
{
_currentPerson.Name = "Jacob";
}
Isso não causará nenhum efeito, a menos que Person implemente INotifyPropertyChanged e a propriedade Name gere o evento PropertyChanged, como mostra a Figura 5.
Figura 5 A interface INotifyPropertyChanged
public class Person : INotifyPropertyChanged
{
public string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
PropChanged( "Name" );
}
}
// Other properties
public event PropertyChangedEventHandler PropertyChanged;
private void PropChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged( this, new PropertyChangedEventArgs( propName ) );
}
}
}
Isso feito, quando você clicar no botão Alterar, o nome mostrado na interface do usuário será alterado para o novo nome no objeto (Jacob).
Vinculação bidirecional
E se o usuário interagir com a interface do usuário e alterar um valor diretamente (por exemplo, digitando um novo nome na TextBox Name)? Em geral, é desejável que essa alteração seja retornada aos dados subjacentes (o objeto DataContext). Para isso, você irá usar vinculação bidirecional.
Para modificar o programa para usar vinculação bidirecional, na propriedade Name, encontre a vinculação Name e modifique-a da seguinte maneira:
<TextBox
x:Name="Name"
TextWrapping="Wrap"
d:LayoutOverrides="Height"
Grid.Column="1"
HorizontalAlignment="Left"
Width="200"
VerticalAlignment="Center"
Text="{Binding Name, Mode=TwoWay}" />
Existem três maneiras para se proceder à vinculação:
- Vinculação única significa que os dados estão vinculados mas nunca serão atualizados, mesmo que os dados sejam atualizados pelo usuário.
- Vinculação única é a padrão; os dados são obtidos da fonte para a interface do usuário mas não são retornados à fonte.
- Vinculação bidirecional permite que os dados sejam obtidos na fonte e retornados à fonte se forem modificados na interface do usuário.
Vinculação de elemento
Altere a linha do botão Avançar para a linha 6 e arraste um controle deslizante para a linha 5. Há uma série de definições importantes em um controle deslizante, incluindo:
- Mínima
- máxima
- Valor
- LargeChange
- SmallChange
Pode-se observá-las refletidas em código na Figura 6.
Figura 6 Adicionando um controle deslizante
<Slider
x:Name="Likability"
Grid.Row="5"
Grid.Column="0"
BorderBrush="White"
BorderThickness="1"
Background="White"
Foreground="Blue"
LargeChange="10"
SmallChange="1"
Minimum="0"
Width="199"
Maximum="100"
Value="50"
Height="90" />
Mínimo e Máximo definem o campo do controle deslizante. Nesse caso, como estou usando porcentagens, eu os defini como 0 e 100, respectivamente.
Value é o valor atual do controle deslizante e será: Mínimo <= valor <= Máximo
LargeChange e SmallChange são usados da mesma maneira que o são em barras de rolagem; eles indicam o que o clique fará e o que a utilização de outro tipo de controle (talvez setas) para mover o controle deslizante de forma incremental irá provocar, respectivamente.
Configurando o TextBlocks
Na coluna da direita, você irá usar três TextBlocks; o primeiro e o terceiro como rótulos fixos (com os valores “Likeability:” e “%,”, respectivamente). O TextBlock do meio exibe uma representação numérica do valor do controle deslizante.
Para conseguir isso, vincule a propriedade Text Value do TextBlock do meio à propriedade Value do controle deslizante, identificando o elemento ao qual você está vinculando com a palavra-chave ElementName, como mostrado na Figura 7.
Figura 7 Vinculando a propriedade Value a um TextBlock
<StackPanel
x:Name="LikeabilityPercentStackPanel"
Grid.Row="5"
Grid.Column="1"
Orientation="Horizontal">
<TextBlock
Text="Likeability: "
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="20,0,5,0" />
<TextBlock
x:Name="SliderValue"
Text="{Binding Value, ElementName=Likeability, StringFormat=F3}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="5,0,0,0"/>
<TextBlock
Text="%"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
</StackPanel>
Execute o programa. À medida que o controle deslizante for ajustado, o valor no TextBlock será atualizado instantaneamente.
Conversores de dados
Algumas propriedades não se vincularão de maneira apropriada a uma dada interface do usuário, ou pode ser que você deseje maior controle sobre a maneira como o valor será exibido. Como um simples exemplo, vamos exibir a data de nascimento do usuário movendo uma linha para baixo e inserindo uma linha com um prompt ("Data de nascimento") e o valor da data de nascimento de Person.
Para isso, você deverá modificar o método GeneratePerson em MainPage.xaml.cs para que ele gere uma data de nascimento válida, o que você faz adicionando essa linha para criar uma data de nascimento aleatória dos últimos vinte anos.
BirthDate = DateTime.Now - TimeSpan.FromDays(_rand.Next(1,365*20)),
É só vincular a propriedade BirthDate que você verá a data e hora do nascimento. Mas você não deseja a hora, somente a data em formato reduzido. Para conseguir isso, você precisa de um DataConverter.
DataConverters são classes que implementam o IValueConverter. Essa interface requer dois métodos, mostrados na Figura 8.
Figura 8 A interface IValueConverter
public object Convert(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture )
{
throw new NotImplementedException();
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture )
{
throw new NotImplementedException();
}
Nesse caso, você só precisa do primeiro (o segundo nunca será chamado). O método é de implementação bem simples; assegure-se de que o tipo de destino seja cadeia de caracteres e que o tipo de valor seja DateTime. Se esse for o caso, pegue o valor, converta-o em um DateTime e, em seguida, chame para ele ToShortDateString, como mostrado na Figura 9.
Figura 9 O método Convert para reduzir um DateTime
public object Convert(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture )
{
if (targetType == typeof( string ) &&
value.GetType() == typeof( DateTime ))
{
return (( DateTime ) value).ToShortDateString();
}
else // Unable to convert
{
return value;
}
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture )
{
throw new NotImplementedException();
}
Isso posto, você vai precisar de um XAML para acessar o conversor de valor; isso poderá ser feito tornando o conversor em um recurso. Abra App.xaml e acrescente um namespace para seu conversor, baseado no namespace de seu aplicativo:
xmlns:mine="clr-namespace:DataBinding">
Em seguida, no mesmo arquivo, encontre a seção <Application.Resource> e adicione um recurso para seu conversor de valor:
<Application.Resources>
<mine:DateConverter x:Key="dateConverter" />
</Application.Resources>
Você pode agora usar a chave em seu arquivo XAML. Atualize a vinculação para o BirthDate, para usar o recurso:
<TextBlock
Grid.Row="6"
Grid.Column="1"
VerticalAlignment="Center"
Text="{Binding BirthDate, Converter={StaticResource dateConverter}}" />
Execute o programa e verá a data exibida no formato reduzido de data.
Aplicativos poderosos
Vinculação de dados permite a criação de poderosos aplicativos para o Windows Phone que gerenciam de maneira confiável o relacionamento entre dados subjacentes e os controles e as exibições que mostram os dados. Nesse artigo, você aprendeu a criar vinculação simples de dados e vinculação bidirecional de dados, como vincular a elementos e como usar conversores de dados para manipular os dados no formato desejado.
Jesse Liberty é um divulgador sênior da comunidade de desenvolvedores da equipe do Windows Phone. Liberty é o anfitrião do popular "Yet Another Podcast" (jesseliberty.com/podcast), e o seu blog (jesseliberty.com) é de leitura obrigatória. Ele é o autor de numerosos best-sellers, inclusive de “Programming Reactive Extensions and LINQ” (Apress, 2011) e “Migrating to Windows Phone" (Apress, 2011). Você pode segui-lo no Twitter, em twitter.com/JesseLiberty.
Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Jason Shaver