Restoring a continuous form after Requery - vba

MS Access arbitrary version.
I have a continuous form which displays a part of a recordset, based on a query that can be sorted in many ways. Let’s say the form displays n records of the recordset (where n>1). The first row in the form isn’t necessary displaying the first record of the recordset. One of the records (let’s say the record on row x of n) is selected.
After a Form.Requery, the first record of the recordset will always be on the first row of the form. That first record is also selected.
That’s not good. I want to restore the form after a Form.Requery so the selected record is still on the x:th row in the form, provided that this is always possible. Which records that is displayed above and beneath the x:th row is obviously depended on the recordset’s underlying query.
In my case, this could be achieved if there was a way to calculate how many rows there is between the selected row and the first row in the form, before Form.Requery.
That calculation would (independently of which part of the recordset that is displayed) result in 0 if the first row of the form is selected, 1 if the second row of the form is selected and so on.
How do I do that?
Or is there is another way to achieve the same thing?

I use this function - see explanations in comments.
'---------------------------------------------------------------------
' Requery a continuous form, keeping the selected record and scroll position as it is before
' With ideas by Stephen Lebans - http://www.lebans.com/SelectRow.htm
'---------------------------------------------------------------------
Public Sub FormRequeryPosition(F As Form)
On Error GoTo Err_FormRequeryPosition
Dim OrigSelTop As Long
Dim RowsFromTop As Long
Dim OrigCurrentSectionTop As Long
' Must cache the current props because Requery will reset them
OrigSelTop = F.SelTop
OrigCurrentSectionTop = F.CurrentSectionTop
' Turn off screen redraw
F.Painting = False
' Requery the Form
F.Requery
' Calculate how many rows, if any, the selected row was from the top prior to the Requery
' Check if Header is visible or not
If F.Section(acHeader).Visible = True Then
RowsFromTop = (OrigCurrentSectionTop - F.Section(acHeader).Height) / F.Section(acDetail).Height
Else
RowsFromTop = OrigCurrentSectionTop / F.Section(acDetail).Height
End If
' Setting the SelTop property forces this row to appear at the top of the Form.
' We will subtract the number of rows required, if any, so that the original
' current row remains at the original position prior to the Requery.
' First set the current record to the last record.
' This is required due to the method that the Access GUI manages the ScrollBar.
' Prevent error on empty form
If F.RecordsetClone.RecordCount > 0 Then
' With complex record source or many records, Access may not know .RecordCount yet -> use MoveLast to get it
F.RecordsetClone.MoveLast
F.SelTop = F.RecordsetClone.RecordCount
F.SelTop = OrigSelTop - RowsFromTop
DoEvents
F.Painting = True
' Now setfocus back to the original row prior to the Requery
' In unknown conditions this raises a runtime error 3001 "Invalid argument"
On Error Resume Next
F.RecordsetClone.AbsolutePosition = F.CurrentRecord + RowsFromTop - 1
If Err.Number = 0 Then
F.Bookmark = F.RecordsetClone.Bookmark
End If
End If
Exit_FormRequeryPosition:
F.Painting = True
Exit Sub
Err_FormRequeryPosition:
MsgBox "Error " & Err.Number & ": " & Err.Description & vbCrLf & "Line: " & Erl
Resume Exit_FormRequeryPosition
End Sub

if you have in the form any unique column then you can do something like this:
Dim uniqueId : uniqueId = me.uniqueField_Value
' uniqueId hold the current selected unique field value
Form.Requery
With Me.RecordsetClone
' if uniqueId is numeric use next line
.FindFirst "[uniqueField] = " uniqueId
' if uniqueId is string uncomment next line and comment previous line
' .FindFirst "[uniqueField] = " & """" & uniqueId & """"
If Not .NoMatch Then
Me.Bookmark = .Bookmark
End If
End With

Related

MS Word VBA Macro from validation

I'm trying to do what, I think, ought to be the simplest of things, but I can't get it to work. i have an MS Word document with a number of legacy drop-down and text fields:
The first option in each drop-down is "Select ...", and if a user tabs out of one of the drop-downs without choosing something other than the first "Select ...", I want a msgbox to appear to tell them to make a selection, which works. What doesn't work, is that after the user dismisses the msgbox, I want the insertion point to return to the drop-down that they didn't select.
I understand that VBA has "timing issues", and from what I've read, one way to address these timing issues is to call the "return" macro from the validation macro. So I've written two macros:
Sub Validate()
' Dim strBookmark As String
' strBookmark = Selection.Bookmarks(1).Name
10: If (bool_debug) Then MsgBox ("NotSelected() - 10: strBookmark = " & strBookmark)
With ActiveDocument
If (strBookmark = "Locality") Then
Call Salary_Step
ElseIf (strBookmark = "Series") Then
20: If (bool_debug) Then MsgBox ("NotSelected() - 20: .FormFields(strBookmark).Name = " _
& .FormFields(strBookmark).Name)
If ((Len(.FormFields(strBookmark).Result) <> 4) Or (Not IsNumeric(.FormFields(strBookmark).Result))) Then _
MsgBox ("Please enter a 4 digit number.")
Call GoBackToPrevious(.FormFields(strBookmark).Name)
ElseIf (.FormFields(strBookmark).DropDown.Value = 1) Then
MsgBox ("Please select a " & Replace(Selection.FormFields(strBookmark).Name, "_", " ") & ".")
Call GoBackToPrevious(.FormFields(strBookmark).Name)
End If
End With
End Sub
and
Sub GoBackToPrevious(strPreviousField)
10: If (bool_debug) Then MsgBox ("GoBacktoPrevious - 10: strPreviousField = " & strPreviousField)
ActiveDocument.Bookmarks(strPreviousField).Range.Fields(1).Result.Select
End Sub
But when I tab out of any of the form fields, the insertion point jumps to the next form field and not back to the one that I just tabbed out of.
I know from the debug code that GoBackToPrevious is being passed the name of the current form field, but MS Word advances to the next field regardless.
I'd really appreciate it if someone can tell me how make MS Word return to and select the drop-down the user did not select appropriately instead of jumping to and selecting the next form field in the document.
Thank you.
P James Norris
EDIT:
Based on #TimothyRylatt comments, I have modified my macro and when they're called.
I have edited Validate as above (commenting out the Dim the strBookmark assignment, and I call it "on entry" to the next form field.
strBookmark is Dimed on the module's declaration section:
Option Explicit
Const bool_debug As Boolean = True
Const str_password As String = "###" ' I have a different password
Public strBookmark As String
and "on exit" from the "current" form field, I attempt to store the "current" bookmark name:
Sub StoreBookmark()
strBookmark = Selection.Bookmarks(1).Name
10: If (bool_debug) Then MsgBox ("StoreBookmark() - 10: strBookmark = " & strBookmark)
End Sub
which I call from the current form field "on exit".
But when I tab out of the current form field to the next form field, the insertion point doesn't go back to the "current" but instead stays in the next form field.
Anyone have any other suggestions/insights?
Thanks,
P James Norris

Subform blocking Recordset.AddNew (Error 3027)

Having a subform displaying the same table used in recordset makes the table read only (error 3027). Getting rid of the subform fixes the problem. Similar setup works in my other form interface.
I tried to delete the subform, which fix the accessibility problem. But that would defeat the original UX purpose.
Option Explicit
Public UnitRS As DAO.Recordset
Public ModelRS As DAO.Recordset
Public NameUnitRecords As String
Public NameModelRecords As String
Public Sub Form_Load()
'Initialization
NameUnitRecords = "tblBatteriesMainRecordsUnits"
NameModelRecords = "tblBatteriesRecordsModels"
End Sub
Private Sub SetUnitRecordsets()
'Set the path to the Battery Records table
Set UnitRS = CurrentDb.OpenRecordset(NameUnitRecords, dbOpenDynaset)
End Sub
Private Sub txtBatteryID_AfterUpdate()
'Set the recordset path for unit records
Call SetUnitRecordsets
'do a findfirst search for the Battery ID, using value from textbox txtBatteryID
UnitRS.FindFirst "[Battery ID]=" & txtBatteryID
'If no matching record, leave the other fields empty
If UnitRS.NoMatch Then
'If there is a matching record, then, grab the model number
Else
'as there is an existing record with model number, run a search on the model number and grab the model info
End If
'close recordset
UnitRS.Close
'check if the button can be enabled
End Sub
Private Sub cmbModelNumber_AfterUpdate()
'Set the recordset path for model records
'do a findfirst search for the Model Number, using value from combobox cmbModelNumber
'If no matching record, leave the other fields empty
If ModelRS.NoMatch Then
'If there is a matching record, then, grab the Model Info
Else
End If
'close recordset
'check if the button can be enabled
End Sub
Private Sub btnSaveAndCLear_Click()
Dim Response, strOldModelNumber
'Set the recordset path for unit records
Call SetUnitRecordsets
'Set the recordset path for model records
'close all related tables, queries and forms
DoCmd.Close acTable, NameUnitRecords, acSaveYes
DoCmd.Close acForm, "frmSubBatteriesMainRecordsUnits", acSaveYes
'If a new model record is required
ModelRS.FindFirst "[Model Number]='" & cmbModelNumber & "'"
If ModelRS.NoMatch Then
'msg box confirm model information
'If user chooses yes
If Response = vbYes Then
'create new model record
'this block could be done with "With...End" format for less error vulerability?
'nah, unless it is repetitively called, it's too much work to inplement just the .addnew part
'requery the two combobox to reflect newest changes
'User chooses no
Else
'popup a message telling the user the battery and model record is not logged, but don't clear the field.
Exit Sub
End If
End If
'need to find the record first, otherwise it will edit the first one
UnitRS.FindFirst "[Battery ID]=" & txtBatteryID
'store the old model number value
strOldModelNumber = UnitRS("Model Number")
'New record
If UnitRS.NoMatch Then
'create a new battery record
UnitRS.AddNew
UnitRS("Battery ID") = Me.txtBatteryID
'If this is an edit on existing record
Else
'if the new value is the same as the old one
If strOldModelNumber = cmbModelNumber Then
'msgbox the same value, no change is done to the database
MsgBox "the data is the same as the old record, no change is made to the record", vbInformation, "Same data"
Exit Sub
Else
'msg box confirm edit
Response = MsgBox("Please confirm edit on existing record: " & Chr(13) & Chr(10) & "BatteryID: " & txtBatteryID & Chr(13) & Chr(10) & "Model Number: " & cmbModelNumber, vbYesNo, "New Model Record Dectected")
'If user chooses yes
If Response = vbYes Then
'goto edit mode
UnitRS.Edit
'if user chooses no
Else
'msgbox notify the user nothing is changed in the database
MsgBox "Battery and Model record not logged, you may retry logging", vbInformation, "Record not logged"
Exit Sub
End If
End If
End If
'both changes the model number and comment field anyway
UnitRS("Model Number") = Me.cmbModelNumber
UnitRS("Comment") = Me.txtComment
'commit update
UnitRS.Update
UnitRS.Close
'clear all flieds
'reset all locks
'requery the sub form to reflect the newest changes
Me.subFrmBatteryRecords.Requery
End Sub
What I would like to achieve is to have a display in the form interface to show the content of the actual record table, so that the user knows what is in the table.
There is nothing wrong with my code. There is a property option in the form object, it's called "record lock", somehow mine had "All Records" selected. By making it "No locks", the accessibility problem is gone.

Ms Access VBA go to last record

I am trying to open a form and move to last record.
I am using the following
DoCmd.RunCommand acCmdRecordsGoToLast
The form open and go to last record, but it is hiding the other records(I must use the scroll bar). This might confuse users that there are no other records.
Is it possible to go to last record and have visible the 10 last records?
It's your lucky day - this is surprisingly non-trivial, and I have written a function for this purpose some time ago.
'---------------------------------------------------------------------------------------
' Procedure : FormGotoEnd
' Author : Andre
' Purpose : Go to the last record of a continuous form, but don't scroll that record to the top
' (as DoCmd.RunCommand acCmdRecordsGoToLast would do).
' Instead scroll up so that the last record is visible at the bottom of the form.
' Parameters: F = the form, can be a subform
' AddtlEmptyRowsBottom = if you want to have room for more than one empty row, for data entry forms
'
' Call this sub e.g. in Form_Load() or in Form_Current of the parent form, like this:
' Call FormGotoEnd(Me)
' or Call FormGotoEnd(Me!SubformControl.Form, 3)
'---------------------------------------------------------------------------------------
'
Public Sub FormGotoEnd(F As Form, Optional AddtlEmptyRowsBottom As Long = 0)
Dim DetailSectionHeight As Long
Dim nVisible As Long
Dim nRecords As Long
On Error GoTo FormGotoEnd_Error
' Calculate height of full details section: Window height minus header+footer
DetailSectionHeight = F.InsideHeight
' Ignore errors if form has no header or footer
On Error Resume Next
If F.Section(acHeader).Visible Then
DetailSectionHeight = DetailSectionHeight - F.Section(acHeader).Height
End If
If F.Section(acFooter).Visible Then
DetailSectionHeight = DetailSectionHeight - F.Section(acFooter).Height
End If
On Error GoTo FormGotoEnd_Error
' Number of visible records in details section
nVisible = CLng(DetailSectionHeight / F.Section(acDetail).Height)
' Nothing to do if the form has no records
If F.RecordsetClone.RecordCount > 0 Then
' For complex record source and/or many records, Access may not know .RecordCount yet
' -> calculate via .MoveLast
F.RecordsetClone.MoveLast
nRecords = F.RecordsetClone.RecordCount
' Nothing to do if all records are visible
If nRecords > nVisible Then
' Move to last record. Use .Bookmark so the subform doesn't need to get focus
F.Bookmark = F.RecordsetClone.Bookmark
' This is the important part!
' Add 2 to AddtlEmptyRowsBottom, in order to see the empty data-entry record plus one empty line
F.SelTop = nRecords - nVisible + 2 + AddtlEmptyRowsBottom
' Make sure the last record is selected
F.Bookmark = F.RecordsetClone.Bookmark
End If
End If
FormGotoEnd_Exit:
On Error GoTo 0
Exit Sub
FormGotoEnd_Error:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in FormGotoEnd", vbExclamation
Resume FormGotoEnd_Exit
End Sub

Showing some rows in textboxes on a Form using vba

I have a query that returns for example 5 rows, I want to show thes these rows(fields) in textboxes. but it only shows me one record. I have also set Default view Property of my form into Continuous form. her is my code:
Private Sub List2_DblClick(Cancel As Integer)
Dim rst As DAO.Recordset
Set rst = CurrentDb.OpenRecordset("SELECT XValue, YValue,Wert FROM tb_DCM_Daten WHERE (FzgID=" & Forms!frm_fahrzeug!ID & " AND [Name]='" & List2.Value & "')")
If rst.RecordCount <> 0 Then
Do While Not rst.EOF
Text8.SetFocus
Text8.Text = rst.Fields("XValue").Value
Text10.SetFocus
Text10.Text = rst.Fields("YValue").Value
Text11.SetFocus
Text11.Text = rst.Fields("Wert").Value
rst.MoveNext
Loop
End If
End Sub
how can I do that?
As it stands now, your code would loop through the Recordset, put the values from the first record into the textboxes, then overwrite those values with the ones from the second record, and overwrite those with the values from the third record, and so on.
It sounds like you want a subform. Have a look at the Office tutorial here for more information.

copy row data from one sheet to one or more sheets based on values in other cells

I have a sheet with user details in columns A - C.
Columns D-H is distribution lists the users are subscribed too. (currently check box linked to cell to indicate which list(s) user is subscribed to)
A user can be subscribed to more than on list.
At the moment I can use filters which works ok to filter users for list x, copy the user info to another sheet, then next list filterd (used text for the list selection)
This does cause some problems between some of the users of this sheet.(those that don't know how to use filters)
I would like to create a new sheet for each list which gets populated automatically.
As a user is added/removed from a list, his details is automatically added/removed from the corresponding 'list sheet'.
This way no one can complain about the filters
At the same time they can then export the 'list sheet' they require to another xls doc or csv as required.
I have found various options on how to do this, but all of then had only one selection column. I thought I could alter some of the ranges etc etc in the sample code but all failed with the limited knowledge I have with VB.
Any suggestions on how to do this?
Thanks!
Please do not try and create two copies of your data. Keeping two versions of the same data in step is very, very difficult.
I believe your best option is to create a macro with which your users can select the filter they require.
You do not describe your data in much detail so I have imagined something like the following:
Name Addr Tele List1 List2 List3 List4 List5
John London 1234 x
Jane Paris 2345 x
Kevin Stockholm 3456 x
Mary Brussels 4567 x
Nigel Dublin 5678 x
Abby Athens 6789 x x x
Brian Rome 7890 x
Given the above layout, the following macro shows the sort of thing I would offer.
When the macro is executed, it displays an InputBox like this:
from which the user can select the filter required.
I hope this gives you some ideas.
Option Explicit
Sub SelectFilter()
Dim ColNum() As Variant
Dim InxList As Long
Dim ListName() As Variant
Dim Prompt As String
Dim ReturnValue As Variant
' Load ListName with user-friendly names for the lists
ListName = Array("Name list 1", "Name list 2", "Name list 3", _
"Name list 4", "Name list 5")
' Load ColNum with the column number for each list. The entries in ColNum
' must be in the same sequence as the entries in ListName. Column "A" is
' column 1, column "B" is column 2 and so on.
ColNum = Array(4, 5, 6, 7, 8)
' Combine the user-friendly list names to create a menu
Prompt = ""
For InxList = 0 To UBound(ListName)
Prompt = Prompt & InxList + 1 & ". " & ListName(InxList) & vbLf
Next
Prompt = Prompt & "Please enter the number against the list you require." _
& vbLf & "Leave box empty to cancel selection."
' Loop until user cancels or enters a permitted value
Do While True
ReturnValue = InputBox(Prompt, "Select filter")
If VarType(ReturnValue) = vbBoolean Then
If Not ReturnValue Then
' The documentation for InputBox claims it returns False if
' the user clicks Cancel. In my experience it return a
' null string but check to be on the safe side.
Exit Sub
Else
' True is not a documented return value from InputBox.
' This code should never be executed but if something
' goes wrong there is a message for the user.
Call MsgBox("Please report there has been a InputBox " & _
"error type 1 to Chaka", vbCritical)
Exit Sub
End If
End If
If VarType(ReturnValue) <> vbString Then
' False or a string are the only documented return values
' This code should never be executed but if something
' goes wrong there is a message for the user.
Call MsgBox("Please report there has been a InputBox " & _
"error type 2 to Chaka", vbCritical)
Exit Sub
End If
If ReturnValue = "" Then
' User has clicked cancel or left the text box empty.
Exit Sub
End If
If IsNumeric(ReturnValue) Then
InxList = ReturnValue - 1
If InxList >= 0 And InxList <= UBound(ListName) Then
' Good selection
Exit Do
End If
End If
Loop
With Sheets("Sheet2")
If .AutoFilterMode Then
' AutoFilter is on. Cancel current selection before applying
' new one because criteria are additive.
.AutoFilterMode = False
End If
.Cells.AutoFilter Field:=ColNum(InxList), Criteria1:="x"
End With
End Sub
A simple solution, hope this helps
Here's a simple solution I figured:
Copy the top row with the heading from the main sheet to all the
sheets
Paste this formula in field A2 of each sheet: ='MainSheet'!A2:I555 (number
555 can be increased as per requirement)
Drag this first row-wise, then column-wise till the end
In the top row with the heading, filter the data as per your requirement, for e.g. in the MainSheet, data can be filtered on any column on any value needed
Do this for all the sheets
When you update the data in the MainSheet, just re-run the filter to refresh the data
HTH,