Search code examples
vbams-accessms-wordeofrecordset

Recordset loop doesn't return the last record


I'm using the following code to loop through a recordset which suppose to transfer all selected records to the Word document in a tabular format. It works however it gives me all records except the last one (i.e. I select 4 records and it returns only 3 from the top).

Private Sub cmdWord_Click()
Dim oWord As Object 'Word.Application
Dim doc As Object 'Word.Document
Set oWord = CreateObject("Word.Application")
Set doc = oWord.Documents.Open("D:\Working\Templates\Letter.doc")
oWord.Visible = True
Dim oAccess As Object
Dim dbs As Database
Dim rst As Recordset
Dim strCriteria As String

With oWord.ActiveDocument
    If .Bookmarks.Exists("Date") = True Then
        .Bookmarks("Date").Select
        If Not IsNull([Forms]![frmForReport_Preview]!Date) Then
            oWord.Selection.Text = (CStr(Format([Forms]![frmForReport_Preview]!Date, "mmm d, yyyy")))
        Else
            oWord.Selection.Text = ""
        End If
    End If

    If .Bookmarks.Exists("Table") = True Then

        Set dbs = CurrentDb
        strCriteria = "SELECT tblForLetter.ID, tblForLetter.DID, tblForLetter.AT, tblForLetter.Expr1, tblForLetter.L FROM tblForLetter WHERE (((tblForLetter.Print)=-1=True));"

        Set rst = dbs.OpenRecordset(strCriteria, dbOpenSnapshot)
        oWord.ActiveDocument.Bookmarks("Table").Select
        oWord.Selection.Text = ""

        i = 1
        If Not (rst.EOF And rst.BOF) Then
            rst.MoveFirst
            Do Until rst.EOF = True
                doc.Tables(1).Columns(1).Cells.Add
                doc.Tables(1).Columns(1).Cells(i + 1).range.Text = rst.Fields(0)
                doc.Tables(1).Columns(2).Cells(i + 1).range.Text = rst.Fields(1)
                doc.Tables(1).Columns(3).Cells(i + 1).range.Text = rst.Fields(2)
                doc.Tables(1).Columns(4).Cells(i + 1).range.Text = rst.Fields(3)
                doc.Tables(1).Columns(5).Cells(i + 1).range.Text = rst.Fields(4)
                rst.MoveNext
                i = i + 1
            Loop
        Else
            MsgBox ("There are no records in the recordset.")
        End If

        DoCmd.Close acForm, "frmForReport_Preview", acSaveYes
        DoCmd.SetWarnings False
        DoCmd.RunSQL "Delete * From [tblForLetter]"
        DoCmd.SetWarnings True

        rst.Close
        Set dbs = Nothing
    End If
End With
End Sub

Solution

  • How exactly do you select the records in the form?

    I'm suspecting this:

    • The form is a continuous form
    • The RecordSource of the form is tblForLetter or SELECT * FROM tblForLetter or something like this
    • There's a checkbox on the form which has the Print column as the Control Source.
    • So when you "select" a record by clicking its checkbox, the Print column in the respective row is set to True / -1.

    If you're doing it like this, you should consider the following:

    • When you change something in one of the rows (in this case you check a checkbox, but it would be the same if you entered text into a text box, for example), this row goes into "edit mode".
      (don't know if Access has a special name for it, so I'll just call it like this)
    • As long as that row is still in edit mode, nothing is changed in the database. You are just changing the controls in the GUI.
    • The row stops being in edit mode when you set the focus to another row (=by clicking on a control in another row).
    • In this moment, Access actually saves your changes into the database.

    So when you select multiple checkboxes, what really happens is this:

    • You click on the checkbox in row 1
    • Row 1 switches to edit mode, and the checkbox is checked in the GUI (but not yet in the database)
    • You click on the checkbox in row 2
    • This ends the edit mode in row 1 and saves the change into the database.
    • Then row 2 switches to edit mode, and the checkbox is checked in the GUI (but not yet in the database)
    • ...and so on.

    Now there's an additional gotcha:
    When a row is in edit mode, and you click on a control that's in the form's header or footer (in other words, not in the "detail" area, the actual continuous part of the form), that row stays in edit mode and the changes are not saved to the database yet.

    So the strange behaviour that you are seeing is probably caused by this:

    • You select four records by clicking on their check boxes.
    • Then you click on the cmdWord button which triggers the code in your question, but in that moment, the fourth row is still in edit mode.
    • This means that the checkbox is checked on the GUI only...but Access hasn't saved this to the database yet!
    • So when you execute your query and filter by ... WHERE tblForLetter.Print=True, you get only the first three selected rows, but not the fourth because in that moment, tblForLetter.Print is still False in the database.

    Solution:

    Generally speaking, you need to make sure that no row is still in edit mode when you run the query that gets the selected rows from the table.

    Without changing your code, you can see the difference when you select your four rows, set the focus to another row so that the fourth row is saved as well, and then click the button.

    To do this automatically in your code, you can requery the form's data source by just calling Me.Requery before selecting from the table.
    Me.Requery will reload the data in the form. If there's still a row in edit mode in that moment, edit mode will end first and the changes in that row will be saved to the database.