Check textbox for future date

Jimmypop

Well-known Member
Joined
Sep 12, 2013
Messages
753
Office Version
  1. 365
Platform
  1. Windows
Good day again

I would like to when user has entered a date (in any format) inside a textbox that when exiting the textbox it checks whether the date is in the future and then displays a message. Also would it be then able to when exiting that the date be changed to show as format 12-Mar-20?

Please note that there are multiple textboxes where the checks needs to be done and they do not follow each other. They start at TextBox9 then go to 13 then 17 (always increasing by 4). I also cannot put them in a frame seeing as there are other textboxes which contain different info below and next to them.

Any ideas?
 
This post is exactly what I need, but events are not working with my file .xlsb. I see in Immediate window "Connection set for..." but nothing happen when click on my textboxes.

I exported my userform to file uploaded by @Jimmypop and it's works fine. Any ideas what is wrong with my file?
 
Upvote 0

Excel Facts

Get help while writing formula
Click the italics "fx" icon to the left of the formula bar to open the Functions Arguments dialog. Help is displayed for each argument.
This post is exactly what I need, but events are not working with my file .xlsb. I see in Immediate window "Connection set for..." but nothing happen when click on my textboxes.

I exported my userform to file uploaded by @Jimmypop and it's works fine. Any ideas what is wrong with my file?

Hi mgruszec,

I had the same issue with this class, I did try everything but still not working in my sheet but was OK in downloaded sheet. Then I tried my own new excel file and improt the class and it is working. So the solution is to export this class from downloaded file and then import into yours. Works like a charm and not even for textbox, but I changed the code and works for generic comboboxes too.

thansk to all, hope this will help someone in furute :-)

cheers
 
Upvote 0
Hi Jaafar,
I am having some trouble,
It debug.prints when the connection is set but it will not when i enter in or update it.


Are you able to advise what i might be doing wrong.
the textbox I am targeting is in a frame.
 
Upvote 0
Quick update:
I downloaded the file and exported the code over and it worked,
Not sure why it didnt work when i copied it over from the forum
 
Upvote 0
Quick update:
I downloaded the file and exported the code over and it worked,
Not sure why it didnt work when i copied it over from the forum

Follow these steps :

1- Add a new UserForm to your vbproject and add to it a few TextBoxes for testing the events.
2- Place this code in the UserForm Module:
Code:
Option Explicit

Private Sub UserForm_Initialize()

    Dim oCtrl As Control, oClass As CTextBoxEvents
 
    For Each oCtrl In Me.Controls
        If TypeOf oCtrl Is msforms.TextBox Then
            Set oClass = New CTextBoxEvents
            oClass.SetControlEvents(oCtrl) = True
        End If
    Next oCtrl
End Sub


3-Add a new Class Module to your vbproject and give it the name of CTextBoxEvents . This class module will be the one responsible for sinking the textbox(es) events.

4- Place the following code in the Class Module :
VBA Code:
Option Explicit

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

#If VBA7 Then
    Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, lpiid As GUID) As Long
    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 IIDFromString Lib "ole32.dll" (ByVal lpsz As Long, lpiid As GUID) As Long
    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


Private oTextBox As Object

Public Property Let SetControlEvents(ByVal TextBox As Object, ByVal SetEvents As Boolean)

    Const S_OK = &H0
    Static lCookie As Long
    Dim tIID As GUID
  
        Set oTextBox = TextBox
        If IIDFromString(StrPtr("{00020400-0000-0000-C000-000000000046}"), tIID) = S_OK Then
            Call ConnectToConnectionPoint(Me, tIID, SetEvents, TextBox, lCookie)
            If lCookie Then
                Debug.Print "Connection set for: " & TextBox.Name
            Else
                Debug.Print "Connection failed for: " & TextBox.Name
            End If
        End If

End Property

Public Sub OnEnter()
Attribute OnEnter.VB_UserMemId = &H80018202
    'Attribute OnEnter.VB_UserMemId = &H80018202
    Debug.Print "[ENTER EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub OnExit(ByVal Cancel As MsForms.ReturnBoolean)
Attribute OnExit.VB_UserMemId = &H80018203
    ' Attribute OnExit.VB_UserMemId = &H80018203
    Debug.Print "[EXIT EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub BeforeUpdate(ByVal Cancel As MsForms.ReturnBoolean)
Attribute BeforeUpdate.VB_UserMemId = &H80018201
    'Attribute BeforeUpdate.VB_UserMemId = &H80018201
    Debug.Print "[BEFORE_UPDATE EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub AfterUpdate()
Attribute AfterUpdate.VB_UserMemId = &H80018200
    'Attribute AfterUpdate.VB_UserMemId = &H80018200
    Debug.Print "[AFTER_UPDATE EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

You will notice the Attribute statement in RED in each of the event procedures which won't let the project compile... This is not an issue. Just ignore that for the moment and carry on with the remaining steps.

5- Save your workbook

6- Export the class module as a CLS file to disk.

7- Delete the Class Module from the vbe project.

8- Import the exported cls file from step 6 (the RED Event Procedure Attribute statements are now invisible in the imported class module)

9- Save the workbook again.

10- Now, load the UserForm and the textboxes events should now be successfully sinked.

Although the above steps are required only once, they are still cumbersome and confusing. I started investigating a more straightforward alternative (albeit more difficult) lately but I never finished it.

Regards.


PS: Although, the above userform code works fine, it is technically incomplete as it should properly disconnect the classes from the connectable textboxes once the userform is closed. This should be easily done with little code.
 
Last edited:
Upvote 0
Thanks for laying all that out for me, on top of banging out the extravagant code in the first place.

I managed to get it to work by exporting the class from the existing workbook you had available for download a few hours ago.

Hats off to you and many thanks.
 
Upvote 0
Follow these steps :

1- Add a new UserForm to your vbproject and add to it a few TextBoxes for testing the events.
2- Place this code in the UserForm Module:
Code:
Option Explicit

Private Sub UserForm_Initialize()

    Dim oCtrl As Control, oClass As CTextBoxEvents
 
    For Each oCtrl In Me.Controls
        If TypeOf oCtrl Is msforms.TextBox Then
            Set oClass = New CTextBoxEvents
            oClass.SetControlEvents(oCtrl) = True
        End If
    Next oCtrl
End Sub


3-Add a new Class Module to your vbproject and give it the name of CTextBoxEvents . This class module will be the one responsible for sinking the textbox(es) events.

4- Place the following code in the Class Module :
VBA Code:
Option Explicit

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

#If VBA7 Then
    Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, lpiid As GUID) As Long
    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 IIDFromString Lib "ole32.dll" (ByVal lpsz As Long, lpiid As GUID) As Long
    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


Private oTextBox As Object

Public Property Let SetControlEvents(ByVal TextBox As Object, ByVal SetEvents As Boolean)

    Const S_OK = &H0
    Static lCookie As Long
    Dim tIID As GUID
 
        Set oTextBox = TextBox
        If IIDFromString(StrPtr("{00020400-0000-0000-C000-000000000046}"), tIID) = S_OK Then
            Call ConnectToConnectionPoint(Me, tIID, SetEvents, TextBox, lCookie)
            If lCookie Then
                Debug.Print "Connection set for: " & TextBox.Name
            Else
                Debug.Print "Connection failed for: " & TextBox.Name
            End If
        End If

End Property

Public Sub OnEnter()
Attribute OnEnter.VB_UserMemId = &H80018202
    'Attribute OnEnter.VB_UserMemId = &H80018202
    Debug.Print "[ENTER EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub OnExit(ByVal Cancel As MsForms.ReturnBoolean)
Attribute OnExit.VB_UserMemId = &H80018203
    ' Attribute OnExit.VB_UserMemId = &H80018203
    Debug.Print "[EXIT EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub BeforeUpdate(ByVal Cancel As MsForms.ReturnBoolean)
Attribute BeforeUpdate.VB_UserMemId = &H80018201
    'Attribute BeforeUpdate.VB_UserMemId = &H80018201
    Debug.Print "[BEFORE_UPDATE EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub AfterUpdate()
Attribute AfterUpdate.VB_UserMemId = &H80018200
    'Attribute AfterUpdate.VB_UserMemId = &H80018200
    Debug.Print "[AFTER_UPDATE EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

You will notice the Attribute statement in RED in each of the event procedures which won't let the project compile... This is not an issue. Just ignore that for the moment and carry on with the remaining steps.

5- Save your workbook

6- Export the class module as a CLS file to disk.

7- Delete the Class Module from the vbe project.

8- Import the exported cls file from step 6 (the RED Event Procedure Attribute statements are now invisible in the imported class module)

9- Save the workbook again.

10- Now, load the UserForm and the textboxes events should now be successfully sinked.

Although the above steps are required only once, they are still cumbersome and confusing. I started investigating a more straightforward alternative (albeit more difficult) lately but I never finished it.

Regards.


PS: Although, the above userform code works fine, it is technically incomplete as it should properly disconnect the classes from the connectable textboxes once the userform is closed. This should be easily done with little code.
This is great stuff, Jaafar!

I've got a few follow up questions for you.

1. You mention that there should be code to disconnect the classes from the connectable textboxes once the userform is closed. Could you provide an example? I'm assuming that it could be handled in the UserForm_Terminate routine by setting the class objects to nothing, but your method of connecting the textboxes is completely new to me so I'm not confident that I'm thinking the right way.

2. Is there a way to use these OnEnter, OnExit, BeforeUpdate, and AfterUpdate to be assigned to different collections of textboxes similar to the method of having different collections for events that are handled in the standard way (i.e. KeyPress, Change, etc.). Even further, is there a way to have some of your events handled by all textboxes and other events only be handled by different iterations of your events. For example, let's say I wanted every textbox to be handled the same for the OnEnter event, but I want the OnExit event to be handled by different types of textboxes that I group together in collections. One OnExit event for all textboxes that are for entering dates, and another OnExit event to be handled for all textboxes that are for single lines of text.

My current solution is to assign a tag to the control at design time and then do a select case in the OnExit event based upon the Ctrl.Tag property. While this works, I'm wondering if there's a more elegant solution.

Here's the code I have for the userform to assign textboxes to your solution for the OnEnter, etc. events as well as assigning textboxes to collections to be able to handle KeyPress events by group. The userform is simple, and is only for testing purposes.

VBA Code:
Option Explicit

Private TextBoxControlColl As Collection
Private DateBoxControlColl As Collection


Private Sub UserForm_Initialize()

    Set TextBoxControlColl = New Collection
    Set DateBoxControlColl = New Collection
    
    Dim oClass As clsTextBoxEvents
    Dim oCtrl As Control
    
    For Each oCtrl In Me.Controls
        
        Select Case TypeName(oCtrl)
            
            Case "TextBox"
                
                Set oClass = New clsTextBoxEvents
                oClass.SetControlEvents(oCtrl) = True
                
                Select Case oCtrl.Name
                
                    Case "TextBox1", "TextBox2", "TextBox3"
                        
                        oCtrl.Tag = "Single Text"
                        Set oClass.TextGroup = oCtrl
                        TextBoxControlColl.Add oClass
                        
                    Case "TextBox4"
                    
                        oCtrl.Tag = "Date"
                        Set oClass.DateGroup = oCtrl
                        DateBoxControlColl.Add oClass
                        
                End Select
                
            Case Else
            
        End Select
        
    Next oCtrl
    
End Sub

Here's the class module code using your method and adding some KeyPress events for the TextGroup and DateGoup

VBA Code:
Option Explicit

Public WithEvents TextGroup As MSForms.TextBox
Public WithEvents DateGroup As MSForms.TextBox

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

#If VBA7 Then
    Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, lpiid As GUID) As Long
    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 IIDFromString Lib "ole32.dll" (ByVal lpsz As Long, lpiid As GUID) As Long
    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

Private oTextBox As Object

Public Property Let SetControlEvents(ByVal TextBox As Object, ByVal SetEvents As Boolean)

    Const S_OK = &H0
    Static lCookie As Long
    Dim tIID As GUID
  
    Set oTextBox = TextBox
    
    If IIDFromString(StrPtr("{00020400-0000-0000-C000-000000000046}"), tIID) = S_OK Then
    
        Call ConnectToConnectionPoint(Me, tIID, SetEvents, TextBox, lCookie)
        
        If lCookie Then
        
            Debug.Print "Connection set for: " & TextBox.Name
            
        Else
        
            Debug.Print "Connection failed for: " & TextBox.Name
            
        End If
        
    End If

End Property

Public Sub OnEnter()
    'Attribute OnEnter.VB_UserMemId = &H80018202
    Debug.Print "[ENTER EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value
            
End Sub

Public Sub OnExit(ByVal Cancel As MSForms.ReturnBoolean)
    'Attribute OnExit.VB_UserMemId = &H80018203
    Debug.Print "[EXIT EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
    'Attribute BeforeUpdate.VB_UserMemId = &H80018201
    Debug.Print "[BEFORE_UPDATE EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub AfterUpdate()
    'Attribute AfterUpdate.VB_UserMemId = &H80018200
    Debug.Print "[AFTER_UPDATE EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value
    
End Sub

Private Sub TextGroup_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)

    Debug.Print "Text Box Character in is: " & Chr(KeyAscii)
    
End Sub

Private Sub DateGroup_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)

    Debug.Print "Date Box Character in is: " & Chr(KeyAscii)
    
End Sub

3. Is there any modifications that would need to be made if a userform's textboxes were created at runtime instead of design time? I know you've offered solutions in the past about assigning events to textbox controls created at runtime.

Thanks a bunch!
 
Upvote 0
1-
You mention that there should be code to disconnect the classes from the connectable textboxes once the userform is closed. Could you provide an example? I'm assuming that it could be handled in the UserForm_Terminate routine by setting the class objects to nothing, but your method of connecting the textboxes is completely new to me so I'm not confident that I'm thinking the right way.
It is the way the class was initially designed that makes it a bit counter-intuitive. One way to properly disconnect the textboxes while maintaining the original class layout is by making the TextBox argument in the SetControlEvents Property an Optional argument and storing the class instances in a module level collection (or an array).

That way, you can still call the SetControlEvents Property and clearing all the connections of all the textboxes in just one go (without needing to pass any of the textboxes).

This illustrates the above idea:

1- The class code will become as follows:
VBA Code:
Option Explicit

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

#If VBA7 Then
    Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, lpiid As GUID) As Long
    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 IIDFromString Lib "ole32.dll" (ByVal lpsz As Long, lpiid As GUID) As Long
    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

Private oTextBox As Object

Public Property Let SetControlEvents(Optional ByVal TextBox As Object, ByVal SetEvents As Boolean)

    Const S_OK = &H0
    Static lCookie As Long
    Dim tIID As GUID
 
    If Not TextBox Is Nothing Then
        Set oTextBox = TextBox
    End If
    If IIDFromString(StrPtr("{00020400-0000-0000-C000-000000000046}"), tIID) = S_OK Then
        If ConnectToConnectionPoint(Me, tIID, SetEvents, oTextBox, lCookie) = S_OK Then
            Debug.Print oTextBox.Name & IIf(SetEvents, " connected ", " disconnected") & " successfully."
        Else
          Debug.Print "Connection failed for: " & oTextBox.Name
        End If
    End If
 
End Property

Public Sub OnEnter()
    'Attribute OnEnter.VB_UserMemId = &H80018202
    Debug.Print "[ENTER EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub OnExit(ByVal Cancel As MSForms.ReturnBoolean)
    ' Attribute OnExit.VB_UserMemId = &H80018203
    Debug.Print "[EXIT EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
    'Attribute BeforeUpdate.VB_UserMemId = &H80018201
    Debug.Print "[BEFORE_UPDATE EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub AfterUpdate()
    'Attribute AfterUpdate.VB_UserMemId = &H80018200
    Debug.Print "[AFTER_UPDATE EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Private Sub Class_Terminate()
    Debug.Print "Class instance treminated and memory released properly."
End Sub


2- The UserForm code becomes as follows:
VBA Code:
Option Explicit

Private oEventsCollection As Collection

Private Sub UserForm_Initialize()

    Dim oCtrl As Control, oClass As CTextBoxEvents
 
    For Each oCtrl In Me.Controls
        If TypeOf oCtrl Is MSForms.TextBox Then
            Set oClass = New CTextBoxEvents
            If oEventsCollection Is Nothing Then
                Set oEventsCollection = New Collection
            End If
            oEventsCollection.Add oClass
            oClass.SetControlEvents(oCtrl) = True
        End If
    Next oCtrl
 
End Sub

Private Sub UserForm_Terminate()

    Dim oCTextInstance As CTextBoxEvents
 
    For Each oCTextInstance In oEventsCollection
        oCTextInstance.SetControlEvents = False
    Next oCTextInstance
 
    Set oCTextInstance = Nothing
    Set oEventsCollection = Nothing
 
End Sub



2-
One OnExit event for all textboxes that are for entering dates, and another OnExit event to be handled for all textboxes that are for single lines of text.
Adding a tag to the textboxes to later be able to distinguish them in the OnExit event would work... This is easier.
Alternatively, you could create two seperate class modules. One for textboxes with dates and one for textboxes with single line text. The two seperate classes will have the same layout.


3-
Is there any modifications that would need to be made if a userform's textboxes were created at runtime instead of design time?
No modification is needed in the class... Here is a code example for connecting textboxes created at runtime leaving the class as is:
VBA Code:
Option Explicit

Private oEventsCollection As New Collection

Private Sub UserForm_Initialize()
    Dim oTextBox As MSForms.TextBox, i As Long
    For i = 0& To 3&
        With Controls.Add("Forms.TextBox.1", "TextBox" & i + 1, True)
            .Width = 100&
            .Height = 20&
            .Top = 30& * i + 10&
            .Left = 10&
        End With
    Next i
End Sub

Private Sub UserForm_Terminate()
    Dim oCTextInstance As CTextBoxEvents
    For Each oCTextInstance In oEventsCollection
        oCTextInstance.SetControlEvents() = False
    Next oCTextInstance
    Set oCTextInstance = Nothing
    Set oEventsCollection = Nothing
End Sub

Private Sub UserForm_AddControl(ByVal Control As MSForms.Control)
    Dim oClass As CTextBoxEvents
    If TypeOf Control Is MSForms.TextBox Then
        Set oClass = New CTextBoxEvents
        oEventsCollection.Add oClass
        oClass.SetControlEvents(Control) = True
    End If
End Sub
 
Last edited:
Upvote 0
1-

It is the way the class was initially designed that makes it a bit counter-intuitive. One way to properly disconnect the textboxes while maintaining the original class layout is by making the TextBox argument in the SetControlEvents Property an Optional argument and storing the class instances in a module level collection (or an array).

That way, you can still call the SetControlEvents Property and clearing all the connections of all the textboxes in just one go (without needing to pass any of the textboxes).

This illustrates the above idea:

1- The class code will become as follows:
VBA Code:
Option Explicit

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

#If VBA7 Then
    Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, lpiid As GUID) As Long
    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 IIDFromString Lib "ole32.dll" (ByVal lpsz As Long, lpiid As GUID) As Long
    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

Private oTextBox As Object

Public Property Let SetControlEvents(Optional ByVal TextBox As Object, ByVal SetEvents As Boolean)

    Const S_OK = &H0
    Static lCookie As Long
    Dim tIID As GUID
 
    If Not TextBox Is Nothing Then
        Set oTextBox = TextBox
    End If
    If IIDFromString(StrPtr("{00020400-0000-0000-C000-000000000046}"), tIID) = S_OK Then
        If ConnectToConnectionPoint(Me, tIID, SetEvents, oTextBox, lCookie) = S_OK Then
            Debug.Print oTextBox.Name & IIf(SetEvents, " connected ", " disconnected") & " successfully."
        Else
          Debug.Print "Connection failed for: " & oTextBox.Name
        End If
    End If
 
End Property

Public Sub OnEnter()
    'Attribute OnEnter.VB_UserMemId = &H80018202
    Debug.Print "[ENTER EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub OnExit(ByVal Cancel As MSForms.ReturnBoolean)
    ' Attribute OnExit.VB_UserMemId = &H80018203
    Debug.Print "[EXIT EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
    'Attribute BeforeUpdate.VB_UserMemId = &H80018201
    Debug.Print "[BEFORE_UPDATE EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Public Sub AfterUpdate()
    'Attribute AfterUpdate.VB_UserMemId = &H80018200
    Debug.Print "[AFTER_UPDATE EVENT] " & oTextBox.Name & vbTab & "Value: " & vbTab & oTextBox.Value

End Sub

Private Sub Class_Terminate()
    Debug.Print "Class instance treminated and memory released properly."
End Sub


2- The UserForm code becomes as follows:
VBA Code:
Option Explicit

Private oEventsCollection As Collection

Private Sub UserForm_Initialize()

    Dim oCtrl As Control, oClass As CTextBoxEvents
 
    For Each oCtrl In Me.Controls
        If TypeOf oCtrl Is MSForms.TextBox Then
            Set oClass = New CTextBoxEvents
            If oEventsCollection Is Nothing Then
                Set oEventsCollection = New Collection
            End If
            oEventsCollection.Add oClass
            oClass.SetControlEvents(oCtrl) = True
        End If
    Next oCtrl
 
End Sub

Private Sub UserForm_Terminate()

    Dim oCTextInstance As CTextBoxEvents
 
    For Each oCTextInstance In oEventsCollection
        oCTextInstance.SetControlEvents = False
    Next oCTextInstance
 
    Set oCTextInstance = Nothing
    Set oEventsCollection = Nothing
 
End Sub



2-

Adding a tag to the textboxes to later be able to distinguish them in the OnExit event would work... This is easier.
Alternatively, you could create two seperate class modules. One for textboxes with dates and one for textboxes with single line text. The two seperate classes will have the same layout.


3-

No modification is needed in the class... Here is a code example for connecting textboxes created at runtime leaving the class as is:
VBA Code:
Option Explicit

Private oEventsCollection As New Collection

Private Sub UserForm_Initialize()
    Dim oTextBox As MSForms.TextBox, i As Long
    For i = 0& To 3&
        With Controls.Add("Forms.TextBox.1", "TextBox" & i + 1, True)
            .Width = 100&
            .Height = 20&
            .Top = 30& * i + 10&
            .Left = 10&
        End With
    Next i
End Sub

Private Sub UserForm_Terminate()
    Dim oCTextInstance As CTextBoxEvents
    For Each oCTextInstance In oEventsCollection
        oCTextInstance.SetControlEvents() = False
    Next oCTextInstance
    Set oCTextInstance = Nothing
    Set oEventsCollection = Nothing
End Sub

Private Sub UserForm_AddControl(ByVal Control As MSForms.Control)
    Dim oClass As CTextBoxEvents
    If TypeOf Control Is MSForms.TextBox Then
        Set oClass = New CTextBoxEvents
        oEventsCollection.Add oClass
        oClass.SetControlEvents(Control) = True
    End If
End Sub

Amazing stuff again, Jaafar!

Thank you for the quick replay and the elegant solutions.

In the class module, I have created an Enum to capture the different styles of input boxes instead of the tag property and encapsulated a private variable for the class to be able to reference the different type of input box throughout the project. Now, I can validate the date in the BeforeUpdate event based upon my TextBoxType property. I'm also able to use the OnEnter function to highlight all of the text that may exist in the textbox. Additionally, I have the ability to capture the KeyPress event for each type of Textbox to try to control the data entry.

Next, I'll experiment with using data masks for each TextBoxType.

Overall, this is exactly what I've been looking for and will make userform development so much easier. Thank you very much!
 
Upvote 0

Forum statistics

Threads
1,223,886
Messages
6,175,191
Members
452,616
Latest member
intern444

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top