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_Initialize e Class_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:
Há dois métodos de classe especiais: Class_Initialize e Class_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.
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.