Gestão dos eventos “Sair” e “Entrar” da Caixa de Texto criados dinamicamente

Dezembro 2016

Infelizmente, os módulos de classe não suportam os eventos Sair, Entrar, AfterUpdate e BeforeUpdate das caixas de texto.


Soluções alternativas

Existem várias soluções mas, aqui, mostraremos somente três delas.

Rotina de validação

A primeira consiste em criar uma rotina de validação, rotina chamada por quantos Sub eventos você tiver de caixa de texto. Fonte (em inglês). Por exemplo, para três caixas de texto:

Private Sub TextBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
  Validation Cancel
End Sub
Private Sub TextBox2_Exit(ByVal Cancel As MSForms.ReturnBoolean)
  Validation Cancel
End Sub
Private Sub TextBox3_Exit(ByVal Cancel As MSForms.ReturnBoolean)
  Validation Cancel
End Sub

' Sub commune de validation
Private Sub Validation(Optional ByVal Cancel As MSForms.ReturnBoolean)
  Dim minha CaixaDeTexto As MSForms.TextBox
  Set minha CaixaDeTexto = Me.ActiveControl
  If Not IsNumeric(monTextBox) Then
    MsgBox "Favor digitar um valor numérico", vbExclamation, CaixaDeTexto.Name
    Cancel = True
  End If
End Sub

O problema desse código, é que ele não tem nada de dinâmico. Você precisará saber, com antecedência, o número de caixas de texto.

Acompanhamento do Formulário de usuário

Fonte (em francês) ou Saiba mais. O código “Silkyroad”, não será descrito aqui, mas ele está disponível na rede. O princípio é o seguinte: um procedimento CibleFocus faz um loop desde o começo do Formulário de usuário (UserForm) e inicia eventos durante uma mudança do ActiveControl. De um modo geral, este procedimento ajuda o usuário graças a um loop do DoEvents. É aí que mora o perigo. Um loop “DoEvents” pode criar problemas na CPU, veja aqui o tutorial DoEvents problemas e soluções

Criação dinâmica de procedimentos de eventos

O princípio aqui é escrever os códigos dos procedimentos de eventos para cada criação de Caixa de Texto. Então, retomemos as duas primeiras soluções para combina-las:

Dim Ct As Control, j As Long, i As Integer
For i = 1 To 10
  Set Ct = Usf.Controls.Add("forms.TextBox.1", "TextBox" & i, True)
  With Usf.CodeModule
    j = .CountOfLines
    .InsertLines j + 1, "Sub TextBox" & i & "_Click()"
    .InsertLines j + 2, "Validation Cancel"
    .InsertLines j + 3, "End Sub"
  End With
Next i

O problema reside, principalmente, em três pontos: o primeiro, é que a criação do formulário de usuário também se torna dinâmica; o segundo, é que a remoção destas linhas de código para fechar o formulário de usuário é viável, mas não é fácil; na realidade, devemos procurar a primeira linha para excluir o seu conteúdo; e o terceiro, é o risco de bloqueio se o limite das linhas for atingido no módulo do UserForm, o que é raro.

Gestão dos eventos

Existe uma função do Windows API para estabelecer uma conexão entre um controle e uma instância de classe. Esta função, ConnectToConnectionPoint é usada para criar uma ligação entre um objeto VBA (origem do evento) e uma Classe (gerenciador do evento). Os eventos são apenas métodos (Sub) da Classe as quais atribuímos um Attribute VB_UserMemId. É a presença deste atributo no método (Sub) da Classe que o torna um gerenciador de eventos.

Function ConnectToConnectionPoint

Declaração a ser colocada no cabeçalho do módulo de Classe:

Private Declare Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" _
                 (ByVal punk As stdole.IUnknown, _
                 ByRef riidEvent As GUID, _
                 ByVal fConnect As Long, _
                 ByVal punkTarget As stdole.IUnknown, _
                 ByRef pdwCookie As Long, _
                 Optional ByVal ppcpOut As Long) As Long

As configurações obrigatórias para passar para esta função são:
punk As stdole.Iunknown ==> Trata-se do gerenciador de eventos, sua instância de Classe
riidEvent As GUID ==> Um identificador global único (cf: AQUI)
fConnect As Long ==> Define o tipo de conexão: VERDADE (-1) = estabelece a conexão, FALSO (0) = desconexão
punkTarget As stdole.Iunknown ==> Seu objeto VBA, no seu caso, seu TextBox
pdwCookie ==> um número único que identifica esta conexão (valor de retorno da função)
Exemplo de uso:

Option Explicit
    Private Type GUID
        Data1 As Long
        Data2 As Integer
        Data3 As Integer
        Data4(0 To 7) As Byte
    End Type
     
    #If VBA7 And Win64 Then
        Private Declare PtrSafe Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" _
                 (ByVal punk As stdole.IUnknown, _
                 ByRef riidEvent As GUID, _
                 ByVal fConnect As Long, _
                 ByVal punkTarget As stdole.IUnknown, _
                 ByRef pdwCookie As Long, _
                 Optional ByVal ppcpOut As LongPtr) As Long
    #Else
        Private Declare Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" _
                 (ByVal punk As stdole.IUnknown, _
                 ByRef riidEvent As GUID, _
                 ByVal fConnect As Long, _
                 ByVal punkTarget As stdole.IUnknown, _
                 ByRef pdwCookie As Long, _
                 Optional ByVal ppcpOut As Long) As Long
    #End If
Sub Connection(Obj As Object)
Dim cGuid As GUID, C As Boolean, Cook As Long
        With cGuid
            .Data1 = &H20400
            .Data4(0) = &HC0
            .Data4(7) = &H46
        End With
        C = True
        ConnectToConnectionPoint Me, cGuid, C, Obj, Cook, 0&
End Sub

Aqui se conecta a instância da Classe Me ao objeto Obj. Ele retornou um valor do tipo Long na variável Cook.

Observação:

A declaração do Private Type GUID, obrigatório para criar o identificador exclusivo global. A declaração dupla dentro de um teste IF da função ConnectToConnectionPoint para tornar esse código válido em sistemas de 32 e/ou 64 bits.

Sua função é receber uma mensagem do evento da fonte dos eventos; encontrar o gerenciador de eventos para esta mensagem do evento e realizar o processamento relativo para o gerenciador de eventos encontrado.

Os Métodos de eventos

Você pode nomeá-los como quiser. Mas pense em manter um nome pertinente para a manutenção de seu código. Pessoalmente, eu chamei de Entrada, Saída, AntesDaAtualização e DepoisDaAtualização. Cada um destes procedimentos deve ter seu próprio Attribute VB_UserMemId. Veja abaixo a lista dos 4 que nos interessam:

Enter: &H80018202 = -2147384830
Exit: &H80018203 = -2147384829
BeforeUpdate: &H80018201 = -2147384831
AfterUpdate : &H80018200 = -2147384832

Para cada procedimento, você deve colocar, na linha de declaração, o UserMemId correspondente. É isto que vai permitir ao VBA saber que evento deve ser acionado. Para fazê-lo, em primeiro lugar, cole o seguinte código em seu módulo de Classe:

 Public Sub Entrada()
'Attribute Entrada.VB_UserMemId = -2147384830
        
    End Sub
    
    Public Sub Saída(ByVal Cancel As MSForms.ReturnBoolean)
'Attribute Saída.VB_UserMemId = -2147384829
        
    End Sub
    
    Public Sub AntesDaAtualização(ByVal Cancel As MSForms.ReturnBoolean)
'Attribute AntesDaAtualização.VB_UserMemId = -2147384831
        
    End Sub
     
    Public Sub ApresMiseAjour()
'Attribute DepoisDaAtualização.VB_UserMemId = -2147384832
        
    End Sub

Em segundo lugar, exporte o módulo de classe (Arquivo/Exportar). Em terceiro, abra-o a partir do seu editor de texto preferido (bloco de notas). Em quarto, tire o comentário das linhas contendo “Attribute VB_UserMemId”. Em quinto, salve. E, em sexto, importe para a sua pasta (sem esquecer de remover a anterior). Você agora tem 4 procedimentos de evento para a sua classe de caixa de texto.

Classe - Criação

Para realizar o que queremos, precisamos de uma variável que contenha todas as nossas caixas de texto e que elas sejam "legíveis", tanto no módulo de classe quanto no UserForm (formulário de usuário).

Variável Tabela Pública

Vamos, então, declarar uma variável de tabela, digitada como nossa classe, em Público em um módulo padrão. Para fazê-lo, insira um módulo padrão (Inserção/ Módulo) e cole este código:
Option Explicit

    Public meusTB(18) As New cTextbox
    'constantes permitem mudar a cor de fundo das Caixas de Texto
    Public Const COR_INICIAL As Long = &H80000005
    Public Const COR_ALTERADA As Long = &H80000003

Bem simples também, nós declaramos uma variável de tabela que deve conter um máximo de 19 controles, digitado As cTextbox. Este tipo não existe ainda. Para isso, vamos ter que criar o nosso módulo de Classe.

Classe - Início do código

Insira um novo módulo de Classe, nomeie o cTextbox e ponha o Type GUID e a declaração do ConnectToConnectionPoint:
Option Explicit
 
    Private Type GUID
        Data1 As Long
        Data2 As Integer
        Data3 As Integer
        Data4(0 To 7) As Byte
    End Type
     
    #If VBA7 And Win64 Then
        Private Declare PtrSafe Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" _
                 (ByVal punk As stdole.IUnknown, _
                 ByRef riidEvent As GUID, _
                 ByVal fConnect As Long, _
                 ByVal punkTarget As stdole.IUnknown, _
                 ByRef pdwCookie As Long, _
                 Optional ByVal ppcpOut As LongPtr) As Long
    #Else
        Private Declare Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" _
                 (ByVal punk As stdole.IUnknown, _
                 ByRef riidEvent As GUID, _
                 ByVal fConnect As Long, _
                 ByVal punkTarget As stdole.IUnknown, _
                 ByRef pdwCookie As Long, _
                 Optional ByVal ppcpOut As Long) As Long
    #End If

Pronto. Nós criamos nossa classe, que nos permitiu digitar a nossa variável de tabela.

UserForm - Código de chamada

Você verá como é simples. Nós vamos usar o procedimento de evento do formulário de usuário:
Private Sub UserForm_Initialize()
. Neste procedimento, teremos que alimentar a nossa variável de tabela Pública com todos os controles que quisermos ver reagir aos eventos Entrada, Saída, etc.

Atenção, saiba que nós também precisamos passar por controles, que eu chamo de containers. Na realidade, a saída de uma caixa de texto colocada dentro de um Frame (quadro) é diferente da saída de uma caixa de texto colocada diretamente no UserForm (formulário do usuário). Neste caso, a saída é controlada pelo evento “Exit” do container (Multi-página, Página ou Frame) e não pela “Exit” da Caixa de Texto. Então, vamos ter que passa-las para a classe. Além disso, para os nossos testes, vamos construir, dinamicamente, controles em um formulário de usuário.

Para testar vários casos, vamos criar caixas de texto diretamente no formulário de usuário, caixas de texto em um frame, caixas de texto nas páginas de uma multi-página e caixas de texto nos próprios frames situados em outros quadro. Para isso, insira um formulário de usuário em uma nova pasta e coloque os seguintes códigos no seu módulo:

====UserForm - Inicialização===

'código que se ativa durante a inicialização do UserForm
    Private Sub UserForm_Initialize()
Chamada do procedimento de criação dinâmica dos controles
        Call Creation_Dinâmica_Dos_Controles
'ADIÇÃO DE TEXTBOX
        meusTB(0).Add Me.Controls("TextBox1")
        'Para o TextBox2: nenhuma particularidade
        meusTB(1).Add Me.Controls("Textbox3")
        meusTB(2).Add Me.Controls("TextBox4")
        meusTB(3).Add Me.Controls("TextBox5")
        meusTB(4).Add Me.Controls("TextBox6")
        meusTB(5).Add Me.Controls("TextBox7")
        'Para o TextBox8: nenhuma particularidade
        meusTB(6).Add Me.Controls("TextBox9")
        meusTB(7).Add Me.Controls("TextBox10")
        meusTB(8).Add Me.Controls("TextBox11")
        meusTB(9).Add Me.Controls("TextBox12")
        meusTB(10).Add Me.Controls("TextBox13")
        meusTB(11).Add Me.Controls("TextBox14")
'ADIÇÃO DE "CONTAINERS"
        meusTB(12).Add Me.Controls("Frame1")
        meusTB(13).Add Me.Controls("Frame2")
        meusTB(14).Add Me.Controls("Frame3")
        meusTB(15).Add Me.Controls("Frame4")
        meusTB(16).Add Me.Controls("MultiPage1").Página1
        meusTB(17).Add Me.Controls("MultiPage1").Página2
        meusTB(18).Add Me.Controls("MultiPage1")
    End Sub

Criação_Dinâmica_Dos_Controles

    Private Sub Criação_Dinâmica_Dos_Controles()
        Dim Ct As Control, Frm As Frame, Frame2 As Frame, Frame3, As Frame, Multi As MultiPage
 
        Me.Move Me.Left, Me.Top, 470, 540
        Set Ct = Me.Controls.Add("forms.Label.1", "Lab1", True)
        Ct.Move 10, 10, 40, 20
        Ct.Caption = "SOBRENOME"
        Set Ct = Me.Controls.Add("forms.TextBox.1", "TextBox1", True)
        Ct.Move 50, 10, 100, 20
        Set Ct = Me.Controls.Add("forms.Label.1", "Lab2", True)
        Ct.Move 10, 30, 40, 20
        Ct.Caption = "Nome"
        Set Ct = Me.Controls.Add("forms.TextBox.1", "TextBox2", True)
        Ct.Move 50, 30, 100, 20
        Set Ct = Me.Controls.Add("forms.Label.1", "Lab3", True)
        Ct.Move 10, 50, 40, 20
        Ct.Caption = "Seguro Social"
        Set Ct = Me.Controls.Add("forms.TextBox.1", "TextBox3", True)
        Ct.Move 50, 50, 100, 20
        Set Frm = Me.Controls.Add("forms.Frame.1", "Frame1", True)
        With Frm
            .Move 160, 10, 200, 100
            .Caption = "Estado civil"
            Set Ct = .Controls.Add("forms.Label.1", "Lab4", True)
            Ct.Move 10, 10, 40, 20
            Ct.Caption = "Data de Nascimento"
            Set Ct = .Controls.Add("forms.TextBox.1", "TextBox4", True)
            Ct.Move 50, 10, 70, 20
            Set Ct = .Controls.Add("forms.Label.1", "Lab5", True)
            Ct.Move 10, 30, 40, 20
            Ct.Caption = “Hora”
            Set Ct = .Controls.Add("forms.TextBox.1", "TextBox5", True)
            Ct.Move 50, 30, 70, 20
            Set Ct = .Controls.Add("forms.Label.1", "Lab6", True)
            Ct.Move 10, 50, 40, 20
            Ct.Caption = "CIDADE"
            Set Ct = .Controls.Add("forms.TextBox.1", "TextBox6", True)
            Ct.Move 50, 50, 100, 20
        End With
        Set Ct = Me.Controls.Add("forms.CommandButton.1", "Bouton1", True)
        Ct.Move 370, 10, 80, 20
        Ct.Caption = "BOTÕES PARA"
        Set Ct = Me.Controls.Add("forms.CommandButton.1", "Bouton2", True)
        Ct.Move 370, 35, 80, 20
        Ct.Caption = "TESTAR SAÍDA"
        Set Ct = Me.Controls.Add("forms.CommandButton.1", "Bouton3", True)
        Ct.Move 370, 60, 80, 20
        Ct.Caption = "POR CLIQUE"
        Set Multi = Me.Controls.Add("forms.Multipage.1", "Multipage1", True)
        With Multi
            .Move 10, 120, 400, 150
            With .Pages(0)
                .Caption = "ENDEREÇO 1"
                Set Ct = .Controls.Add("forms.Label.1", "Lab7", True)
                Ct.Move 10, 10, 40, 20
                Ct.Caption = "NÚMERO"
                Set Ct = .Controls.Add("forms.TextBox.1", "TextBox7", True)
                Ct.Move 50, 10, 30, 20
                Set Ct = .Controls.Add("forms.Label.1", "Lab8", True)
                Ct.Move 10, 30, 40, 20
                Ct.Caption = "RUA"
                Set Ct = .Controls.Add("forms.TextBox.1", "TextBox8", True)
                Ct.Move 50, 30, 300, 60
            End With
            With .Pages(1)
                .Caption = "ENDEREÇO 2"
                Set Ct = .Controls.Add("forms.Label.1", "Lab9", True)
                Ct.Move 10, 10, 40, 20
                Ct.Caption = "CEP"
                Set Ct = .Controls.Add("forms.TextBox.1", "TextBox9", True)
                Ct.Move 50, 10, 50, 20
                Set Ct = .Controls.Add("forms.Label.1", "Lab10", True)
                Ct.Move 10, 30, 40, 20
                Ct.Caption = "CIDADE"
                Set Ct = .Controls.Add("forms.TextBox.1", "TextBox10", True)
                Ct.Move 50, 30, 150, 20
            End With
        End With
        Set Frm = Me.Controls.Add("forms.Frame.1", "Frame2", True)
        With Frm
            .Caption = "Tudo digital"
            .Move 35, 300, 400, 200
            Set Ct = .Controls.Add("forms.TextBox.1", "TextBox11", True)
            Ct.Move 10, 10, 240, 20
            Set Frm2 = .Controls.Add("forms.Frame.1", "Frame3", True)
        End With
        With Frm2
            .Move 10, 35, 370, 140
            Set Ct = .Controls.Add("forms.TextBox.1", "TextBox12", True)
            Ct.Move 10, 10, 240, 20
            Set Frm3 = .Controls.Add("forms.Frame.1", "Frame4", True)
        End With
        With Frm3
            .Move 10, 35, 350, 100
            Set Ct = .Controls.Add("forms.TextBox.1", "TextBox13", True)
            Ct.Move 10, 10, 100, 20
            Set Ct = .Controls.Add("forms.TextBox.1", "TextBox14", True)
            Ct.Move 10, 30, 100, 20
        End With
        Set Frm = Nothing
        Set Frm2 = Nothing
        Set Frm3 = Nothing
        Set Ct = Nothing
        Set Multi = Nothing
    End Sub

Destruição das instâncias de classe

Como criamos, na inicialização, 19 instâncias de classe, vamos ter que destruí-los. Para isso, vamos esperar o fim da utilização do nosso formulário de usuário, ou seja, seu evento Terminate. O que nos dá o código:

Private Sub UserForm_Terminate()
        Dim i As Long
        For i = LBound(meusTB) To UBound(meusTB)
            meusTB(i).Clear
        Next i
        Erase meusTB
    End Sub

Ao chegar neste ponto, se você tentar abrir o formulário de usuário, vai se deparar com um erro: o método não existe. Na verdade, para adicionar nossos controles à classe, nós utilizamos o método Add. Para remover as instâncias de classe, usamos o método Clear. Ora, nenhum Sub ou função, em nosso módulo de classe, leva esses nomes.

A Classe – código chamado

As variáveis

Abaixo das declarações do tipo GUID e da Função ConnectToConnectionPoint, vamos declarar as 4 seguintes variáveis:

 Private iCook As Long 'valor retornado pela conexão
   Private iObjet As Object 'Nosso controle
   Private iNom As String 'o nome do nosso controle
   Private iFocus As Boolean 'uma variável que ajuda a saber se ele tem o “Focus” ou não

Para alimentar a variável iFocus a partir do código da classe e poder ler as propriedades iNome e iFocus, precisamos criar propriedades em Leitura e/ou Leitura - Escrita. Os códigos:

  Public Property Let Focus(booF As Boolean)
        iFocus = booF
    End Property
    
     Public  Property Get Focus() As Boolean
        Focus = iFocus
    End Property
    
     Public Property Get Nom() As String
        Nome = iNome
    End Property

Para por aqui pois há muitos tutoriais na internet sobre esse assunto.

A conexão

Para criar (e destruir) nossas conexões, vamos criar, em Private, uma função de conexão:

Private Sub ConnectEvent(ByVal Connect As Boolean)
    Dim cGuid As GUID
        With cGuid
            .Data1 = &H20400
            .Data4(0) = &HC0
            .Data4(7) = &H46
        End With
        ConnectToConnectionPoint Me, cGuid, Connect, iObjet, iCook, 0&
    End Sub

Agora, então, vamos definir os dois métodos para adicionar e remover instâncias da nossa classe.

Os métodos de adição e remoção

O método Add permitirá a conexão da classe ao objeto:

Public Sub Add(ByVal Obj As Object)
        Set iObjet = Obj 'variável Objeto alimentado de nosso controle (do lado da chamada: .Add Me.Controls("TextBox1"))
        iNome = Obj.Name 'o nome do nosso controle armazenado em sua instância de classe
        Call ConnectEvent(True) 'connection : True = connection
    End Sub

O método Clear vai desconectar o objeto:
    Public Sub Clear()
        If (iCook <> 0) Then Call ConnectEvent(False)
        Set iObjet = Nothing
    End Sub

Os procedimentos de eventos

Seus códigos foram dados acima. Lembre-se do Attribute xxxxxx.VB_UserMemId
O objetivo destes códigos vai ser colorir uma caixa de texto durante a digitação e impedir a saída, caso esteja vazia. Basicamente, dispomos destes 4 Sub (que devem permanecer Públicos!!!)

    Public Sub Entrada()
'Attribute Entrada.VB_UserMemId = -2147384830
        
    End Sub
    
    Public Sub Saída(ByVal Cancel As MSForms.ReturnBoolean)
'Attribute Saída.VB_UserMemId = -2147384829
        
    End Sub
    
    Public Sub AntesDaAtualização(ByVal Cancel As MSForms.ReturnBoolean)
'Attribute AntesDaAtualização.VB_UserMemId = -2147384831
        
    End Sub
     
    Public Sub DepoisDaAtualização()
'Attribute DepoisDaAtualização.VB_UserMemId = -2147384832
        
    End Sub

Os dois últimos, AntesDaAtialização e DepoisDaAtualização não nos interessam para o exercício. Vamos deixá-los de fora. No procedimento de Entrada, é preciso atribui-lhe o “focus” (ajustar sua variável Focus = True) e, se for uma Caixa de Texto, colorir o fundo. O código tornar-se-á simplesmente:

    Public Sub Entrada()
'Attribute Entrada.VB_UserMemId = -2147384830
        Focus = True
        If TypeOf iObjet Is MSForms.TextBox Then iObjet.BackColor = COR_VISITADA 
End Sub

No procedimento de Saída, vamos precisar diferenciar se trata-se de uma Caixa de Texto ou de um container e, se for uma Caixa de Texto, verificar se ela está vazia e se ela é impedida de sair, se não, mudar sua cor e sua variável Focus. Se for um container, verificar se o seu activeControl é uma Caixa de Texto, se sim, iniciar o procedimento de evento de saída dessa Caixa de Texto. No caso do container vamos precisar de um procedimento que verifique o ActiveControl e inicie o procedimento de evento.

    Private Sub Container_Saída(ByVal Cancel As MSForms.ReturnBoolean)
        'o código ativado em "Frame_Exit" ou "MultiPage_Exit"
        Dim Container As Object
        If TypeOf iObjeto Is MSForms.MultiPage Then
            Set Container = iObjeto.SelectedItem
        Else
            Set Container = iObjeto
        End If
        If Container.ActiveControl Is Nothing Then Exit Sub
        If TypeOf Container.ActiveControl Is MSForms.TextBox Then
   'inicie o procedimento de evento de saída do Activecontrol
            CallByName ItemByName(Container.ActiveControl.Name), "Saída", VbMethod, Cancel
        End If
    End Sub

Para iniciar o procedimento de evento da Caixa de Texto vom o “Focus”, utilizamos aqui CallByName. CallByName usa como parâmetro a instância da classe que deve ser chamada, o nome do procedimento a ser ativado, o método e os eventuais parâmetros a serem passados no procedimento. Aqui, teremos que encontrar a instância de classe certa. Para isso, vamos usar uma função que afivele todas as instâncias de classe. Assim sendo, nós fizemos bem de declarar a nossa variável de tabela de controle em Público. Então, a função é a seguinte:

  Private Function ItemByName(Nome As String) As cTextbox
    Dim i As Integer
        For i = LBound(meusTB) To UBound(meusTB)
            If meusTB(i).Nome = Nome Then
                Set ItemByName = meusTB(i): Exit Function
            End If
        Next i
    End Function


E aqui, então, fortalecido com estes dois métodos, o procedimento de Saída:

    Public Sub Saída(ByVal Cancel As MSForms.ReturnBoolean)
'Attribute Saída.VB_UserMemId = -2147384829
        If TypeOf iObjeto Is MSForms.MultiPage Or TypeOf iObjeto Is MSForms.Page Or TypeOf iObjeto Is MSForms.Frame Then
            Call Container_ Saída(Cancel)
        Else
            If iObjeto.Text = "" Then Cancel = True
        End If
        Focus = CBool(Cancel)
        If Not Focus Then
            If TypeOf iObjeto Is MSForms.TextBox Then iObjet.BackColor = COR_INICIAL
        End If
    End Sub

Conclusão

Fontes:

Implementation of the event handling by API: ConnectToConnectionPoint
TextBox Exit & Enter (em francês)

Uma pasta de exemplo está disponível aqui e um exemplo de uso aqui (ambos em francês).

Veja também :
Este documento, intitulado « Gestão dos eventos “Sair” e “Entrar” da Caixa de Texto criados dinamicamente »a partir de CCM (br.ccm.net) está disponibilizado sob a licença Creative Commons. Você pode copiar, modificar cópias desta página, nas condições estipuladas pela licença, como esta nota aparece claramente.