Windows Phone

Vinculação de dados do Windows Phone

Jesse Liberty

Baixar o código de exemplo

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#.

The Data Binding View in Windows Phone
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 Data­Contexts 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:

  1. Vinculação única significa que os dados estão vinculados mas nunca serão atualizados, mesmo que os dados sejam atualizados pelo usuário.
  2. 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.
  3. 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