André Alves de Lima

Talking about Software Development and more…

Implementando as Interfaces de Notificação de Alteração no WPF

Olá pessoal, tudo tranquilo?

Depois de um longo tempo sem escrever, volto com a série de artigos sobre Gerenciamento de Dados com o WPF. Afinal, mesmo após o //build, aparentemente o WPF ainda vai ter ao menos alguns anos de vida.

No artigo de hoje falerei um pouco sobre as Change Notification Interfaces que utilizamos no WPF: INotifyPropertyChanged e INotifyCollectionChanged, além da classe ObservableCollection<T>.

Como você já deve ter percebido, a engine de binding do WPF é muito poderosa. Você consegue fazer binding entre propriedades de controles, de modo que sempre que ocorrer qualquer alteração em propriedades que participam de um binding, a interface seja automaticamente atualizada para refletir essas alterações.

Porém, se quisermos ligar uma propriedade de um controle do WPF com uma propriedade de uma classe de negócio, precisamos implementar uma maneira de notificar a engine do WPF quando nossa propriedade for alterada. E é justamente para isso que servem as interfaces de notificação de alterações do WPF.

Primeiramente, vamos abordar a interface INotifyPropertyChanged. Ela é utilizada para que os bindings linkados com propriedades da nossa classe sejam automaticamente atualizados quando houverem alterações em propriedades dos nossos objetos. Para implementarmos essa interface em uma classe do nosso projeto, é necessário declarar o evento PropertyChanged e dispará-lo sempre que uma propriedade da nossa classe for alterada.

Para exemplificar essa implementação, primeiramente observe abaixo a implementação de uma simples classe chamada Pessoa:

class Pessoa
{
    private string nome;
    private string sobreNome;
    private int idade;

    public string Nome
    {
        get { return nome; }
        set { nome = value; }
    }
    public string SobreNome
    {
        get { return sobreNome; }
        set { sobreNome = value; }
    }
    public int Idade
    {
        get { return idade; }
        set { idade = value; }
    }
}

Vamos agora alterar essa classe, de modo que ela implemente a interface INotifyPropertyChanged:

class Pessoa : System.ComponentModel.INotifyPropertyChanged
{
    private string nome;
    private string sobreNome;
    private int idade;

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    public string Nome
    {
        get { return nome; }
        set
        {
            nome = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Nome"));
        }
    }
    public string SobreNome
    {
        get { return sobreNome; }
        set
        {
            sobreNome = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("SobreNome"));
        }
    }
    public int Idade
    {
        get { return idade; }
        set
        {
            idade = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Idade"));
        }
    }
}

Note que a implementação da interface INotifyPropertyChanged consiste em disparar o evento PropertyChanged toda vez que uma propriedade da clase é alterada, ou seja, em todos os setters precisamos disparar o evento informando o nome da propriedade que está sendo alterada.

Observando atentamente a implementação atual, podemos fazer um refactoring, criando um método para fazer o disparo do evento. Com isso, evitamos repetição de código e deixamos o código mais limpo:

class Pessoa : System.ComponentModel.INotifyPropertyChanged
{
    private string nome;
    private string sobreNome;
    private int idade;

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    public string Nome
    {
        get { return nome; }
        set
        {
            nome = value;
            OnPropertyChanged("Nome");
        }
    }
    public string SobreNome
    {
        get { return sobreNome; }
        set
        {
            sobreNome = value;
            OnPropertyChanged("SobreNome");
        }
    }
    public int Idade
    {
        get { return idade; }
        set
        {
            idade = value;
            OnPropertyChanged("Idade");
        }
    }

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
    }
}

Com essa implementação, podemos utilizar as propriedades dessa nossa classe Pessoa em qualquer binding no WPF e, uma vez que alterarmos o valor de qualquer propriedade, a engine do WPF será notificada e tratará de refletir as alterações nos controles respectivos.

Tudo isso funciona muito bem se estivermos manipulando uma única instância da nossa classe, mas, e se por algum motivo quisermos trabalhar com uma coleção de objetos? Para exemplificarmos essa situação, suponhamos que tenhamos que implementar uma classe chamada ListaDePessoas, mesmo que isso não faça sentido considerando o que veremos mais a frente neste artigo:

class ListaDePessoas : IEnumerable, ICollection
{
    private List listaInterna = new List();

    public IEnumerator GetEnumerator()
    {
        foreach (Pessoa pessoa in listaInterna)
            yield return pessoa;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return listaInterna.GetEnumerator();
    }

    public void Add(Pessoa item)
    {
        listaInterna.Add(item);
    }

    public void Clear()
    {
        listaInterna.Clear();
    }

    public bool Contains(Pessoa item)
    {
        return listaInterna.Contains(item);
    }

    public void CopyTo(Pessoa[] array, int arrayIndex)
    {
        listaInterna.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return listaInterna.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(Pessoa item)
    {
        return listaInterna.Remove(item);
    }
}

Se linkarmos uma instância dessa nossa classe ListaDePessoas a um controle do WPF (um ListView, por exemplo) e modificarmos essa instância em runtime através de código, as alterações não serão refletidas no controle o qual essa instância está linkado. Para que as alterações sejam refletidas automaticamente, precisamos implementar a interface INotifyCollectionChanged, como podemos verificar abaixo:

class ListaDePessoas : IEnumerable, ICollection, INotifyCollectionChanged
{
    private List listaInterna = new List();
    public event NotifyCollectionChangedEventHandler CollectionChanged;

    private void OnCollectionChanged(NotifyCollectionChangedAction action)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, new NotifyCollectionChangedEventArgs(action));
    }

    public IEnumerator GetEnumerator()
    {
        foreach (Pessoa pessoa in listaInterna)
            yield return pessoa;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return listaInterna.GetEnumerator();
    }

    public void Add(Pessoa item)
    {
        listaInterna.Add(item);
        OnCollectionChanged(NotifyCollectionChangedAction.Add);
    }

    public void Clear()
    {
        listaInterna.Clear();
        OnCollectionChanged(NotifyCollectionChangedAction.Reset);
    }

    public bool Contains(Pessoa item)
    {
        return listaInterna.Contains(item);
    }

    public void CopyTo(Pessoa[] array, int arrayIndex)
    {
        listaInterna.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return listaInterna.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(Pessoa item)
    {
        bool resultado = listaInterna.Remove(item);
        OnCollectionChanged(NotifyCollectionChangedAction.Remove);
        return resultado;
    }
}

Notem que a implementação da interface INotifyCollectionChanged implica em declararmos o evento CollectionChanged e dispará-lo sempre que ocorrer uma mudança na nossa coleção, ou seja, sempre que um item for adicionado ou removido e sempre que nossa coleção for resetada.

Porém, como comentado anteriormente, em 99% dos casos, não faz sentido implementarmos esse tipo de inteface, uma vez que já existe no .NET Framework uma classe chamada ObservableCollection<T>, que justamente é uma coleção genérica que já implementa o mecanismo de notificação da interface INotifyCollectionChanged. Então, no nosso caso, faz muito mais sentido ao invés de utilizarmos essa classe ListaDePessoas, criarmos uma ObservableColletion<Pessoa>.

É isso aí pessoal. Espero que vocês tenham entendido como funcionam as interfaces que utilizamos para que a engine do WPF seja notificada quando ocorrem alterações nos nossos objetos, de forma que os controles visuais “bindados” aos nossos objetos também sejam atualizados corretamente.

Segue abaixo a versão deste artigo em vídeo que foi publicada no portal MSDN:

Até a próxima!

André Alves de Lima

6 thoughts on “Implementando as Interfaces de Notificação de Alteração no WPF

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *