VBA - Artigo 029 - Usando formulários com classe – parte 1: classe


Usando formulários com classe – parte 1: classe

O título deste artigo tem duplo sentido de forma intencional. Aqui veremos como usar formulários com classe usando classes. Se você ainda não leu o artigo sobre classes ou quer revisá-lo antes de continuar, segue o link. Usar classes é fazer uso do que a programação orientada a objetos tem de melhor.

Antes de mais nada, é preciso entender como projetar para que esse processo seja usado de forma eficiente. Precisamos definir a função de cada um: o que o formulário fará, o que a classe fará e quem vai se comunicar com quem. A imagem abaixo deixa isso de forma mais clara:


Através da imagem vemos que a classe fará a ponte entre o formulário e a planilha. Quais as vantagens de ser feito dessa forma?  Em primeiro lugar, o principal objetivo da programação orientada a objetos é reaproveitamento de código. Uma classe bem estruturada pode ser reaproveitada em outras partes do projeto ou em outros projetos. Um formulário se comunicando diretamente com a planilha dificilmente será reaproveitável. Já a classe poderá ser reaproveitada com pequenas alterações. Uma outra vantagem de usar classes é que você pode fazer a consistência de valores nela própria, não no formulário, mantendo assim toda a inteligência em um só lugar e aumentando a quantidade de código que poderá ser reaproveitado com a classe.

Suponha um exemplo onde você tem uma planilha com dados de clientes, fornecedores e funcionários. Em todos os casos teremos código, nome, documento de identificação (CPF/CNPJ) e mais alguns dados específicos de cada um. Os métodos e funções de cada classe provavelmente serão os mesmos: adicionar, pesquisar, alterar e desativar (exclusão lógica; bem melhor que exclusão física - explicarei mais adiante). Ao fazer uma classe com suas propriedades e métodos comunicando com a respectiva planilha você pode replicar boa parte do código para as outras classes, bastando alterar, adicionar ou remover algumas propriedades e refletindo essas alterações nos métodos e funções. Depois basta desenvolver o novo formulário e criar suas ligações com a respectiva classe. Além disso, há a possibilidade de testar a classe com sub-rotinas de teste, sem ter o formulário ainda pronto.

No caso de fazer um formulário comunicando diretamente com a planilha até dá para copiar o formulário e fazer as devidas alterações, mas será preciso muita atenção. O problema é que as pessoas acabam dando muita atenção à aparência primeiro e depois pensando na funcionalidade. Com uma classe específica pronta e devidamente testada, basta fazer as conexões do formulário com essa classe, sem muita necessidade de código no formulário, pois a maior parte das funcionalidades está na classe. Os eventos do formulário basicamente chamarão os métodos e as funções da classe ou definirão os valores de suas propriedades.

Vamos usar um exemplo simples de um formulário de funcionários, onde temos as seguintes propriedades: número registro do funcionário, nome completo, data de nascimento, CPF, cargo e salário. A planilha terá o nome Funcionários e o nome de código (codename) PlFuncionarios. Se não entendeu ou não sabe onde definir esses nomes, veja a imagem abaixo, é autoexplicativa:


Com o nome de código definido temos a vantagem de não precisar usar uma variável do tipo Sheet para fazer a referência à planilha. Além disso, caso o usuário renomeie a planilha (o nome que aparece para ele no Excel) o funcionamento não sofrerá interferência, já que a referência será feita via codename.

Crie a planilha e faça o cabeçalho, conforme a imagem abaixo:


Em seguida, crie um módulo de classe chamado clsFuncionario e acrescente o seguinte código das propriedades:

Private pRegistro       As Long             ' Registro do funcionário
Private pNome           As String           ' Nome completo
Private pDataNascimento As Date             ' Data de nascimento
Private pCPF            As String * 11      ' CPF
Private pCargo          As Integer          ' Código do cargo
Private pSalario        As Currency         ' Salário
Private pAtivo          As Boolean          ' Indicador de ativo
Private pInconsistencia As Boolean          ' Indicador de dado inconsistente
Private pLinha          As Long             ' Ponteiro de linha para PlFuncionarios
' Registro: somente leitura
Public Property Get Registro() As Long
    Registro = pRegistro
End Property
' Nome: leitura e escrita
Public Property Get Nome() As String
    Nome = pNome
End Property
Public Property Let Nome(Valor As String)
    pNome = Valor
    pInconsistencia = False
End Property
' DataNascimento: leitura e escrita
Public Property Get DataNascimento() As Date
    DataNascimento = pDataNascimento
End Property
Public Property Let DataNascimento(Valor As Date)
    pDataNascimento = Valor
    pInconsistencia = False
End Property
' CPF: leitura e escrita
Public Property Get CPF() As String
    CPF = pCPF
End Property
Public Property Let CPF(Valor As String)
    Valor = Trim(Replace(Replace(Valor, ".", ""), "-", ""))
    If Len(Valor) < 11 Then
        Valor = String(11 - Len(Valor), "0") & Valor
    End If
    If ValidarCPF(Valor) Then
        pCPF = Valor
        pInconsistencia = False
    Else
        pCPF = 0
        pInconsistencia = True
    End If
End Property
' Funcao: leitura e escrita
Public Property Get Cargo() As Integer
    Cargo = pCargo
End Property
Public Property Let Cargo(Valor As Integer)
    If Valor > 0 Then
        pCargo = Valor
        pInconsistencia = False
    Else
        pCargo = 0
        pInconsistencia = True
    End If
End Property
' Salario: leitura e escrita
Public Property Get Salario() As Currency
    Salario = pSalario
End Property
Public Property Let Salario(Valor As Currency)
    If Valor > 0 Then
        pSalario = Valor
        pInconsistencia = False
    Else
        pSalario = 0
        pInconsistencia = True
    End If
End Property
' Ativo: somente leitura
Public Property Get Ativo() As Boolean
    Ativo = pAtivo
End Property
' Inconsistencia: somente leitura
Public Property Get Inconsistencia() As Long
    Inconsistencia = pInconsistencia
End Property

Caso você tenha lido (ou relido) o artigo sobre classes deve entender todo o código (ou pelo menos uma boa parte). Se fez uma análise entre o enunciado e o código, percebeu que acrescentei duas propriedades booleanas somente leitura chamadas Ativo e Inconsistencia:

- Ativo, como o nome diz, servirá para dizer se o registro está ativo. Quando essa propriedade valer verdadeiro significa que o registro pode ser usado sem problemas, quando valer falso significa que não pode ser usada porque foi "excluída". Isso é uma exclusão lógica, ou seja, o registro está inativo e não deve ser utilizado. Exclusões físicas de dados costumam dar muita dor de cabeça para recuperar os dados porque sempre haverá algum usuário agindo como tal. A exclusão lógica previne que um registro seja de fato apagado, podendo aparecer em históricos e até mesmo ser recuperado, bastando alterar o status desta propriedade;

- Inconsistencia servirá para indicar ao formulário (ou qualquer outro objeto que instancie essa classe) que o valor enviado para a propriedade é inválido e não foi armazenado. Perceba que a variável pInconsistencia é atualizada em todas as Property Let, mantendo essa propriedade atualizada sempre que um novo valor for associado a alguma propriedade da classe. Nas propriedades que há validação de dados pInconsistencia pode receber verdadeiro ou falso, nas que não há validação recebe apenas falso para não interferir alguma checagem dessa propriedade mal colocada.

As propriedades Cargo e Salario validam se recebem valores positivos diferentes de zero, caso contrário ligam o indicador de inconsistência. Já Nome e DataNascimento apenas guardam o dado, sem efetuar consistências.

Note que a propriedade CPF está como uma string de 11 dígitos. Uma variável do tipo long armazena até 2.147.483.647, ou seja, não consegue armazenar todos os 11 dígitos. Na escrita (Let) é primeiro removido eventuais pontos e hífens, depois o conteúdo é transformado em 11 dígitos se necessário e por fim é enviado para a função ValidarCPF, que primeiramente deve checar se o valor recebido é numérico antes de conferir se o DAC é válido para o número.

A propriedade Registro é somente leitura, ou seja, não podemos associar um valor diretamente à propriedade. Como é que vamos definir um valor novo? Através de uma função chamada Pesquisa. Essa função, como o próprio nome entrega, fará a pesquisa na tabela pelo Registro, recebendo o valor na função e retornando verdadeiro ou falso. Se verdadeiro, também carrega os valores em suas respectivas propriedades. Ao contrário do que possa parecer, essa forma é feita para simplificar. Imagine que devêssemos definir o valor de Registro e depois executar a função Pesquisa:

Funcionario.Registro = txtRegistro.Value
Funcionario.Pesquisar

Colocando o Registro como parâmetro na função Pesquisar reduz uma linha:

Funcionario.Pesquisar(txtRegistro.Value)

Essa função Pesquisar deverá saber o intervalo atual da tabela de funcionários, guardar o número do registro (fornecido como parâmetro) na propriedade Registro da classe e pesquisar essa coluna para saber se o valor existe na base. Se existir, irá armazenar o número da linha em pLinha e compor as propriedades com seus respectivos valores, além de retornar o valor verdadeiro. Caso não exista, o valor de pLinha será o número da última linha mais um, já preparando a classe para adicionar um eventual novo registro e retornará falso. Compreendido o funcionamento da função, segue o código:

Public Function Pesquisar(Registro As Long) As Boolean
    Dim UltimaLinha As Long
    Dim Intervalo   As Range
    Dim Encontrado  As Range
    Dim NumCPF      As String
    pRegistro = Registro
    PlFuncionarios.Activate
    UltimaLinha = PlFuncionarios.Cells.Find("*", LookIn:=xlFormulas, _
        SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
    Set Intervalo = PlFuncionarios.Range(Cells(1, 1), Cells(UltimaLinha, 1))
    Set Encontrado = Intervalo.Find(pRegistro, LookIn:=xlFormulas, _
        LookAt:=xlWhole, SearchOrder:=xlByColumns)
    If Encontrado Is Nothing Then
        plinha = UltimaLinha + 1
        Pesquisar = False
    Else
        pLinha = Encontrado.Row
        pNome = PlFuncionarios.Cells(pLinha, 2).Value
        pDataNascimento = PlFuncionarios.Cells(pLinha, 3).Value
        NumCPF = PlFuncionarios.Cells(pLinha, 4).Value
        If Len(NumCPF) < 11 Then
            pCPF = String(11 - Len(NumCPF), "0") & NumCPF
        Else
            pCPF = NumCPF
        End If
        pCargo = PlFuncionarios.Cells(pLinha, 5).Value
        pSalario = PlFuncionarios.Cells(pLinha, 6).Value
        pAtivo = PlFuncionarios.Cells(pLinha, 7).Value
        Pesquisar = True
    End If
End Function

Você pode testar a função incluindo um funcionário manualmente e criando uma rotina de teste que exiba os dados quando encontrado ou exiba uma mensagem quando não encontrado. Pode fazer essa rotina como um exercício, vale a pena testar uma função ou método de classe tão logo esteja pronto.

Veja que é na pesquisa que leremos o valor do CPF, que é armazenado como número na planilha e, portanto, o Excel remove os zeros à esquerda. Para trazer o número com 11 dígitos é preciso fazer a verificação que está na rotina e preencher os zeros conforme a necessidade.

As outras funcionalidades precisarão que Pesquisar tenha sido executada, para os valores de Registro e pLinha estejam devidamente preenchidos. Caso contrário, é possível que pLinha tenha valor zero e uma mensagem de erro surgirá ao tentar acessar a linha zero da planilha, que não existe. Para prevenir isso, vamos criar uma função privada que retornará verdadeiro quando os valores sejam positivos e diferentes de zero, caso contrário retornará falso:

Private Function ValidarPosicao() As Boolean
    If pRegistro >= 0 And pLinha >= 0 Then
        ValidarPosicao = True
    Else
        ValidarPosicao = False
    End If
End Function

Note que essa função é privada, ou seja, somente será acessada internamente pela própria classe. Podemos então passar às outras funcionalidades. Poderíamos construir como métodos, que não retornam valores, mas vamos podemos construir como funções, retornando verdadeiro para indicar que processou conforme o esperado e falso para indicar que não processou por algum problema, como a função ValidarPosicao retornando falso (afinal, é para isso mesmo que ela foi criada). Segue o código dessas funções:

Public Function Adicionar() As Boolean
    If ValidarPosicao = False Then
        Adicionar = False
    Else
        PlFuncionarios.Cells(pLinha, 1).Value = pRegistro
        PlFuncionarios.Cells(pLinha, 2).Value = pNome
        PlFuncionarios.Cells(pLinha, 3).Value = pDataNascimento
        PlFuncionarios.Cells(pLinha, 4).Value = pCPF
        PlFuncionarios.Cells(pLinha, 5).Value = pCargo
        PlFuncionarios.Cells(pLinha, 6).Value = pSalario
        PlFuncionarios.Cells(pLinha, 7).Value = True
        Adicionar = True
    End If
End Function
Public Function Alterar() As Boolean
    If ValidarPosicao = False Then
        Alterar = False
    Else
        PlFuncionarios.Cells(pLinha, 2).Value = pNome
        PlFuncionarios.Cells(pLinha, 3).Value = pDataNascimento
        PlFuncionarios.Cells(pLinha, 4).Value = pCPF
        PlFuncionarios.Cells(pLinha, 5).Value = pCargo
        PlFuncionarios.Cells(pLinha, 6).Value = pSalario
        Alterar = True
    End If
End Function
Public Function Ativar() As Boolean
    If ValidarPosicao = False Then
        Ativar = False
    Else
        PlFuncionarios.Cells(pLinha, 7).Value = True
        Ativar = True
    End If
End Function
Public Function Desativar() As Boolean
    If ValidarPosicao = False Then
        Desativar = False
    Else
        PlFuncionarios.Cells(pLinha, 7).Value = False
        Desativar = True
    End If
End Function

Crie as rotinas de teste para as funções acima como exercício, colocando em um módulo exclusivo para esse fim. Ponha um nome como TesteClasseFuncionario. Tenha o hábito de sempre testar cada método ou função logo após ter codificado, isso garante que você testará com a lógica ainda fresca na cabeça, permitindo encontrar eventuais erros mais facilmente. Também recomendo manter o módulo de testes, para testar eventuais alterações futuras no código ou mesmo para ter rotinas de teste prontas quando replicar a classe em outro lugar.
.
Perceba que não inclui ainda a função ValidarCPF e por isso dará erro ao tentar associar um valor à propriedade CPF. Você pode codificar essa parte  como exercício (a forma de calcular o DAC é facilmente encontrado na internet) antes de seguir adiante. Você também exercitar criando um método privado para ordenar a tabela e chamá-lo no final da função AdicionarOs códigos seguem abaixo, mas tente fazer como exercício antes:


Function ValidarCPF(CPF As String) As Boolean
    If IsNumeric(CPF) Then
        If Len(CPF) < 11 Then
            CPF = String(11 - Len(CPF), "0") & CPF
        End If
        If Right$(CPF, 2) = CalcularDAC(CPF) Then
            ValidarCPF = True
        Else
            ValidarCPF = False
        End If
    Else
        ValidarCPF = False
    End If
End Function
Private Function CalcularDAC(CPF As String) As String
    Dim Soma    As Integer
    Dim Resto   As Integer
    Dim DAC1    As Integer
    Dim DAC2    As Integer
    Soma = 10 * CInt(Left(CPF, 1)) + 9 * CInt(Mid(CPF, 2, 1)) + 8 * _
        CInt(Mid(CPF, 3, 1)) + 7 * CInt(Mid(CPF, 4, 1)) + 6 * _
        CInt(Mid(CPF, 5, 1)) + 5 * CInt(Mid(CPF, 6, 1)) + 4 * _
        CInt(Mid(CPF, 7, 1)) + 3 * CInt(Mid(CPF, 8, 1)) + 2 * _
        CInt(Mid(CPF, 9, 1))
    Resto = Soma Mod 11
    If Resto = 0 Or Resto = 1 Then
        DAC1 = 0
    Else
        DAC1 = 11 - Resto
    End If
    Soma = 11 * CInt(Left(CPF, 1)) + 10 * CInt(Mid(CPF, 2, 1)) + 9 * _
        CInt(Mid(CPF, 3, 1)) + 8 * CInt(Mid(CPF, 4, 1)) + 7 * _
        CInt(Mid(CPF, 5, 1)) + 6 * CInt(Mid(CPF, 6, 1)) + 5 * _
        CInt(Mid(CPF, 7, 1)) + 4 * CInt(Mid(CPF, 8, 1)) + 3 * _
        CInt(Mid(CPF, 9, 1)) + 2 * DAC1
    Resto = Soma Mod 11
    If Resto = 0 Or Resto = 1 Then
        DAC2 = 0
    Else
        DAC2 = 11 - Resto
    End If
    CalcularDAC = CStr(DAC1) & CStr(DAC2)
End Function
Private Sub OrdenarTabela()
    Dim UltimaLinha As Long
    Dim Intervalo As Range
    PlFuncionarios.Activate
    UltimaLinha = PlFuncionarios.Cells.Find("*", LookIn:=xlFormulas, _
        SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
    Set Intervalo = PlFuncionarios.Range(Cells(1, 1), Cells(UltimaLinha, 7))
    With PlFuncionarios.Sort
        .SortFields.Clear
        .SortFields.Add Range("A1")
        .Header = xlYes
        .SetRange Intervalo
        .Apply
    End With
End Sub

Até aqui vimos apenas a classe, que aparentemente está pronta - se você exercitou e fez as rotinas mencionadas acima. A parte do formulário segue em um artigo à parte para não deixar este tão extenso e para facilitar a consulta quando necessário (se quiser informação sobre classe vem aqui, se quiser sobre formulário vai no outro).

Para dúvidas sobre o artigo, comentários ou sugestões, utilize os comentários abaixo. Até o próximo artigo!

Pedro Martins


Pós-graduando em Business Intelligence e Big Data pela Faculdade Impacta de Tecnologia. Formado em Tecnologia em Eletrônica Digital com Ênfase em Microprocessadores


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