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.
Pedro! Parabéns pelo espetacular artigo! Uma pérola de conhecimento! Grato mais um vez pela contribuição!
ResponderExcluirValeu! E eu sou grato pelo espaço disponibilizado :)
ExcluirShow Pedro Martins e Alessandro Trovato.
ExcluirSou um entusiasta do Excel e vba (tenho fé que ainda serei um programador) e já há algum tempo que desenvolvo algumas aplicações. No entanto ainda não sabia trabalhar com classes seu artigo muito me ajudou, pois certamente vai facilitar minha vida de "programador".
obrigado por compartilhar o conhecimento.
Parabéns Pedro. A sua publicação é uma preciosidade, já que não encontramos muitas informações a respeito de Classes na internet.(Se houverem algumas referências, por gentileza, disponibilize-as).
ResponderExcluirSigo o canal do Trovato há bastante tempo, e consequentemente conheço suas publicações,que por sinal, são sempre muito completas e de grande valia.
Continuem disseminando o conhecimento,Senhores! Vocês não imaginam a quantidade de pessoas que evoluem com a boa vontade de vocês.
Um abraço e até a Próxima !
Obrigado pelo comentário :)
ExcluirRealmente é difícil encontrar algo sobre classes no VBA na internet, só se encontra em inglês e normalmente está explicado de forma rasa ou faltando informações importantes. Espero que este artigo seja usado como referência de aprendizado para muitos.
Abraço e até o próximo artigo
Muito bom. Já procurava algo sobre classe em vba há algum tempo e o seu artigo muito ajuda a quem está iniciando, como eu. Obrigado por partilhar o conhecimento.
ResponderExcluirMuito bom o artigo, bem claro e objetivo. Irá me ajudar muito em um novo projeto.
ResponderExcluirExcelente site
ResponderExcluirBom artigo , porem não conseguir rodar ainda
ResponderExcluirExcepcional Artigo! Tornou o difícil em simples! Finalmente compreendo melhor o conceito de classes! Obrigado por compartilhar conhecimento Mestre!
ResponderExcluirInfelizmente estou tendo erro ao executar a Sub abaixo:
ResponderExcluirMensagem de erro: O tipo definido pelo usuário não foi definido. Se alguém poder me ajudar.
Sub TesteClasse()
Dim Produto As clsProduto
Set Produto = New clsProduto
Produto.Nome = "Teste"
Debug.Print "Nomedo Produto: " & Produto.Nome
End Sub
Parabéns, Pedro, pelo excelente artigo!
ResponderExcluirAinda não sei exatamente como utilizar as classes, mas é um material muito rico e raro.
Muito Obrigado
Complementando...estou com dificuldade para entender como funciona a variável "valor". Como ela recebe a informação?
Excluir