Noções básicas sobre a associação de dados

Concluído

Tech logo of U W P and W P F.

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!

Tech logo of U W P and W P F. W P F appears dimmed.

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.

Screenshot of the Visual Studio Create a new project dialog box.

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.

Screenshot of the running app with the clock.

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.

Tech logo of U W P and W P F. U W P appears dimmed.

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.

Screenshot of the Visual Studio Create a new WPF project dialog box.

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.

Screenshot of the running app with the clock.

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