MS Access – VBA – Set Report to Use Default Printer

It is easy during development to inadvertently change the page setup setting of a report (or reports) to use a local printer. Once deployed your database will throw an error message nagging the user to switch from the one specified to his default printer. Why not avoid this issue altogether?!

I created a very simply procedure that simply go through the report collection and ensure that all the report are set to use the default printer. I then call this procedure (along with turn off SubDataSheets, deactivate AllowZeroLength property, etc.) in my deploy procedure I run before deploying any database to my users.

'---------------------------------------------------------------------------------------
' Procedure : RptPrntSetDef
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Ensure that all the report apge setups are set to use the Default Printer
' Copyright : The following may be altered and reused as you wish so long as the
'             copyright notice is left unchanged (including Author, Website and
'             Copyright).  It may not be sold/resold or reposted on other sites (links
'             back to this site are allowed).
'
' Revision History:
' Rev       Date(yyyy/mm/dd)        Description
' **************************************************************************************
' 1         2011-05-23              Initial Release
'---------------------------------------------------------------------------------------
Sub RptPrntSetDef()
    On Error GoTo Error_Handler
    Dim db               As DAO.Database
    Dim DbP              As Object
    Dim DbO              As AccessObject

    Set db = CurrentDb
    DoCmd.SetWarnings False
    Debug.Print "RptPrntSetDef Begin"
    Debug.Print "================================================================================"
    'Check Reports
    Set DbP = application.CurrentProject
    For Each DbO In DbP.AllReports
        DoCmd.OpenReport DbO.Name, acDesign
        If Reports(DbO.Name).Report.UseDefaultPrinter = False Then
            Debug.Print "Editing Report '" & DbO.Name & "'"
            Reports(DbO.Name).Report.UseDefaultPrinter = True
            DoCmd.Close acReport, DbO.Name, acSaveYes
        Else
            DoCmd.Close acReport, DbO.Name, acSaveNo
        End If
    Next DbO
    Debug.Print "================================================================================"
    Debug.Print "RptPrntSetDef End"

Error_Handler_Exit:
    On Error Resume Next
    DoCmd.SetWarnings True
    Set DbO = Nothing
    Set DbP = Nothing
    Set db = Nothing
    Exit Sub

Error_Handler:
    MsgBox "MS Access has generated the following error" & vbCrLf & vbCrLf & "Error Number: " & _
    Err.Number & vbCrLf & "Error Source: RptPrntSetDef" & vbCrLf & "Error Description: " & _
    Err.Description, vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Sub

<

MS Access – VBA – Hide the Navigation Pane

The Basics

Here is a simple bit of code that permits you to hide the MS Access’ main object browser/Navigation Pane, to stop nosy users from accessing tables, queries, etc…

'DoCmd.SelectObject acTable, , True
DoCmd.SelectObject acModule, , True
DoCmd.RunCommand acCmdWindowHide

In conjunction with the code to determine whether the user is running the runtime or full version of Access (see MS Access – Determine if Runtime or Full Version
) you could insert a section of code such as:

If SysCmd(acSysCmdRuntime) = False Then
    'DoCmd.SelectObject acTable, , True
    DoCmd.SelectObject acModule, , True
    DoCmd.RunCommand acCmdWindowHide
End If

A Function You Say! (Update 2016-09-20)

Now transforming the above into a reusable function that we can call as we see fit, you get something like:

'---------------------------------------------------------------------------------------
' Procedure : HideNavPane
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Control the visibility of the Access Navigation Pane
' Copyright : The following may be altered and reused as you wish so long as the
'             copyright notice is left unchanged (including Author, Website and
'             Copyright).  It may not be sold/resold or reposted on other sites (links
'             back to this site are allowed).
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' bVisible  : True/False - whether the Nav Pane should be visible or not.
'               True = Display the Nav Pane
'               False = Hide the Nav Pane
'
' Usage:
' ~~~~~~
' Call HideNavPane(True)  'Display the Nav Pane
' Call HideNavPane(False) 'Hide the Nav Pane
'
' Revision History:
' Rev       Date(yyyy/mm/dd)        Description
' **************************************************************************************
' 1         2011-02-13              Initial Release
' 2         2012-09-25              Added runtime check -> SysCmd(acSysCmdRuntime)
' 3         2015-11-07              Switched acTable to acModule
'---------------------------------------------------------------------------------------
Public Sub HideNavPane(bVisible As Boolean)
    On Error GoTo Error_Handler

    If SysCmd(acSysCmdRuntime) = False Then
        If bVisible = True Then
            '            DoCmd.SelectObject acTable, , True
            DoCmd.SelectObject acModule, , True
        Else
            '            DoCmd.SelectObject acTable, , True
            DoCmd.SelectObject acModule, , True
            DoCmd.RunCommand acCmdWindowHide
        End If
    End If

Error_Handler_Exit:
    On Error Resume Next
    Exit Sub

Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Source: HideNavPane" & vbCrLf & _
           "Error Description: " & Err.Description _
           , vbOKOnly + vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Sub

So how can this be used exactly?

Well, I make a call to a similar function as part of my AutoExec macro which is used to initialize my database so the navigation pane is hidden as soon as the database is opened.

Note

Some of you will notice that I switched from using

DoCmd.SelectObject acTable, , True

to

DoCmd.SelectObject acModule, , True

and the reason is quite simple, I recently encountered an error trying to run the original code using acTable in a database and couldn’t figure out why. The error was in question was

Run-time error ‘2544’:
Microsoft Access cannot find the ” you referenced in the Object Name argument.

Eventually, after a cup of coffee it hit me, the db in question had no tables (it is used to process external file, but doesn’t store anything in tables)! Now I know this is a rarity, but to make the code more robust, switching it to use acModule makes much more sense, since by having this very function in the db, I can guarantee the presence of at least one Module! So there you have it. That said, for 99% of cases, the original acTable would work just fine.

MS Excel – VBA – Determine Last Row

I had been needing to find an easy way to determine the last used row in a given column, or possibly the next available row in a column. I search Google and found numerous examples, which I used for a short period of time. Code such as:

Range("A65536").End(xlup).Select

But that code was not 100% reliable and I wanted, as much as possible, to avoid useless .Select statement to try and optimize my code as best I could. Well, I finally managed to get it down to a single line of code without the use of any .Select statements and which is flexible regardless of the version of Excel being used. See my code below:

Dim lLastRow As Long
Dim lNextAvailableRow As Long

lLastRow = ActiveSheet.Cells(ActiveSheet.Rows.Count, 1).End(xlUp).Row
lNextAvailableRow = lLastRow + 1

The only thing you need to know to use this code is that the 1 in (ActiveSheet.Rows.Count, 1) represents the columns to determine the last used row in. Hence 1=Column A, 2=Column B, …, 7=Column G, …

How does it work exactly? We use ActiveSheet.Rows.Count to get the total number of rows on the sheet and we use that as a starting point and then use End(xlUp) to move up until it has a value. Then from the matching cell we use .Row to return the row the last value was found.

If you don’t like having to use numeric values for the Column, you could use the following to enter a textual value and have Excel do the conversion to a numeric value:

ActiveSheet.columns("A").column

So we could then alter the above into:

Dim lLastRow As Long
Dim lNextAvailableRow As Long

lLastRow = ActiveSheet.Cells(ActiveSheet.Rows.Count, ActiveSheet.Columns("A").Column).End(xlUp).Row
lNextAvailableRow = lLastRow + 1

And now, you need only replace the “A” with whatever column you wish to find the last used row in.

So if we want to turn this into a proper function, we could simply do:

'---------------------------------------------------------------------------------------
' Procedure : GetLastUsedRowInColumn
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Determine the last used row for the specified column
' Copyright : The following is release as Attribution-ShareAlike 4.0 International
'             (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/
' Req'd Refs: None required
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' sColumnLtr: The column letter to find the last row of
'
' Revision History:
' Rev       Date(yyyy-mm-dd)        Description
' **************************************************************************************
' 1         2011-05-09              Initial Release
' 2         2024-11-28              Renamed function
'---------------------------------------------------------------------------------------
Public Function GetLastUsedRowInColumn(ByVal sColumnLtr As String) As Long
    On Error GoTo Error_Handler

    GetLastUsedRowInColumn = ActiveSheet.Cells(ActiveSheet.Rows.Count, _
                                               ActiveSheet.Columns(sColumnLtr).Column).End(xlUp).Row

Error_Handler_Exit:
    On Error Resume Next
    Exit Function

Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Source: GetLastUsedRowInColumn" & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Description: " & Err.Description & _
           Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
           , vbOKOnly + vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Function

Now, you can simply use it in you code by doing, for instance to select the last row in Column “D”:

ActiveSheet.Range("D" & GetLastUsedRowInColumn("D")).Select

Or to select the next available row in Column “D”:

ActiveSheet.Range("D" & GetLastUsedRowInColumn("D") + 1).Select

So on and so forth.

MS Access – Mandatory Fields, Mandatory Entries

Here is a common question: “How can I make a field mandatory to my users?”

Well, as always, there are different techniques that can be employed. I will breifly covert 2: using Table Field Properties and using Form Events.

 

Table Setup

The easiest method is to merely set the table’s Required field property to Yes.

That said, this approach does present limitations. Mainly the fact that the message returned to the user references the Field Name (see the example below) and not its’ Caption or Description or Form Control Name, so the message in fact ends up confusing most users! This is why, using form event can come in handy.

The field ‘YourFieldName’ cannot contain a Null value because the Required property for this field is set to True. Enter a value in this field.

 

Form Event

Happily, we can add a lot more functionality and user-friendliness to our form by using its’ BeforeUpdate event to perform our validation. By inserting a little VBA code, for instance, something along the lines of:

Private Sub Form_BeforeUpdate(Cancel As Integer)
    If IsNull(Me.[YourControlName]) Then
        Cancel = True
        MsgBox "You must enter a value for 'YourControlNameOrYourDescription'. Please make a valid entry or press ESC to Cancel"
        'You could set the focus on the specific control if your wish, change the background color, ...
    End If
End Sub

As you can see, both are easy to implement, but the second method offers much more control and you can explain to your users in plain English to problem. Furthermore, you could also perform more advance data validation to not only ensure they have made an entry, but that it meet the require format…

MS Access – JET SHOWPLAN

I came across an article about optimizing queries using a hidden JET tool call SHOWPLAN which basically generates a showplan.out file (which is merely a text file) which elaborates the method(s) used by Access to execute the query. With this information, it is possible to perform query optimizations, determine which fields require indexing… to optimize your queries.  In some cases, this can have significant impact on database performance well worth the time invested!

I’m not going to rewrite the article, if the subject interests you, then simply look at the source:

A Few Extra Precisions

That said, I did want to try and add a little more information for anyone trying to get Showplan to work on Windows 7 (possibly Windows Vista – untested). To be able to use ShowPlan one must create a registry entry to enable it. In the original atricle, they indicate that the base registry key is found at:

\\HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\JET\4.0\Engines

this may be the case for Windows XP, but if you are using Windows 7 (and I suspect Vista as well) you will not find this key. Instead look for:

\\HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Jet\4.0\Engines

Once you locate the base registry key, create a new key entitled Debug, and create a String value entitled JETSHOWPLAN with a value of ON.  Finally, restart Access and the query plans automatically will get created in your Documents folder.

Office365 Click-To-Run Show Plan Registry Key

With the more recent Office365 Click-to-run versions of Access the registry key has changed.  For my installation, for Access 2016 x32 for instance, I ended up using:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\Wow6432Node\Microsoft\Office\16.0\Access Connectivity Engine\Engines

I then created a new key entitled Debug, then created a String value entitled JETSHOWPLAN with a value of ON.  Then, I restarted Access and the query plans automatically got created in my Documents folder whenever I ran a query.

How did I know where to create this key?  I didn’t and documentation is impossible to find (what’s new).  I simply used the RegEdit’s Find tool to locate the term Access Connectivity Engine and was able to locate one with a key entitled Engines and then tried my luck by adding the Debug key… and it worked.  So even if the exact key does not exist for you, the above steps should enable you to find the proper key for your setup, but do be careful messing around with the registry!

So just be aware that the CTR versions use another registry key.

A Few Resources on the Subject

Domain Registry of Canada – a SCAM?

I recently got a letter in the mail from Domain Registry of Canada indicating I had to renew 2 domains. Their letter, and envelop is as close as you can get to resembling official Government of Canada letterhead, but isn’t. Actually what then send out in an invoice for services, for which you have not yet even signed up for! But if you return the letter with payment you are in fact agreeing to switch your business over to them!

Domain Registry of Canada Letter
Domain Registry of Canada Letter

So what or who is Domain Registry of Canada exactly? They are merely a domain registrar, a company that offer the service of registering your website address (your .com, .ca, .net, …).

Continue reading

MS Access – VBA – Append/Insert Record into Another External Database

So how can one take data from one database and insert it into another database?

There are 2 methods that come to mind and both are quick and easy to implement.

 

Specifying the Database to Append to in Your SQL Statement

The first method is to modify your query to include the external database as a reference. A basic Insert query would look something along the lines of:

INSERT INTO TableName ( Field1, Field2, ...)
VALUES ( Value1, Value2, ...);

but did you know you can also include, as part of your SQL statement, the database to append the data into?! So we could easily modify the SQL statement like:

INSERT INTO TableName ( Field1, Field2, ...) In 'FullPathAndDbNameWithExtension'
VALUES ( Value1, Value2, ...);

Here’s a concrete example:

INSERT INTO Temp1( FirstName, LastName) In 'C:\Databases\Clients.mdb'
VALUES ( "Don", "Hilman");

 

Using Linked Tables

Another approach is to merely create a linked table to your secondary database and then run a normal append query on your linked table!

Either way, as you can clearly see for yourself, it is not a hard thing to insert data into a table contained within another external database!

MS Access – VBA – Determine Where a Field Is Used

I recently had to make a minor change to a poorly designed, but large database and had to determine where certain fields were being used so I could go make the necessary changes. In this case, I had to review hundreds of MS Access objects, so a manual approach was just not acceptable. As such, I created a VBA to let the computer do the checking for me and report back. Below is the fruits of my labor.

'---------------------------------------------------------------------------------------
' Procedure : FindFieldUsedWhere
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Locate where a field is used within queries, forms and reports
' Copyright : The following may be altered and reused as you wish so long as the
'             copyright notice is left unchanged (including Author, Website and
'             Copyright).  It may not be sold/resold or reposted on other sites (links
'             back to this site are allowed).
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' sFieldName    : Field Name to search for in the various Access objects
'
' Usage:
' ~~~~~~
' FindFieldUsedWhere("Type A")
'
' Revision History:
' Rev       Date(yyyy/mm/dd)        Description
' **************************************************************************************
' 1         2011-04-08                 Initial Release
'---------------------------------------------------------------------------------------
Function FindFieldUsedWhere(sFieldName As String)
On Error GoTo Error_Handler
    Dim db               As DAO.Database
    Dim qdf              As DAO.QueryDef
    Dim sSQL             As String
    Dim ctl              As Control
    Dim frm              As AccessObject
    Dim DbO              As AccessObject
    Dim DbP              As Object

    Set db = CurrentDb
    Debug.Print "FindFieldUsedWhere Begin"
    Debug.Print "Searching for '" & sFieldName & "'"
    Debug.Print "================================================================================"

    'Check Queries
    For Each qdf In db.QueryDefs
        'qdf.Name    'The current query's name
        'qdf.SQL     'The current query's SQL statement
        sSQL = qdf.SQL
        If InStr(sSQL, sFieldName) Then
            'The Query is a Make Table Query and has our TableName we are looking for
            Debug.Print "Query: " & qdf.Name
        End If
    Next

    'Check Forms
    For Each frm In CurrentProject.AllForms
        DoCmd.OpenForm frm.Name, acDesign
        If InStr(Forms(frm.Name).RecordSource, sFieldName) Then
            'The Query is a Make Table Query and has our TableName we are looking for
            Debug.Print "Form: " & frm.Name
        End If
        'Loop throught the Form Controls
        For Each ctl In Forms(frm.Name).Form.Controls
            Select Case ctl.ControlType
                Case acComboBox
                    If Len(ctl.Tag) > 0 Then
                        If InStr(ctl.Tag, sFieldName) Then
                            'The Query is a Make Table Query and has our TableName we are looking for
                            Debug.Print "Form: " & frm.Name & " :: Control: " & ctl.Name
                        End If
                        If InStr(ctl.ControlSource, sFieldName) Then
                            'The Query is a Make Table Query and has our TableName we are looking for
                            Debug.Print "Form: " & frm.Name & " :: Control: " & ctl.Name
                        End If
                    End If
                Case acTextBox, acCheckBox
                    If InStr(ctl.ControlSource, sFieldName) Then
                        'The Query is a Make Table Query and has our TableName we are looking for
                        Debug.Print "Form: " & frm.Name & " :: Control: " & ctl.Name
                    End If
            End Select
        Next ctl
        DoCmd.Close acForm, frm.Name, acSaveNo
    Next frm

    'Check Reports
    Set DbP = Application.CurrentProject
    For Each DbO In DbP.AllReports
        DoCmd.OpenReport DbO.Name, acDesign
        If InStr(Reports(DbO.Name).RecordSource, sFieldName) Then
            'The Query is a Make Table Query and has our TableName we are looking for
            Debug.Print "Report: " & DbO.Name
        End If
        'Loop throught the Report Controls
        For Each ctl In Reports(DbO.Name).Report.Controls
            Select Case ctl.ControlType
                Case acComboBox
                    If Len(ctl.Tag) > 0 Then
                        If InStr(ctl.Tag, sFieldName) Then
                            'The Query is a Make Table Query and has our TableName we are looking for
                            Debug.Print "Report: " & DbO.Name & " :: Control: " & ctl.Name
                        End If
                        If InStr(ctl.ControlSource, sFieldName) Then
                            'The Query is a Make Table Query and has our TableName we are looking for
                            Debug.Print "Report: " & DbO.Name & " :: Control: " & ctl.Name
                        End If
                    End If
                Case acTextBox, acCheckBox
                    If InStr(ctl.ControlSource, sFieldName) Then
                        'The Query is a Make Table Query and has our TableName we are looking for
                        Debug.Print "Report: " & DbO.Name & " :: Control: " & ctl.Name
                    End If
            End Select
        Next ctl
        DoCmd.Close acReport, DbO.Name, acSaveNo
    Next DbO

    Debug.Print "================================================================================"
    Debug.Print "FindFieldUsedWhere End"

Error_Handler_Exit:
    On Error Resume Next
    Set qdf = Nothing
    Set db = Nothing
    Set ctl = Nothing
    Set frm = Nothing
    Set DbP = Nothing
    Set DbO = Nothing
    Exit Function

Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Source: FindFieldUsedWhere" & vbCrLf & _
           "Error Description: " & Err.Description, vbCritical, "An Error has Occurred
    Resume Error_Handler_Exit
End Function

This is my first draft and I haven’t looked at optimizing my code. It was more about getting results than developing a procedure to be reused often. Hopefully it can help someone else in the same boat as I found myself! Simply execute the procedure and it will return a listing of what Access objects use the specified field in the immediate window of the VBE console.

MS Access – VBA – Relink Back-End Tables

In any well developed MS Access database, it becomes necessary to automate the relinking of the back-end database tables.  If nothing more  than to simplify switching between Development and Production environments.

There are any number of existing resources that you can very rapidly implement.

Below are a few useful links with VBA code samples of how to relink linked-tables through automation:

In complexe database setups, it may become necessary to relink your database to multiple back-ends. I started out writting my own code to do this and then came accross nice sample from David Fenton Associates:

Another good one is the J Street Relinker:

Hopefully these links will save you some time searching the net.

GE 1.1 Cu Ft Microwave – JES1142WPC – Review

My wife and I owned a GE micorwave for several years (purchased from Zellers) which we have had for 6+ yrs. It worked great! But after working beautifully for many years, started to shows its’ age and so we went out and decided to purchase another GE unit.

We ended up purchasing a GE 1.1 Cu Ft Turntable Microwave from WalMart.

Sadly, neither of us is very happy with this purchase! Especially when compared with our previous GE. Here are the top reason why we are dissatisfied:

  • The Kitchen Timer resets itself – say you are in the process of cooking something and you want to setup the kitchen timer and then go and insert the item in your oven, when you return to the microwave to actually start the timer, the microwave has gone back to clock mode?! Why!?
  • Inconsistent Heating – sometimes the food comes out piping hot and other times not so much. Other times the food is cold but the plate are burning hot?
  • Inconsistent Time Entry Format – depending on which function you activate, the way you enter time changes. This is very frustrating and for no reason. Just poor programming on GE’s part!
  • Sparking/Burning of Vegetables – I have tried to cook various vegetables and many of them litterally start sparking and/or burning in this microwave!
  • Sliding Around – when you activate a function, and especially when you open the microwave door, the unit slides around all over the counter. The microwave pads (feet) are simply not doing the job.
  • So basically, GE sure isn’t producing a product of quality like they previously did. I would not recommend this unit and would most probably stay away from GE as a whole if this is any indication of the level of thought, product testing and quality the are now putting forth!