Bill Mosca's Microsoft Access Database Tools, Code Samples and more for the serious developer.
Home
Access Basics eBook
How To's and Articles
Utilities and Add-Ins
Code Samples
Recommended Books
Reviews
Misc Downloads & Links
Blog, blah, blah
About Us
Contact Us

 

Mailing More Than One Attachment Using Outlook

Count the Number of Workdays Between 2 Dates

Turn Off AllowDesignChanges

Turn Off Subdatasheets

Close All Forms/Reports But The Current One

 

Convert Decimals to Fractions

Mailing More Than One Attachment Using Outlook

This code takes advantage of late binding so you do not have to add a reference to the Outlook library which may vary from machine to machine. If you want to use early binding, add the Outlook library to your references and substitute the Object data type for the one in the comments.

 

Just remember not everyone will have the same version of Outlook that you do. That's why I use late binding. With late binding, the VBA compiler will select the "Outlook.Application" that is available on the machine. With early binding, the version is hard-coded. If it does not match your version, it will fail.

 

Public Function Outlook_SendEmail(ByVal strTo As String, _
                           ByVal strSubject As String, _
                           ByVal strMsg As String, _
                           ParamArray AttachmentList() As VariantAs Boolean
'Purpose  : Automatically send email via late-binding Outlook Automation.
'   Call like this:
'    Call Outlook_SendEmail("Bill.Mosca@MyDomain.com","Hey there.", _
     "Here is my message","C:\MyFiles\Test1.txt","C:\MyFiles\Test2.txt")
'DateTime : 11/30/2003 12:12
'Author   : Bill Mosca, modified by ChrisO to use Array for attachments.
    Dim objOLApp As Object    'Outlook.Application
    Dim outItem As Object    'Outlook.MailItem
    Dim outFolder As Object    'MAPIFolder
    Dim DestFolder As Object    'MAPIFolder
    Dim outNameSpace As Object    'NameSpace
    Dim lngAttachment As Long

    On Error GoTo err_Outlook_SendEmail

    Set objOLApp = CreateObject("Outlook.Application")
    Set outNameSpace = objOLApp.GetNamespace("MAPI")
    Set outFolder = outNameSpace.GetDefaultFolder(6)    'olFolderInbox=6
    Set outItem = objOLApp.CreateItem(0)                'olMailItem=0

    outItem.Body = strMsg
    outItem.Subject = strSubject
    outItem.To = strTo

    With outItem.Attachments
        For lngAttachment = LBound(AttachmentList) To UBound(AttachmentList)
            .Add AttachmentList(lngAttachment)
        Next lngAttachment
    End With

    outItem.Send
    Outlook_SendEmail = True

exit_Outlook_SendEmail:
    On Error Resume Next
    Set outItem = Nothing
    Set outFolder = Nothing
    Set outNameSpace = Nothing
    Set objOLApp = Nothing
    Exit Function

err_Outlook_SendEmail:
    Select Case Err.Number
        Case 287
            'User stopped Outlook from sending email.
            MsgBox "Email Cancelled.", vbInformation, "DCDS"
        Case Else
            MsgBox "Error " & Err.Number & " (" & Err.Description _
                   & ") in procedure Outlook_SendEmail of Module mod_Utilities"
    End Select

    Resume exit_Outlook_SendEmail

End Function

back to top

 

 

 

Count the Number of Workdays Between Two Dates

I'm often asked how to determine the number of workdays between 2 dates. Assuming the workweek is Monday through Friday, this code will do it.

 

It can be edited to accept a different work week. Use a Holidays table to discount those days.

Note: I used ADO in this example only because, at the time it was written, Microsoft said DAO was dead. Silly me, I believed them.

 

Public Function NetWorkdays(varStartDate As Variant, varEndDate As Variant) _
    As Long
'Purpose  : Calculate the number of workdays (M-F)
'DateTime : 6/17/2000 13:09
'Author   : Bill Mosca
    Dim datCurrDate As Date
    Dim intWkDays As Integer
    Dim strSQL As String
    Dim cnn As ADODB.Connection
    Dim rs As ADODB.Recordset
    Dim lngRecs As Long

    'Make sure both dates are passed. If not, leave function.
    On Error GoTo err_NetWorkdays


    If IsNull(varStartDate) Or IsNull(varEndDate) Then
        NetWorkdays = 0
        Exit Function
    End If

    If varStartDate = varEndDate Then
        NetWorkdays = 1
        GoTo exit_NetWorkdays
    End If


    datCurrDate = varStartDate
    intWkDays = 0

    Do While datCurrDate <= varEndDate
        If Weekday(datCurrDate) <> vbSunday _
            And Weekday(datCurrDate) <> vbSaturday Then
            intWkDays = intWkDays + 1
        End If

        datCurrDate = DateAdd("d", 1, datCurrDate)
    Loop

'****************************************************************
'    'If you have a table that contains Holidays, use it to
'    'find the number of holidays between the 2 dates.
'    'Otherwise comment out this area.
'    Set cnn = CurrentProject.Connection
'    Set rs = New ADODB.Recordset
'    strSQL = "SELECT COUNT(Holiday) FROM tblHolidays " _
'        & "WHERE Holiday BETWEEN #" & varStartDate _
'        & "# AND #" & varEndDate & "#"
'    rs.Open strSQL, cnn, adOpenStatic
'    lngRecs = rs.Fields(0)
'    intWkDays = intWkDays - lngRecs
'****************************************************************
    NetWorkdays = intWkDays

exit_NetWorkdays:
    On Error Resume Next
    Set rs = Nothing
    Set cnn = Nothing
    Exit Function

err_NetWorkdays:
    Resume exit_NetWorkdays

End Function

back to top

 

 

 

Turn Off AllowDesignView

For some reason not known to me, Access 2000 - 2003 defaults all forms to show the Properties box in form view. While that might help in touching up a form's design, it can be embarrassing it you forget to set the "Allow Design Changes" property to Design View Only. It's time to quit going through each form to manually make that change. This code will iterate through all forms and change the setting for you.


Public Function FormDesignViewOnly()
    Dim doc As DAO.Document
    Dim db As DAO.Database
    
    Set db = CurrentDb
    
    For Each doc In db.Containers("Forms").Documents
        DoCmd.OpenForm doc.Name, acDesign
        Forms(doc.Name).AllowDesignChanges = False
        DoCmd.Close acForm, doc.Name, acSaveYes
    Next
    
    MsgBox "All Forms set to AllowDesignChanges=False", vbInformation
    Set db = Nothing
    
End Function

back to top

 

 

 

Turn Off Subdatasheets

Starting back in Access 2000, the tables had a new property called Subdatasheet. This remarkable feature gave you the abiltiy to drill down to related records when viewing a table in datasheet view. There is a little plus sign (+) to the left of the first field in each record. Clicking on that plus sign would open a "sub" view of child records for that particular record.

 

Unfortunately, that feature can really slow down the performance of not only the database as a whole, but also the loading of forms. I generally turn it off because the databases I create usually don't allow the users direct access to the tables. Forms and reports are all they see so the users don't have a need for the Subdatasheets. Why sacrifice performance unnecessarily?

 

If you open a table in design view and right-click on on the table, you will see Properties as a choice. Select it. By setting the Subdatasheet property to [None], you turn off the feature for that table. But this must be done for each table. There is no global setting. By using DAO TableDefs you can loop through all the tables and set the property.

 

Here is how it is done (Note: If you are using Access 2000, be sure to add the DAO library to your references).

 Public Function TurnOffSubDataSheets()
'Purpose  : Access sets all tables' SubDataSheets to Auto which slows up loading time.
'           This function will set the property to [none].
'DateTime : 1/14/2004 09:20
'Author   : Bill Mosca
    Dim db As DAO.Database
    Dim prop As DAO.Property
    Dim propName As String, propVal As String
    Dim propType As Integer, i As Integer

    On Error Resume Next

    Set db = CurrentDb

    propName = "SubDataSheetName"
    propType = 10
    propVal = "[NONE]"

    For i = 0 To db.TableDefs.Count - 1
        If (db.TableDefs(i).Attributes And dbSystemObject) = 0 Then
            If db.TableDefs(i).Properties(propName).Value <> propVal Then
                db.TableDefs(i).Properties(propName).Value = propVal
            End If

            If Err.Number = 3270 Then
                Err.Clear
                Set prop = db.TableDefs(i).CreateProperty(propName)
                prop.Type = propType
                prop.Value = propVal
                db.TableDefs(i).Properties.Append prop
            Else
                If Err.Number <> 0 Then
                    Resume Next
                End If
            End If
        End If
    Next i

    MsgBox "The " & propName & _
            " value for all non-system tables has been updated to " & propVal & "."

    Set prop = Nothing
    Set db = Nothing

End Function

back to top

 

 

 

Close All Forms/Reports But The Current One
When designing a database I might have several forms and/or reports open at once. Using the Find/Replace feature in the code window can also open more objects if they contain the string you are searching for.

 

So there I am with 20 to 30 objects open, and I want to close all but the one I selected. Clicking on Window/Cascade will organize the objects so that the Close icons are easily seen, thus letting you click on each one to close the objects, but that takes forever.

 

I wrote this code to do all that "clicking" for me. There is an option for saving without a prompt that comes in handy when you want to save them all or pick and choose which ones to save.

Public Function CloseFrmsRpts()
'Purpose  : Close all forms and reports opened in design view
'           except the current one.
'DateTime : 11/2/2007 07:46
'Author   : Bill Mosca
'Reference:
    Dim objObject As AccessObject
    Dim intPrompt As Integer
    Dim intUserResp As Integer

    'Set Save flag. Automatically save unless user wants prompt.
    'acSavePrompt=0; acSaveYes=1
    intUserResp = MsgBox("Objects will automatically be saved unless " _
            & "you specify a prompt." _
            & vbNewLine & vbNewLine _
            & "Prompt to save?", vbQuestion + vbYesNo + vbDefaultButton2, "Save?")

    Select Case intUserResp
        Case vbYes
            intPrompt = acSavePrompt
        Case vbNo
            intPrompt = acSaveYes
    End Select

    For Each objObject In CurrentProject.AllForms
        If objObject.IsLoaded = True _
                And Application.CurrentObjectName <> objObject.Name Then
            DoCmd.Close acForm, objObject.Name, intPrompt
        End If
    Next

    For Each objObject In CurrentProject.AllReports
        If objObject.IsLoaded = True _
                And Application.CurrentObjectName <> objObject.Name Then
            DoCmd.Close acReport, objObject.Name, intPrompt
        End If
    Next

End Function

back to top 

 

 

Convert Decimals to Fractions
This one is from Clive Williams. I've seen a few examples of how to do this conversion, but they have all been rather kludgy. Clive's elegant function does it perfectly with very few lines of code. Definately one to keep in your code vault.

Public Function DecToFraction(pDecimal As Double, _
                Optional pPowerOfTwoDenominator As Integer = 6) As String
'Purpose  : Convert a positive decimal number to an integer plus a fraction.
'           e.g. 1.375 => 1 3/8, 2.0 => 2
'DateTime : 6/7/2008 10:26
'Author   : Clive Williams
    Dim i As Integer
    Dim f As Long
    Dim n As Integer
    Dim Nmr As Double

    Nmr = 2 ^ pPowerOfTwoDenominator    ' Initial Denominator

    i = Int(pDecimal)    ' Integral Part

    f = Nmr * (pDecimal - i)    ' Fractional Part x Denominator

    While 0 = (f Mod 2) And (f > 1)    ' Halve the Numerator and the Denominator
        f = f / 2
        Nmr = Nmr / 2
    Wend

    If f = 0 Then    ' Ignore the fraction
        DecToFraction = CStr(i)
    Else
        DecToFraction = CStr(i) & " and " & CStr(f) & "/" & CStr(Nmr)
    End If

End Function

back to top