Same Array/msgbox to display different messages

NatWally

New Member
Joined
Sep 19, 2018
Messages
26
Currently my array brings up three different msgboxes, as shown with the Functions 'Expired', 'Expiring', and 'NoTraining'. The array msgboxes display information based on whether a date is Expired (older than current date), Expiring (within 31 days), and is a date is missing (NoTraining).
No matter what, the msgboxes for the array will always come up, but will sometimes be blank (depending on the criteria in the SELECT CASE statement) and the following IF statement.
Does anyone know anyway of coding it so that if the msgboxes were to come up blank (if nothing fits the criteria), a different message will be shown in the box? I can't get the collection and boolean NoExpiredTraining to work properly bringing up an overall msgbox instead of the array msgboxes, so I need a message to appear in the Private Functions if there isn't going to be any data in the boxes (i.e. if the box would come up blank, then a "Everything okay" message)

This is my code:

Code:
[/FONT][/COLOR]
Sub Expire_New()


Dim arr()       As Variant
Dim msg(1 To 3) As String
Dim x           As Long
Dim nDx         As Long
Dim dDiff       As Long
LDays = 31


'I would recommend using a named sheet rather than
'ActiveSheet as this can change unexpectedly
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Support Staff")
With ws
    x = .Cells(.Rows.Count, TRAINING_DATE_COL).End(xlUp).Row
    arr = .Cells(21, 1).Resize(x - 20, 26).Value
End With


'I am a big fan of collections.  They make code easier to read
'and to implement.  The collection below will be scanned to
'see if there are any training dates that are set to expire within
'30 days or if there are people without any training
Dim colTrainingDate As Collection
Set colTrainingDate = CopyArrDimToCollection(arr, TRAINING_DATE_COL)


'This boolean will be used to control continued flow of the
'macro.  If NoExpiredTraining gets set to false, then there
'are people who must complete training.
'Dim NoExpiredTraining As Boolean: NoExpiredTraining = True


For x = LBound(arr, NAME_COL) To UBound(arr, NAME_COL)


    'Since every row requires a Name and Surname columns
    'to have data in them, let's check this first.
    'If a row doesn't have a name then skip it.
    If arr(x, NAME_COL) <> "" And arr(x, SURNAME_COL) <> "" Then


        'Always good practice to declare your variables/objects
        'relevant to where they will be used
        'vDx is an index used to loop through the collection of
        'Training Dates.  This is checking to see if any training
        'Dates are empty or less than 31 days from expiration
        Dim vDx As Variant
        For Each vDx In colTrainingDate
            If vDx = "" Then
                'blank date means needs training
                NoExpiredTraining = False
            ElseIf DateDiff("d", Date, vDx) < 31 Then
                'less than 31 days means needs training
                NoExpiredTraining = False
            End If
        Next


        'At this point you can determine if you want to continue
        'If there is no expired training, display the message and exit
        'the sub.
        If NoExpiredTraining Then
            'msg(4) = MsgBox("There are either no ...
            'is only used if want to do something based on
            'what button the user pressed.  Otherwise use
            'the Method form of MsgBox
            MsgBox "There are either no expired safeguarding certificates, or no certificate expiring within the next 31 days.", vbCritical, "Warning"
            Exit Sub
        Else
            'There is expired training.  Let's collect the status
            'of each individual
            If arr(x, TRAINING_DATE_COL) = "" Then
                'if the training date column is empty
                'put a really big default value in dDiff
                'otherwise you have to trap an error with DateDiff
                'and handle it
                dDiff = 100
            Else
                'training date column has a date value
                dDiff = DateDiff("d", Date, arr(x, TRAINING_DATE_COL))
            End If


            'Now let's see what the training status for the person is
            Select Case dDiff
                Case Is <= 0:   'Training is expired
                    msg(1) = Expired(msg(1), _
                          arr(x, NAME_COL), _
                          arr(x, 2), _
                          arr(x, TRAINING_DATE_COL))
                Case Is <= 31:  'Training is expiring
                    msg(2) = Expiring(msg(2), _
                          arr(x, NAME_COL), _
                          arr(x, 2), _
                          arr(x, TRAINING_DATE_COL), dDiff)
            End Select
            If Len(arr(x, 19)) = 0 And Len(arr(x, 1)) > 0 And Len(arr(x, 2)) > 0 Then
         msg(3) = NoTraining(msg(3), arr(x, 1), arr(x, 2), arr(x, 18))


  End If
  End If
  End If




 Next x
'Because of the Exit Sub statement above, the code bwlow
'will only execute if there are expired, expiring or missing
'training
For x = LBound(msg) To UBound(msg)
    msg(x) = Replace(msg(x), "@NL", vbCrLf)
    If Len(msg(x)) < 1024 Then
        MsgBox msg(x), vbExclamation, "Safeguarding Certificate Notification"
    Else
       MsgBox "String length for notification too long to fit into this MessageBox", vbExclamation, "Invalid String Length to Display"
    End If
Next x








Erase arr
Erase msg


End Sub


'***************************************************************************
'**
'** This fucntion copies all rows of data for the column specified into
'** a collection
Private Function CopyArrDimToCollection(ByRef mMultiDimArray() As Variant, _
                                    ByVal mColumnToCopy As Long) As Collection
Dim retVal As New Collection
Dim nDx As Long


For nDx = LBound(mMultiDimArray, 1) To UBound(mMultiDimArray, 1)
    retVal.Add mMultiDimArray(nDx, mColumnToCopy)
Next
Set CopyArrDimToCollection = retVal


End Function


Private Function Expired(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As Variant, ByRef var3 As Variant) As String


If Len(msg) = 0 Then msg = "Persons with EXPIRED Safeguading Certificates@NL@NL"


Expired = msg & "(@var3) @var1 @var2@NL"
Expired = Replace(Expired, "@var1", var1)
Expired = Replace(Expired, "@var2", var2)
Expired = Replace(Expired, "@var3", var3)




End Function


Private Function Expiring(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As Variant, ByRef var3 As Variant, ByRef d As Long) As String


If Len(msg) = 0 Then msg = "Persons with EXPIRING Safeguarding Certificates@NL@NL"


Expiring = msg & "(@var3) @var1 @var2 (@d days remaining)@NL"
Expiring = Replace(Expiring, "@var1", var1)
Expiring = Replace(Expiring, "@var2", var2)
Expiring = Replace(Expiring, "@var3", var3)
Expiring = Replace(Expiring, "@d", d)


End Function


Private Function NoTraining(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As Variant, ByRef var3 As Variant) As String


If Len(msg) = 0 Then msg = "SAFEGUARDING TRAINING NOT COMPLETED FOR @NL@NL"


NoTraining = msg & " @var1 @var2@NL"
NoTraining = Replace(NoTraining, "@var1", var1)
NoTraining = Replace(NoTraining, "@var2", var2)
NoTraining = Replace(NoTraining, "@var3", var3)


End Function

 
Not sure if that means a copy is forthcoming or if not, what it is you expect me to do with that info. Just to clarify, I'm saying I'm willing to look at said workbook to see if I can make it work for you, but not create my own in the hopes I can make it work with your code.
 
Upvote 0

Excel Facts

Who is Mr Spreadsheet?
Author John Walkenbach was Mr Spreadsheet until his retirement in June 2019.
My excel workbook on the Dropbox is fine for you to use, look at, test out, and edit the code. I think it is the way the SELECT CASE works (this probably isn't the right thing to use). The SELECT CASE of course only works if data satisfies that criteria, which is why I don't think this is the right thing. E.g. the 2nd message in the SELECT CASE will not come up if there is no data to be pulled through for the EXPIRED function. It will only come up with the message in the first part of the SELECT CASE if there is no data to pull through.
 
Upvote 0
For some reason, I could see no code or button in what I thought I previously downloaded. I was about to reply with that, but decided to download again and check. For some reason, it's there now. I will take a look but I may have to pm you for instructions/reminder on how to replicate your issue or what to look for.
 
Upvote 0
First, I think this is way more complicated than it needs to be what with dictionaries, collections and arrays. I'd be inclined to concatenate names & data to a string variable if test on the data warranted it for each person. That might just be my Access vba brain talking to me.

That said, I think you forced yourself down a path by splitting the training status into separate parts and by adding default values to an array/collection when there's nobody who fits the criteria. That means you are locked into always presenting a message for a status regardless of whether or not it has any members. As you have it, you can't test for this by examining the length of your message because you prepend it with a generic message. I will attempt to guide you to a fix as I don't know if what I have will do exactly as you wish.

In this code, I check var3 and as long as it's NONE, it doesn't complete the execution and the message box length remains 0 until it does. It may be that you want a default message where no names are captured. In that case, you'd construct that message in the function part that calls this code IF the message length remained 0 as a result of this code. What I think would be marginally better is that you pass Null to these vars (assuming they're variants) or "" if strings rather than a value such as "NONE". Should you ever alter what you pass and forget to fix the code, it will not work as intended since the passed value won't be NONE anymore. The idea would be to alter all 3 message box build functions, not just this one.

Code:
Private Function NoTraining(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As Variant, ByRef var3 As Variant) As String
If var3 <> "NONE" Then
   If Len(msg) = 0 Then msg = "SAFEGUARDING TRAINING NOT COMPLETED FOR: @NL@NL"
      NoTraining = msg & " @var1 @var2@NL"
      NoTraining = Replace(NoTraining, "@var1", var1)
      NoTraining = Replace(NoTraining, "@var2", var2)
      NoTraining = Replace(NoTraining, "@var3", var3)
   End If
End If

End Function
Also, the following changes would be required in the Select Case block:
Code:
Select Case msg(x)
  Case msg(1)
    If Len(msg(x)) & vbNullString > 0 Then
    'MsgBox "(If this box is blank, there is nothing Expired)" & vbCrLf & vbCrLf & msg(x), vbExclamation, "Safeguarding Certificate Notification"
    MsgBox msg(x), vbExclamation, "Safeguarding Certificate Notification"
  Case msg(2)
...and so on
This is where you'd construct the alternate message such as "There are no persons with expired training". All 3 Case blocks would need to be altered as such. I thought I was going to be able to simply alter the Select Case block by examining .TrainingExpired but quickly gave up trying to assess the value of a collection or dictionary item (or whatever it is). This is what I was getting at by saying that because each of these is its own entity/member, I could not. If they were property or string values rather than objects, then I believe I could have. Not sure you'll get the drift, so I'll leave it at that.

I left your old message box line in the Select Case block to show the difference. I do believe that in the code shown, if subsequent execution of the message box code does have a value other than NONE, it should still build the message box as intended. However, I wasn't able to validate that.

Hope this helps.
 
Last edited:
Upvote 0
This seems to have done the trick! - i just needed to add END IF into each part of the SELECT CASE.

I didn't need to change the bits in the NoTraining procedure.

If you check my code below, in fact it does something better now thanks to you. It no longer even comes up with some message boxes! For example, if there is only a date expired and nothing else, then only the EXPIRED will be called. The same goes for the rest of them, or if there are two of them - the other will not come up when it doesn't need to.

The new code is below if you want to test it out:

Code:
For x = LBound(msg) To UBound(msg)
        msg(x) = Replace(msg(x), "@NL", vbCrLf)
        If Len(msg(x)) < 1024 Then
        Select Case msg(x)
    Case msg(1)
        If Len(msg(x)) & vbNullString > 0 Then
            'MsgBox "(If this box is blank, there is nothing Expired)" & vbCrLf & vbCrLf & msg(x), vbExclamation, "Safeguarding Certificate Notification"
            MsgBox msg(x), vbExclamation, "Safeguarding Certificate Notification"
            End If
    Case msg(2)
        If Len(msg(x)) & vbNullString > 0 Then
            'MsgBox "(If this box is blank, there is nothing Expired)" & vbCrLf & vbCrLf & msg(x), vbExclamation, "Safeguarding Certificate Notification"
            MsgBox msg(x), vbExclamation, "Safeguarding Certificate Notification"
            End If
    Case msg(3)
        If Len(msg(x)) & vbNullString > 0 Then
            'MsgBox "(If this box is blank, there is nothing Expired)" & vbCrLf & vbCrLf & msg(x), vbExclamation, "Safeguarding Certificate Notification"
            MsgBox msg(x), vbExclamation, "Safeguarding Certificate Notification"
            End If
            End Select
    Else
         MsgBox "String length for notification too long to fit into this MessageBox", vbExclamation, "Invalid String Length to Display"
    End If

This is probably what you intended it to do, I just hadn't picked up on that!
 
Last edited:
Upvote 0
re - no messages for no people: that's what I was getting at in
It may be that you want a default message where no names are captured. In that case, you'd construct that message in the function part that calls this code IF the message length remained 0 as a result of this code.
I tried your version (sorry I forgot the End If) and see that it does seem to work but I'm not sure why because I thought this

...Then msg = "Persons with EXPIRED Safeguading Certificates:@NL@NL"
in Expired(msg(1) for example

was making the msg string "Persons with..." which meant it couldn't be zero length even if no one fell into the category. I guess I didn't fully grasp how it was constructed. I would test a few scenarios just in case.

Anyway, glad I was able to help.
 
Upvote 0
per your PM to review similar code for Visa checking...

What I think I would do to keep it simple now that you've added a condition for when there's nothing to report for Visas (unlike the other procedure):

Create a module level variable under Option Explicit
Code:
Option Explicit
Private bolNoExpires

Let's be sure it's True at the start:
Code:
Private Sub Visacheck_Click()

  bolNoExpires = True 'this must come first
  Visa_Check ActiveSheet, "Name"

End Sub

In each test, if there's something to report, set boolean to False
Code:
For Each Key In Visacheck.Keys
  Set personInquiryVisa = Visacheck(Key)
  If personInquiryVisa.VisaExpired _
  And personInquiryVisa.VisaDate <> DateSerial(1900, 1, 1) Then 'Training is expired
  bolNoExpires = False 'something came up, so flip this value from True if not already done
At the end of the x loop after Next x, put (or similar)
Code:
If bolNoExpires Then MsgBox "No visas are expired or are expiring in the next 31 days"
This means that if bolNoExpires remains True, no message boxes were created for expired or expiring. Note that now you cannot institute a default message like you were originally for training, but that shouldn't be a concern as it would be like going backwards.

Observations:
I found it a bit confusing at first when the name of a procedure (NoVisa) sounds like there is a problem (i.e. there isn't or soon won't be a valid visa), yet True is OK. Oh well...

More important:
I counted about 25 SET statements in just 1 procedure. Each one creates an object which sets aside a block of pc memory that stays reserved until you release it with SET something = Nothing (where something is whatever you previously SET), or close the file/app. I can't recall if it is the application or just the workbook that will release that memory. Much worse if it's the application as you could open several files without ever closing the app. Do this on 2 or 3 sheets in the same session and you're talking 50 - 75 blocks of memory that should have been released. The result can be pc performance issues across all applications because of lack of memory resources.

If this were Access, I wouldn't bother will all those collections, dictionaries and such objects, not only because of the complexity of what you have, but now you should be setting LOTS of objects to "Nothing". Maybe that's just my bias talking...

Hope the above works for you (it did for me for expiring, expired and no expired/expiring).
 
Upvote 0
Thanks for this.

Do I need to delete any of the code and then change it with this?
Not exactly sure which bits I need to keep or delete, or is the only code I need that shown above?

Do you have the full code that you used and I will replace it with what I have.
 
Last edited:
Upvote 0
That's a lot of code to dump in when you only need a few changes. If I missed something, I'll break something else in it. Let's try this but backup your file first:
At the top of your code module for teaching staff worksheet you have
Code:
Option Explicit

Private Sub TeachSafe_Click()
Expire_New ActiveSheet, "Name"

End Sub

Change it to
Code:
Option Explicit
[COLOR=#ff0000][B]Private bolNoExpires[/B][/COLOR]

Private Sub TeachSafe_Click()
Expire_New ActiveSheet, "Name"

End Sub
You have a procedure written as
Code:
Private Sub Visacheck_Click()
Visa_Check ActiveSheet, "Name"

End Sub

Change it to
Code:
Private Sub Visacheck_Click()
[COLOR=#ff0000][B]bolNoExpires = True[/B][/COLOR]
Visa_Check ActiveSheet, "Name"

End Sub

In Public Sub Visa_Check(ByRef ws As Worksheet, ByVal Name As String)
you have
Code:
For Each Key In Visacheck.Keys
  Set personInquiryVisa = Visacheck(Key)
  If personInquiryVisa.VisaExpired _
  And personInquiryVisa.VisaDate <> DateSerial(1900, 1, 1) Then 'Training is expired
change it to
Code:
For Each Key In Visacheck.Keys
  Set personInquiryVisa = Visacheck(Key)
  If personInquiryVisa.VisaExpired _
  And personInquiryVisa.VisaDate <> DateSerial(1900, 1, 1) Then 'Training is expired
[B][COLOR=#ff0000]  bolNoExpires = False[/COLOR][/B] 'something came up, so flip this value from True if not already done

near the end of the same procedure you have
Code:
Else
         MsgBox "String length for notification too long to fit into this MessageBox", vbExclamation, "Invalid String Length to Display"
    End If

    Next x
    
End Sub

change it to
Code:
Else
         MsgBox "String length for notification too long to fit into this MessageBox", vbExclamation, "Invalid String Length to Display"
    End If

    Next x
[COLOR=#ff0000][B]If bolNoExpires Then MsgBox "No visas are expired or are expiring in the next 31 days"[/B][/COLOR]
    
End Sub
You can easily find portions of the code by copying a phrase and paste it into the Find dialog (ctrl+F). Let me know if you're still stuck.
 
Upvote 0
This works perfectly, thank you!

One last thing - it's not as important - how hard would it be to get it to also have to find either "Y" or "YES" in the "On a temp visa?" column for it to work?
That would then stop the mistake of accidentally adding a date into the columns, and those dates then coming up on a button click - so effectively, not only does a name and surname needs to be present, but a Y or YES in column must also be there as that is what would actually indicate that there is a temporary visa. If it complicates things massively, then not to worry!
 
Upvote 0

Forum statistics

Threads
1,224,823
Messages
6,181,181
Members
453,022
Latest member
Mohamed Magdi Tawfiq Emam

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