Noções básicas sobre a associação de dados
Nesta lição, você aprenderá a criar um aplicativo que mostra a hora atual. A lição apresenta os conceitos básicos de associação de dados, mostra como extrair dados do código para a interface do usuário do seu aplicativo, e como atualizá-la para exibir o relógio na interface do usuário. A lição formará a base para tarefas de associação de dados mais complexas em lições posteriores. Certo, vamos começar!
1. Criar o projeto
Se ele ainda não estiver em execução, abra o Visual Studio. Crie um novo projeto universal do Windows em C# usando o modelo Aplicativo em branco (Windows universal). Chame-o de DatabindingSample. Esse é o projeto com o qual você trabalhará durante todo o módulo de Interface do Usuário e Dados.
Quando você clicar em OK, o Visual Studio solicitará que sejam inseridas as versões de destino e mínima do Windows. Este projeto é apenas um exercício e você não está planejando implantá-lo em computadores que executam uma versão mais antiga do Windows. Portanto, você pode selecionar a versão mais recente do Windows para a versão mínima e a versão de destino e clicar em OK.
2. Adicionar o TextBlock para a exibição do relógio
Assim que o projeto tiver sido completamente inicializado e carregado, abra MainPage.xaml
clicando nele duas vezes no Gerenciador de Soluções.
Dica
Se estiver com pouco espaço na tela, use a lista suspensa no canto superior esquerdo para alternar o editor e simular uma tela de resolução mais baixa. É recomendável usar Área de Trabalho de 13,3" (1280x720) com escala de 100% para este módulo, mas escolha aquela que for mais confortável para você.
Adicione a linha a seguir entre as marcas de abertura e fechamento do elemento Grid
.
<TextBlock HorizontalAlignment="Right"
Margin="10"
Text="{x:Bind CurrentTime}" />
Isso criará um TextBlock
no canto superior direito da janela, com uma margem de 10 unidades da borda. Em seguida, vamos cuidar do Text
do TextBlock
.
O bloco Text={x:Bind CurrentTime}
é a sua primeira experiência com a associação de dados. x:Bind
é uma extensão de marcação XAML que é compilada em código C# com o restante do seu aplicativo. Aqui, ela conecta a propriedade Text
do TextBlock
à propriedade CurrentTime
. Se você tentar compilar o projeto agora, vai se deparar com a seguinte mensagem de erro:
Erro WMC1110 do XamlCompiler: Caminho de associação inválido 'CurrentTime': a propriedade 'CurrentTime' não pode ser encontrada no tipo 'MainPage'
Isso indica que está faltando uma propriedade CurrentTime
de MainPage
no compilador. Depois que criarmos essa propriedade, o seu conteúdo será exibido no TextBlock
no canto superior direito.
Observação
O UWP também dá suporte a um método de associação de dados mais antigo, que se parece com este: Text={Bind CurrentTime}
. Esse método mais antigo funciona um pouco diferente de {x:Bind}
. Notadamente, ele não oferecerá erros de tempo de compilação se você tiver um erro de digitação. Neste módulo, vamos nos concentrar exclusivamente na nova maneira {x:Bind}
de associação, que fornece verificação de erros em tempo de compilação. No entanto, {x:Bind}
é menos desenvolvido do que {Bind}
e recebe novos recursos. Por esse motivo, escolhemos seguir com a versão mais recente do Windows na criação do projeto.
3. Criar a propriedade CurrentTime
Abra MainPage.xaml.cs e adicione a seguinte definição de propriedade à classe MainPage
.
public string CurrentTime => DateTime.Now.ToLongTimeString();
Se não estiver familiarizado com a sintaxe acima, ela é chamada de membro apto para expressão. Ela foi introduzida no C# 6.0, e é uma abreviação para o seguinte:
public string CurrentTime
{
get { return DateTime.Now.ToLongTimeString(); }
}
4. Execute o aplicativo
Se você iniciar o aplicativo agora (usando a tecla F5 ou o comando Depurar/Iniciar Depuração no menu), o aplicativo será compilado e executado. O melhor é que parece que vai funcionar! A hora atual é exibida no canto superior direito.
No entanto, algo não está certo porque o relógio não é atualizado. Ele está parado na hora em que o aplicativo foi iniciado pela primeira vez. Como o aplicativo saberia quando atualizar o valor no TextBlock
? Precisamos orientar o runtime do UWP a atualizá-lo uma vez por segundo.
5. Especificar os modos de associação
As associações {x:Bind}
foram altamente otimizadas para desempenho. Isso significa que elas não fazem nada que não tenha sido explicitamente solicitado pelo desenvolvedor. Portanto, por padrão, uma associação {x:Bind}
avalia a origem da associação (no nosso caso, a propriedade CurrentTime
) apenas uma vez. Esse tipo de associação é chamado de associação OneTime
. Se quisermos que a estrutura UWP continue atualizando a interface do usuário, temos que especificar explicitamente outro modo de associação: OneWay
ou TwoWay
.
O modo de associação TwoWay
indica uma associação bidirecional entre nosso código C# (a lógica) e a interface do usuário. Esse tipo de associação será útil posteriormente quando estivermos associando a controles que o usuário possa manipular. Mas para um TextBlock
, uma associação OneWay
é preferível, porque as alterações de dados só serão originadas no código, nunca na interface do usuário.
Para especificar um modo de associação OneWay
para nosso TextBlock
, altere {x:Bind CurrentTime}
para {x:Bind CurrentTime, Mode=OneWay}
. A marca TextBlock
inteira dentro da Grid
agora deve ter a aparência desta marcação.
<TextBlock HorizontalAlignment="Right"
Margin="10"
Text="{x:Bind CurrentTime, Mode=OneWay}" />
Essa associação instruirá o runtime do UWP a criar a infraestrutura necessária para acompanhar as alterações na propriedade CurrentTime
e refleti-la no Text
do TextBlock
. Essa infraestrutura adicional ocupa uma quantidade pequena de ciclos de CPU e memória, e é por isso que ela não é o padrão.
Se você executar o aplicativo agora, o relógio ainda não será atualizado. Precisamos notificar o sistema de que a propriedade CurrentTime
foi alterada.
6. Implementar a interface INotifyPropertyChanged
Essa notificação ocorre por meio da interface INotifyPropertyChanged
. Trata-se de uma interface simples com um único evento.
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
}
Qualquer classe que tenha uma propriedade simples de C# como a origem da associação de dados deve implementar a interface INotifyPropertyChanged
. A classe deve disparar o evento PropertyChanged
quando a interface do usuário tiver que ser atualizada. Vamos prosseguir e adicionar a interface à declaração da classe MainPage
.
public sealed partial class MainPage : Page, INotifyPropertyChanged
Também temos que implementar a interface adicionando o evento PropertyChanged
à classe.
public event PropertyChangedEventHandler PropertyChanged;
7. Invocar o evento PropertyChanged
a cada segundo
O que falta agora é invocar o evento PropertyChanged
toda vez que queremos atualizar o relógio (ou seja, a cada segundo). Para começar, vamos declarar um objeto DispatcherTimer
na classe MainPage
.
private DispatcherTimer _timer;
Agora vamos configurá-lo no construtor (depois da chamada a InitializeComponent
) para que ele seja acionado a cada segundo.
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_timer.Tick += (sender, o) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
_timer.Start();
A primeira linha acima cria o temporizador com um intervalo de um segundo, e a última linha o inicia. Vamos examinar o que acontece quando o temporizador é disparado (na segunda linha).
PropertyChanged?.Invoke
é um atalho para verificar se um evento é nulo e, em caso negativo, invocá-lo. Como a maioria dos eventos, o primeiro argumento é o remetente (this
). O segundo argumento para o evento PropertyChanged
é um objeto PropertyChangedEventArgs
recentemente criado, que tem um construtor esperando uma cadeia de caracteres como o nome da propriedade. Portanto, o assinante do evento PropertyChanged
(nesse caso, o sistema UWP) receberá o nome da propriedade atualizada e poderá agir corretamente.
Dica
Não use literais de cadeia de caracteres (como "CurrentTime"
) para o nome da propriedade. O uso da cadeia de caracteres em si está sujeito a erros de digitação, o que pode resultar em problemas de dificuldade de depuração quando a interface do usuário não estiver sendo atualizada. Além disso, uma renomeação inocente da propriedade também poderá introduzir erros se as constantes da cadeia de caracteres não forem atualizadas. O ideal é sempre usar a expressão nameof
, que é imune a erros de digitação e pode acompanhar as renomeações.
O MainPage.xaml.cs inteiro deve ter esta aparência:
namespace DatabindingSample
{
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private DispatcherTimer _timer;
public MainPage()
{
this.InitializeComponent();
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_timer.Tick += (sender, o) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
_timer.Start();
}
public string CurrentTime => DateTime.Now.ToLongTimeString();
public event PropertyChangedEventHandler PropertyChanged;
}
}
8. Executar o aplicativo
Se você executar o aplicativo agora, o relógio será atualizado. Parabéns, você criou a sua primeira associação de dados!
9. Resumo
Agora você sabe como usar {x:Bind
para criar uma maneira rápida e automática de extrair dados do código para a interface do usuário do seu aplicativo UWP. Essa técnica é verificada no momento da compilação. Você também se familiarizou com a interface INotifyPropertyChanged
. Essa interface permite ao aplicativo notificar a estrutura UWP quando uma propriedade associada de dados for alterada e a interface do usuário precisar ser atualizada.
1. Criar o projeto
Se ele ainda não estiver em execução, abra o Visual Studio. Crie outro projeto WPF em C# usando o modelo Aplicativo do WPF. Dê a ele o nome de DatabindingSampleWPF e selecione OK. Esse é o projeto com o qual você trabalhará durante todo o módulo de Interface do Usuário e Dados.
2. Criar a classe Clock
Como nossa tarefa é exibir a hora atual, faz sentido criar uma classe Clock
primeiro. Clique com o botão direito do mouse no projeto DatabindingSampleWPF
, no Gerenciador de Soluções, selecione Adicionar / Classe e insira Clock
como o nome da classe.
Copie o seguinte código no arquivo recém-criado:
using System;
namespace DatabindingSampleWPF
{
public class Clock
{
public string CurrentTime => DateTime.Now.ToLongTimeString();
}
}
Se não estiver familiarizado com a sintaxe acima para a propriedade CurrentTime
, ela é chamada de membro apto para expressão. Ela foi introduzida no C# 6.0, e é uma abreviação para o seguinte:
public string CurrentTime
{
get { return DateTime.Now.ToLongTimeString(); }
}
Como você pode ver, tudo o que a classe Clock
tem até agora é uma simples propriedade string
que retorna a hora atual em um formato longo de hora. A próxima etapa é exibir a hora dentro do aplicativo em si.
3. Adicionar o TextBlock para a exibição do relógio
Se você tiver MainWindow.xaml
aberto no Visual Studio, selecione a respectiva guia. Caso contrário, você pode abri-lo clicando duas vezes nele no Gerenciador de Soluções.
Adicione a linha a seguir entre as marcas de abertura e fechamento do elemento Grid
.
<TextBlock HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="10"
Text="{Binding CurrentTime}">
<TextBlock.DataContext>
<local:Clock/>
</TextBlock.DataContext>
</TextBlock>
Essa marcação criará um novo TextBlock
no lado superior direito da janela, com uma margem de 10 unidades distante da borda.
O bloco Text="{Binding CurrentTime}"
é a sua primeira experiência com a associação de dados. {Binding}
é uma extensão da marcação XAML. Aqui, ela conecta a propriedade Text
do TextBlock
à propriedade CurrentTime
– mas a propriedade CurrentTime
de qual objeto?
O objeto ao qual a associação de dados se refere é instanciado no DataContext
do TextBlock
. Portanto, o código XAML acima não apenas cria um controle TextBlock
, mas também instancia um objeto Clock
. Além disso, o código associa a propriedade Text
do TextBlock
à propriedade CurrentTime
do objeto Clock
que ele criou. A propriedade CurrentTime
é chamada de origem da associação, enquanto a propriedade Text
é chamada de destino da associação.
4. Execute o aplicativo
Se você iniciar o aplicativo agora (usando a tecla F5 ou o comando Depurar/Iniciar Depuração no menu), o aplicativo será compilado e executado. O melhor é que parece que vai funcionar! A hora atual é exibida no canto superior direito.
No entanto, algo não está certo porque o relógio não é atualizado. Ele está parado na hora em que o aplicativo foi iniciado pela primeira vez. Como o aplicativo saberia quando atualizar o valor no TextBlock
? Precisamos orientar o runtime do WPF a atualizá-lo uma vez por segundo.
Em outras palavras, precisamos notificar o sistema de que a propriedade CurrentTime
foi alterada.
5. Implementar a interface INotifyPropertyChanged
Essa notificação ocorre por meio da interface INotifyPropertyChanged
. Trata-se de uma interface simples com um único evento.
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
}
Qualquer classe que tenha uma propriedade simples de C# como a origem da associação de dados deve implementar a interface INotifyPropertyChanged
. A classe deve disparar o evento PropertyChanged
quando a interface do usuário tiver que ser atualizada. Vamos prosseguir e adicionar a interface à declaração da classe Clock
.
using System.ComponentModel;
public class Clock : INotifyPropertyChanged
{
Também temos que implementar a interface adicionando o evento PropertyChanged
à classe.
public event PropertyChangedEventHandler? PropertyChanged;
6. Invocar o evento PropertyChanged
a cada segundo
O que falta agora é invocar o evento PropertyChanged
toda vez que queremos atualizar o relógio (ou seja, a cada segundo). Para começar, vamos adicionar o namespace System.Windows.Threading
aos using
s e declarar um objeto DispatcherTimer
na classe Clock
.
private DispatcherTimer _timer;
Agora vamos configurá-lo no construtor para que ele seja acionado a cada segundo.
public Clock()
{
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_timer.Tick += (sender, o) => PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(CurrentTime)));
_timer.Start();
}
A primeira linha no construtor cria o temporizador com um intervalo de um segundo, e a última linha o inicia. Vamos examinar o que acontece quando o temporizador é disparado (na segunda linha).
PropertyChanged?.Invoke
é um atalho para verificar se um evento é nulo e, em caso negativo, invocá-lo. Como a maioria dos eventos, o primeiro argumento é o remetente (this
). O segundo argumento para o evento PropertyChanged
é um objeto PropertyChangedEventArgs
recentemente criado, que tem um construtor esperando uma cadeia de caracteres como o nome da propriedade. Portanto, o assinante do evento PropertyChanged
(nesse caso, o sistema WPF) receberá o nome da propriedade atualizada e poderá agir corretamente.
Dica
Não use literais de cadeia de caracteres (como "CurrentTime"
) para o nome da propriedade. O uso da cadeia de caracteres em si está sujeito a erros de digitação, o que pode resultar em problemas de dificuldade de depuração quando a interface do usuário não estiver sendo atualizada. Além disso, uma renomeação inocente da propriedade também poderá introduzir erros se as constantes da cadeia de caracteres não forem atualizadas. A prática recomendada é usar a expressão nameof
, que é imune a erros de digitação e pode acompanhar operações de renomeação.
O Clock.cs
completo deverá ficar com a seguinte aparência:
namespace DatabindingSampleWPF
{
using System;
using System.ComponentModel;
using System.Windows.Threading;
public class Clock : INotifyPropertyChanged
{
private DispatcherTimer _timer;
public string CurrentTime => DateTime.Now.ToLongTimeString();
public event PropertyChangedEventHandler PropertyChanged;
public Clock()
{
// setup _timer to refresh CurrentTime
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_timer.Tick += (sender, o) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
_timer.Start();
}
}
}
7. Executar o aplicativo
Se você executar o aplicativo agora, o relógio será atualizado. Você criou a sua primeira associação de dados!
8. Resumo
Agora você sabe como usar {Binding}
para criar uma maneira rápida e automática de extrair dados do código para a interface do usuário do seu aplicativo WPF. Você também se familiarizou com a interface INotifyPropertyChanged
. Essa interface permite ao aplicativo notificar a estrutura WPF quando uma propriedade associada de dados for alterada e a interface do usuário precisar ser atualizada.