segunda-feira, 31 de julho de 2017

Trabalhando com data binding no Windows Forms -parte 2

Por André Alves Lima em 26/07/2017

A classe BindingList

O primeiro problema na nossa implementação é o fato de termos utilizado a classe “List” para criarmos a nossa coleção de produtos. A classe “List” não foi desenvolvida com o intuito de ser utilizada em data binding. Porém, não se preocupe. Para isso nós temos a classe “BindingList“, que é justamente uma “List” que suporta data binding!
Se trocarmos a declaração da nossa coleção de produtos por uma BindingList, nós teremos um resultado um pouco melhor:
1
2
// C#
private System.ComponentModel.BindingList<Produto> _produtos = new System.ComponentModel.BindingList<Produto>();
1
2
' VB.NET
Private Produtos As New System.ComponentModel.BindingList(Of Produto)
Veja que o texto do primeiro produto foi alterado de imediato. Além disso, o novo produto que inserimos na lista em tempo de execução também foi exibido com sucesso. Porém, nós ainda temos um probleminha a ser resolvido. Se nós continuarmos inserindo novas linhas no grid, chega uma hora em que o grid para de atualizar o nome do primeiro produto:
Isso acontece porque a nossa classe “Produto” não implementa a interface “INofifyPropertyChanged“.

A interface INotifyPropertyChanged

A interface INotifyPropertyChanged é utilizada para que o mecanismo de data binding seja notificado quando o valor de uma propriedade for alterado. Nas classes internas do .NET (nos controles do Windows Forms, por exemplo), isso tudo já está implementado nativamente. Porém, se criarmos novas classes no nosso projeto (como foi o caso da classe “Produto” que criamos nesse exemplo), nós temos que implementar essa interface caso a classe venha a ser utilizada em algum data binding.
Essa não é uma particularidade do Windows Forms. Muito pelo contrário, essa interface é essencial também no famoso data binding do WPF. Se não implementarmos essa interface nas nossas classes, nós teremos esse mesmo problema no WPF.
Uns tempos atrás eu escrevi um artigo falando das interfaces de notificação de alteração no WPF. Hoje eu só vou mostrar como implementá-las nesse exemplo, e não vou entrar em muitos detalhes sobre elas. Caso você queira aprender mais sobre esse assunto, confira o artigo que eu acabei de mencionar.
A implementação da interface INotifyPropertyChanged é muito tranquila. Nós só temos que disparar o evento “NotifyPropertyChanged” em todos os “setters” das nossas propriedades (que agora não poderão ser auto-implementadas):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// C#
public class Produto : System.ComponentModel.INotifyPropertyChanged
{
    private int _id;
    public int Id
    {
        get { return _id; }
        set
        {
            _id = value;
            OnPropertyChanged("Id");
        }
    }
    private string _nome;
    public string Nome
    {
        get { return _nome; }
        set
        {
            _nome = value;
            OnPropertyChanged("Nome");
        }
    }
    private decimal _valorUnitario;
    public decimal ValorUnitario
    {
        get { return _valorUnitario; }
        set
        {
            _valorUnitario = value;
            OnPropertyChanged("ValorUnitario");
        }
    }
    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
' VB.NET
Public Class Produto
    Implements System.ComponentModel.INotifyPropertyChanged
 
    Private _id As Integer
    Public Property Id As Integer
        Get
            Return _id
        End Get
        Set(value As Integer)
            _id = value
            OnPropertyChanged("Id")
        End Set
    End Property
 
    Private _nome As String
    Public Property Nome As String
        Get
            Return _nome
        End Get
        Set(value As String)
            _nome = value
            OnPropertyChanged("Nome")
        End Set
    End Property
 
    Private _valorUnitario As Decimal
    Public Property ValorUnitario As Decimal
        Get
            Return _valorUnitario
        End Get
        Set(value As Decimal)
            _valorUnitario = value
            OnPropertyChanged("ValorUnitario")
        End Set
    End Property
 
    Public Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    Private Sub OnPropertyChanged(ByVal PropertyName As String)
        RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(PropertyName))
    End Sub
End Class
Se testarmos o comportamento do grid mais uma vez depois dessa alteração, nós veremos que agora ele se comportará corretamente:
Como você deve ter notado, a implementação da interface INotifyPropertyChanged pode acabar sendo um pouco “maçante“. Imagine termos que implementar todo esse código em todas as nossas classes de modelo? Vai dar um certo trabalho. É pensando nisso que a Microsoft vem investindo em novas funcionalidades no C# e VB.NET para facilitar a nossa vida. Eu já escrevi uns tempos atrás sobre o atributo CallerMemberName, que facilita um pouco esse processo. Se pesquisarmos na internet, encontraremos maneiras mais fáceis de implementarmos essa interface, como esta que eu encontrei no StackOverflow:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// C#
public class Produto : System.ComponentModel.INotifyPropertyChanged
{
    private int _id;
    public int Id
    {
        get { return _id; }
        set { SetField(ref _id, value); }
    }
    private string _nome;
    public string Nome
    {
        get { return _nome; }
        set { SetField(ref _nome, value); }
    }
    private decimal _valorUnitario;
    public decimal ValorUnitario
    {
        get { return _valorUnitario; }
        set { SetField(ref _valorUnitario, value); }
    }
 
    protected bool SetField<T>(ref T field, T value, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
            return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
' VB.NET
Public Class Produto
    Implements System.ComponentModel.INotifyPropertyChanged
 
    Private _id As Integer
    Public Property Id As Integer
        Get
            Return _id
        End Get
        Set(value As Integer)
            SetField(_id, value)
        End Set
    End Property
 
    Private _nome As String
    Public Property Nome As String
        Get
            Return _nome
        End Get
        Set(value As String)
            SetField(_nome, value)
        End Set
    End Property
 
    Private _valorUnitario As Decimal
    Public Property ValorUnitario As Decimal
        Get
            Return _valorUnitario
        End Get
        Set(value As Decimal)
            SetField(_valorUnitario, value)
        End Set
    End Property
 
    Protected Function SetField(Of T)(ByRef field As T, value As T, <System.Runtime.CompilerServices.CallerMemberName> Optional propertyName As String = Nothing) As Boolean
        If EqualityComparer(Of T).[Default].Equals(field, value) Then
            Return False
        End If
        field = value
        OnPropertyChanged(propertyName)
        Return True
    End Function
 
    Public Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    Private Sub OnPropertyChanged(ByVal PropertyName As String)
        RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(PropertyName))
    End Sub
End Class


Continua na parte 3

Nenhum comentário:

Postar um comentário