VBA - Artigo 013 - Classes no VBA


Classes no VBA

Este artigo é extenso devido a quantidade de detalhes. Recomendo ler quando tiver bastante tempo para ler e praticar os códigos de exemplo. Há muitos conceitos aqui e pode levar um tempo para absorver tudo. Leia e releia quantas vezes for necessário e use os comentários do blog se houver necessidade.

Classe é uma ferramenta poderosa em programação orientada a objetos (POO). Embora há quem diga que o VBA não seja totalmente orientado a objetos, a falta de alguns conceitos de POO não impede de realizar ótimos trabalhos. A classe serve para projetar um objeto, conceito principal da POO. É preciso enfatizar o fato que a classe é um projeto do objeto, ou seja, a classe só é utilizada para criar (ou instanciar) os objetos.

No VBA a classe é definida em um módulo de classe, que é adicionado indo em Inserir/Módulo de classe. É nesse módulo que você criará as propriedades e os métodos. Ao criar um módulo de classe, a primeira coisa a fazer é alterar a propriedade (Name) para um nome mais apropriado do que Classe1, como por exemplo clsProduto. É boa prática colocar o prefixo cls para diferenciar a classe dos objetos que serão criados a partir dela. A criação do objeto é feita da seguinte maneira:

Dim Produto As clsProduto
Set Produto = new clsProduto

A primeira linha efetua a declaração (obrigatório quando se utiliza Option Explicit), enquanto a segunda efetua a criação do objeto. Somente a partir da criação de um objeto que é possível utilizar as propriedades e os métodos da classe que serviu de origem. Do jeito que está, o objeto Produto não tem utilidade nenhuma, pois não tem nenhuma propriedade ou método.

Para criar propriedades, é preciso declará-las no módulo de classe. Coloque as seguintes linhas no módulo clsProduto:

Option Explicit
Private pCodigo As Long
Private pNome As String
Private pQuantidade As Long
Private pPreco As Currency
Private pAtivo As Boolean

Perceba que todas as propriedades foram declaradas como privadas, ou seja, só serão acessadas internamente nesse módulo. Também foi adicionado um prefixo p de propriedade. Você pode definir o prefixo que quiser, desde que fique fácil de distinguir o que será usado só pela classe e o que será acessado pelo objeto.

As propriedades ainda não estão prontas para uso, falta os meios para conseguir acessá-las e torná-las úteis de fato. Precisamos declarar procedimentos que permitam esse acesso. Para isso temos de criar Property Get e Property Let para cada propriedade. Vejamos como fica a propriedade Nome:

Public Property Get Nome() As String
    Nome = pNome
End Property
Public Property Let Nome(Valor As String)
    pNome = Valor
End Property

Get serve para obter a propriedade, enquanto Let serve para armazenar um valor na propriedade. Com este código já é possível acessar a propriedade Nome. Atente bem ao tipo do objeto nas declarações, que precisam combinar como tipo da propriedade. Da mesma forma, perceba que a declaração dos procedimentos é pública, para que possamos acessar. Se fosse declarado como privados a propriedade seria interna do objeto e só acessada pelos próprios procedimentos do módulo de classe. Não estranhe, há casos em que é necessário fazer dessa forma, como veremos mais adiante.

Crie um módulo novo (módulo comum, não de classe) para testar essa propriedade Nome com o seguinte código:

Sub TesteClasse()

    Dim Produto As clsProduto
    Set Produto = New clsProduto

    Produto.Nome = "Teste"
    Debug.Print "Nome do produto: " & Produto.Nome

End Sub

Depure o código (tecla F8) e acompanhe como a execução percorre os procedimentos, entrando em Property Let para armazenar o valor e depois entrando em Property Get para obter o conteúdo da propriedade.

Como você pode ter notado, Property Get e Property Let são procedimentos como outros, sendo possível incluir uma consistência dos dados enviados antes de armazenar. Veja a propriedade Quantidade abaixo:

Public Property Get Quantidade() As Long
    Quantidade = pQuantidade
End Property
Public Property Let Quantidade(Valor As Long)
    If Valor >= 0 Then
        pQuantidade = Valor
    Else
        Err.Raise Number:=1001, Description:="A quantidade não pode ser negativa"
    End If
End Property

O procedimento Property Let inclui uma validação e só armazenará o valor caso seja maior ou igual a zero. Isso impede que nosso objeto tenha quantidade negativa. Caso venha um valor inválido, um erro é gerado com Err.Raise. Caso não conheça ou não entenda este método, visite meu décimo segundo artigo, que explica o objeto Err.

Acrescente o código logo abaixo do Debug.Print em TesteClasse e depure, depois altere o valor para negativo e veja o que acontece:

    Produto.Quantidade = 50
    Debug.Print "Quantidade do produto: " & Produto.Quantidade

Ainda temos duas propriedades a serem criadas no exemplo. A propriedade Preco é bem similar à Quantidade, também precisando de uma validação para impedir números negativos. Neste caso também vamos impedir o valor 0:

Public Property Get Preco() As Currency
    Preco = pPreco
End Property
Public Property Let Preco(Valor As Currency)
    If Valor > 0 Then
        pPreco = Valor
    Else
        Err.Raise Number:=1002, Description:="O preço deve ser um valor positivo"
    End If
End Property

A propriedade Codigo é similar à propriedade Nome, sem necessidade de consistência. Porém, se quisermos controlar o código, não devemos permitir que procedimentos externos modifiquem o valor. Desta forma, Property Let deverá ser declarada como private, ou seja, só o próprio módulo de classe poderá modificar esta propriedade:

Public Property Get Codigo() As Long
    Codigo = pCodigo
End Property
Private Property Let Codigo(Valor As Long)
    pCodigo = Valor
End Property

Se você tentar associar um valor a Produto.Codigo no procedimento TesteClasse irá receber uma mensagem de erro, alertando que esse componente não existe. Desta forma impedimos que o seja associado um valor que possa quebrar a ordem desejada ou até repetir um valor existente. A propriedade Codigo foi criada como somente leitura, ou seja, só podemos ler o valor, não modificar. É possível criar propriedades somente escrita, neste caso Property Get deverá ser privado enquanto Property Let deverá ser público.

Mas como iremos gerar um código para o produto? Vamos pensar um pouco: em que momento precisamos gerar um número novo para um produto? Somente quando for criar um novo. Portanto, o procedimento de adicionar produto deverá buscar o último valor utilizado, incrementar 1 e definir esse valor como o código do novo produto. Isso será feito mais adiante.

A propriedade Ativo, por sua vez, servirá para indicar se o produto foi "excluído" ou não. Usaremos essa propriedade como uma exclusão lógica, ou seja, a linha não será removida da tabela, apenas constará como um produto inativo. Imagine que tenha orçamentos existentes mencionando um determinado produto e então o apagamos da tabela: perderemos os vínculos de dados nos orçamentos e também toda a integridade de dados, levando a erros imprevisíveis. Por isso, é preferível fazer uma exclusão lógica (marcar como inativo) ao invés de física (apagar a linha do produto definitivamente). Como essa propriedade não deve ser acessada diretamente, apenas por procedimentos, teremos Property Get e Property Let declarados como privados:

Private Property Get Ativo() As Boolean
    Ativo = pAtivo
End Property
Private Property Let Ativo(Valor As Boolean)
    pAtivo = Valor
End Property

Até aqui terminamos a criação das propriedades, agora passaremos aos métodos. Mas antes, criaremos uma planilha para colocar os produtos. Crie uma planilha Produtos e renomeie o valor da propriedade (Name) para plProdutos. Se não souber como fazer isso veja meu terceiro artigo. Crie um cabeçalho para os dados: Código, Nome, Quantidade, Preço e Ativo. Em seguida, entre no Gerenciador de nomes (tecla de atalho Ctrl + F3) e crie uma área nomeada dinâmica chamada tbProdutos com a seguinte fórmula:

=DESLOC(Produtos!$A$1;0;0;CONT.VALORES(Produtos!$A:$A);5)

Note que nessa área nomeada foi incluído o cabeçalho. Isso garante que a quantidade mínima de linhas seja 1, o que impede de acontecer erros por não ter nenhuma linha com produto.

Há dois métodos de classe especiais: Class_InitializeClass_Terminate. Como os próprios nomes sugerem, eles se referem à inicialização e à finalização da classe, respectivamente. Na inicialização podemos definir valores iniciais a uma classe. Poderíamos definir um valor inicial para as propriedades, mas não iremos usar esses métodos neste artigo. Acrescente o procedimento a seguir no final do módulo da classe:

Public Sub Adicionar()

    Dim Produtos As Range
    Dim Linha As Long

    Set Produtos = plProdutos.Range("tbProdutos")
    Linha = Produtos.Rows.Count + 1

    If Produtos.Rows.Count = 1 Then ' Se só existe a linha do cabeçalho
        Codigo = 1
    Else
        Codigo = Produtos.Cells(Produtos.Rows.Count, 1).Value + 1
    End If

    Produtos.Cells(Linha, 1).Value = Codigo
    Produtos.Cells(Linha, 2).Value = Nome
    Produtos.Cells(Linha, 3).Value = Quantidade
    Produtos.Cells(Linha, 4).Value = Preco
    Produtos.Cells(Linha, 5).Value = True

End Sub

Por fim, no módulo de teste (onde há a sub-rotina TesteClasse) acrescente o código a seguir:

Sub TesteAdicionar()

    Dim Produto As clsProduto
    Set Produto = New clsProduto

    Produto.Nome = "Teste"
    Produto.Quantidade = 50
    Produto.Preco = 10
    Produto.Adicionar

End Sub

Não precisamos acrescentar um valor para a propriedade Ativo porque ao adicionar ele será sempre Verdadeiro (não faz muito sentido incluir um produto inativo). Depure esse código, acompanhando cada passo do processo. Veja como os valores das propriedades são adicionados e lidos. Observe como grava uma linha na tabela. Repita o processo algumas vezes para acrescentar algumas linhas, veja como incrementa o código. Remova algumas linhas e acrescente mais para ver o resultado.

O próximo código será o último mais um, mas se o usuário alterar a ordem das linhas pode gerar duplicidade de código. Por isso é preciso de um procedimento que ordene a tabela de produtos antes de incluir um novo. Podemos criar esse procedimento na própria classe como private, para que somente a própria classe execute. Adicione esta rotina no módulo da classe:

Private Sub OrdenarTabela()

    Dim Produtos As Range
    Set Produtos = plProdutos.Range("tbProdutos")

    plProdutos.Sort.SortFields.Clear
    plProdutos.Sort.SortFields.Add Key:=Produtos.Cells(1, 1), _
        SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
   
    With plProdutos.Sort
        .SetRange Produtos
        .Header = xlYes
        .MatchCase = False
        .Orientation = xlTopToBottom
        .SortMethod = xlPinYin
        .Apply
    End With

End Sub

Em seguida, acrescente a chamada desse procedimento dentro de Adicionar, logo após a declaração de variáveis, desta forma:

    Dim Produtos As Range
    Dim Linha As Long

    OrdenarTabela
   
    Set Produtos = plProdutos.Range("tbProdutos")
    Linha = Produtos.Rows.Count + 1

Altere a ordem de alguns produtos e experimente adicionar um novo. Você verá que o método Adicionar passa a ordenar a tabela antes de acrescentar um produto novo a lista, impedindo o uso de um código já existente.

Podemos também incluir um método para pesquisar se um determinado código existe na tabela, obtendo os valores do produto caso positivo ou deixando tudo em branco caso contrário. Veja o código abaixo:

Public Sub Pesquisar(Valor As Long)
   
    Dim Produtos As Range
    Dim Linha As Long

    Set Produtos = plProdutos.Range("tbProdutos")

    Codigo = 0
    Nome = ""
    Quantidade = 0
    Preco = 0.01    ' O preço deve ser sempre positivo

    For Linha = 2 To Produtos.Rows.Count
        If Produtos.Cells(Linha, 1).Value = Valor Then
            Codigo = Produtos.Cells(Linha, 1).Value
            Nome = Produtos.Cells(Linha, 2).Value
            Quantidade = Produtos.Cells(Linha, 3).Value
            Preco = Produtos.Cells(Linha, 4).Value
            Ativo = Produtos.Cells(Linha, 5).Value
            Exit For
        End If
    Next

End Sub

Antes de iniciar a pesquisa, as propriedades do objeto Produto são zerados, para que um eventual conteúdo anterior não interfira na validação da pesquisa. A estrutura de repetição começa com valor 2 porque a primeira linha do intervalo tbProdutos é o cabeçalho. Podemos criar um procedimento para testar o funcionamento da pesquisa. Coloque o código abaixo no módulo de teste:

Sub TestePesquisar()

    Dim Produto As clsProduto
    Set Produto = New clsProduto

    Produto.Pesquisar (3)
   
    If Produto.Codigo = 0 Then
        Debug.Print "O código " & Produto.Codigo & " não foi encontrado"
    Else
        Debug.Print "Codigo: " & Produto.Codigo
        Debug.Print "Nome: " & Produto.Nome
        Debug.Print "Quantidade: " & Produto.Quantidade
        Debug.Print "Preco: " & Produto.Preco
    End If

End Sub

Crie vários produtos com valores diferentes e execute o procedimento. A área de Verificação imediata mostrará se o produto existe ou não na tabela. Caso exista, mostrará os valores das propriedades, caso contrário dará uma mensagem informativa.

O método para alteração é similar ao de pesquisar. Com base nas propriedades, podemos localizar o código e atualizar o restante das propriedades com os valores atuais. Vejamos:

Public Sub Atualizar(Valor As Long)  
 
    Dim Produtos As Range
    Dim Linha As Long

    Set Produtos = plProdutos.Range("tbProdutos")

    For Linha = 2 To Produtos.Rows.Count
        If Produtos.Cells(Linha, 1).Value = Valor Then
            Produtos.Cells(Linha, 2).Value = Nome
            Produtos.Cells(Linha, 3).Value = Quantidade
            Produtos.Cells(Linha, 4).Value = Preco
            Exit For
        End If
    Next

End Sub

Para testar, vamos utilizar o seguinte procedimento:

Sub TesteAtualizar()

    Dim Produto As clsProduto
    Set Produto = New clsProduto

    Produto.Pesquisar (6)
   
    Produto.Quantidade = 25
    Produto.Preco = 18
   
    Produto.Atualizar (Produto.Codigo)

End Sub

Neste teste pesquisamos um produto, alteramos algumas propriedades e depois atualizamos o mesmo produto para que a tabela reflita as alterações desejadas. Depure para compreender o funcionamento.

Por fim, vamos criar um método para desativar o produto:

Public Sub Desativar(Valor As Long)
   
    Dim Produtos As Range
    Dim Linha As Long
    Dim Encontrado As Boolean

    Set Produtos = plProdutos.Range("tbProdutos")
    Encontrado = False

    For Linha = 2 To Produtos.Rows.Count
        If Produtos.Cells(Linha, 1).Value = Valor Then
            Produtos.Cells(Linha, 5).Value = False
            Encontrado = True
            Exit For
        End If
    Next
   
    If Encontrado = False Then
        Err.Raise Number:=1003, Description:="Produto não encontrado"
    End If

End Sub

O procedimento irá procurar o código do produto e irá alterar o conteúdo da célula Ativo para Falso. Se o produto não for encontrado uma mensagem de erro aparecerá. Podemos testar o código com outro procedimento de teste:

Sub TesteDesativar()

    Dim Produto As clsProduto
    Set Produto = New clsProduto

    Produto.Desativar (1)

End Sub

Execute o código e veja a alteração feita na tabela. Experimente também um código não existente para ver a mensagem de erro. Se houver necessidade pode-se criar um método para reativar um produto, o que você deve fazer com muita facilidade.

Como utilizar essa classe que foi criada? Imagine um formulário de produtos, onde podemos criar, consultar, alterar e excluir produtos. Os eventos dos botões, onde estaria toda a lógica, pode fazer uma consistência dos dados (conferir se os dados essenciais estão preenchidos e se são válidos) para então chamar o método da classe que efetiva o cadastro ou a alteração na tabela de produtos. Em um outro formulário que for preciso obter o dado de algum produto, bastaria chamar o método da classe, sem necessidade de replicar o código que estaria no formulário de produtos.

As classes servem para encapsular um objeto e manter toda a inteligência acerca dele. No início pode parecer um pouco complicado trabalhar com classes, mas com o tempo você verá que pode facilitar muito a longo prazo. Da mesma forma que formulários, módulos de classe podem ser exportados para ser importados por outras planilhas e adaptados,  com poucas alterações, demandando menos tempo do que fazer todo o trabalho de novo.

Vimos até aqui como funcionam as propriedades das classes. Utilizamos propriedades com e sem consistência no Property Let, bem como propriedades somente leitura e totalmente privada. Criamos métodos para adicionar, pesquisar, alterar e desativar produtos, assim como procedimentos para testar o funcionamento da classe. Também criamos um método privado da classe. Acredito que este conteúdo apresentado dá uma boa base para que você possa criar suas próprias classes.

Sempre que for criar uma classe crie também um módulo exclusivo para testes, de forma que você possa verificar se suas propriedades e métodos estão funcionando adequadamente, como fiz aqui. Manter um módulo separado para testes evita confusão com o código que será de fato usado, que deverá estar em outro módulo. Assim que estiver tudo testado e funcionando, o módulo de testes pode ser excluído se quiser.

Deixe seu comentário informando se conseguiu absorver como funcionam as classes no Excel. Diga se houve dificuldade de entender alguma coisa e se vai começar a usar classes em projetos futuros.

Pedro Martins


Formado em Tecnologia em Eletrônica Digital, já trabalhou como artefinalista, eletrotécnico, programador de CLP (para máquinas industriais) e analista de sistemas em sistema bancário, programando em COBOL.
Mexe com computadores e programação desde a segunda metade dos anos 1980, quando teve um MSX e aprendeu a programar em BASIC. É a favor da disseminação do conhecimento.





Catálogo de aulas (NOVIDADE)

Criei um catálogo de aulas para ajudar você em seus estudos. Acesse clicando na imagem abaixo ou clique aqui.


Postagem Anterior Próxima Postagem