How can I make a form instance open a specific record on creation? - vba

I'm trying to optimize the performance of my access frontend. One of the problems I'm stumbling on is how to set a multi-window form to open immediately to a specific record, because at the moment it queries everything twice.
Essentially, I allow my users to open multiple instances of a form. That way, a user can compare multiple records by placing the windows of those forms side by side.
At the moment, my code looks like this:
Set frm = New Form_Name
frm.RecordSource = "select * from Table where id = " & ID 'ID is a variable passed to the method
I'm pretty sure back then this question was one of the building blocks I relied on.
The problem is that on the first line, access already entirely opens the form and does everything the form does when opening, such as Form_Open, Form_Current, and loading subforms. Then, when I set the recordsource, it does all (or most) of that again, which significantly slows down the process of opening the form.
Here are some of the things I've tried:
Changing the Form_Current to recognize that it's being "used" twice and only actually run the code once. The problem is that the code is already very optimized and doesn't seem to be the bottleneck, so this doesn't seem to do much. Actually opening the form seems to be the bottleneck.
Trying to change the properties of the original form so that it opens the specific record I need, but I can't seem to change the properties without it opening the form.
Looking for a way to supply arguments to the form. That doesn't seem to be supported in VBA.
One other idea that popped into my mind was creating a variable in vba with the ID of the record, setting recordsource in the form properties to fetch that variable, but then, when I would open another form and change that ID, the form
I realize that I can do this with DoCmd.OpenForm, but that doesn't give me the option to open multiple instances of the same form.
How can I achieve this? This would mean a significant performance improvement.

OK, so I actually wanted to ask this question. But just before I hit the submit button, I had an idea...
Here's the idea: you set a global variable in VBA, which you then access in the form filter command. In my case I just added Public OpeningID As Long at the top of my VBA module. Then I create a function to get that value:
Public Function getFormID() As Long
getFormID = OpeningID
End Function
Then, in the form, you can set the filter criteria as ID = getFormID(). And then when you open the form instance, you do it like this:
OpeningID = ID
Set frm = New Form_Name
OpenindID = 0 'reset this to make sure that if it's being accessed when it shouldn't, it generates a clear error
The only problem is what happens when you refresh the form, as that will call the method again. When you refresh the form via VBA, you can set the OpeningID beforehand, and to avoid users refreshing the form, you can add this to your form (don't forget to turn on Key Previews in the form settings):
Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
If KeyCode = vbKeyF5 Then KeyCode = 0
End Sub
Bang. Forms open almost twice as fast. Awesome.

Related

Why does MsAccess Record entry only Update (and show in table) after form close and reopen?

Some Background:
I have a database that acts as a ledger (keeps tabs on current payments).
1:Payments for each customer are stored in one table (PaymentTbl),
2:I have another table (TermAgreementTbl) that holds information on the agreed terms of the service
3: My last table (PdUpToTbl) takes the payment information as well as term agreements information from the two other tables and calculates/displays the information in a clearer manner. One of the ways it will do this, is by deleting the last record in PdUpToTbl, and replacing it, or by just adding a new record (case dependent).
Now MY Issue:
I have a form for my TermAgreementTbl that has a subform showing the relevant PdUpToTbl.
A button opens a pop-up form to enter a new payment and update the related PdUpTotbl.
Everything in the back end is functional, however after I enter a new payment (and save and close the pop-up payment form), NO new record is shown in my PdUpToTbl Subform. Instead it shows something like (some irrelevant info redacted):
For the new record to display properly, I have to close the entire form, and reopen it. There has got to be a way to get around this through vba with some code, right?
Thank you for taking the time.
Edit 1:
By the way, after I perform A LOT of vba code, I use this to enter my record:
With pdUpToRS
.AddNew
![DatePaid] = NewRecordSet.Fields("DatePaid").Value
![Amount] = Amount
![AppliedAmount] = AppliedAmount
![OnAcct] = OnAcct
![AllPdUpTo] = AllPdUpTo
![RemainBalDue] = RemainBalDue
![PdUpToString] = PdUpToString
![PaymentType] = NewRecordSet.Fields("PaymentType").Value
![PaymentNumber] = PaymentNumber
![ID] = NewRecordSet.Fields("ID").Value
![PmntTblID] = PmntTblID
![BdCk] = BdCk
![Late] = Lte
![ApplyDiscount] = ApplyDiscount
![ForgetUnderage] = ForgetUnder
![ForgetOverage] = ForgetOver
![Note] = Note
.Update
End With
Update using requery
I have tried to Requery using:
Forms![MainForm]![Subform].Requery
But it gives me the error:
2118 - - - You must save the current field before you run the Requery action.
And if I add the save line:
DoCmd.RunCommand acCmdSaveRecord
Forms![MainForm]![Subform].Requery
I get the resulting error:
2046 - - - The command or action 'SaveRecord' isn't available now.
Ok, the docmd "menu" options to save a record?
They OFTEN run on the form that has the current focus - so often, then the form you want to save is not the one you expected.
I suggest you replace this:
DoCmd.RunCommand acCmdSaveRecord
with
if me.Dirty then me.Dirty = false
Now, above above "me" is is the current form WHERE that code is running, not some form that might happen to have the focus.
Now, as for a form "requery" (to refresh without close then re-open)?
Again, assuming the above just did the save of the data, then to force a re-load of the current form (again, the form in which the code is running), then:
me.Requery
In fact, if you did not have multiple sub-forms, then a me.refresh would probably work (and a me.Refresh will also save the current record).
So, while the if me.dirty = true then me.dirty = false is about the best way to save the current reocrd in the current form where the code is running?
It is a question of where your code is running, and often when the code is running.
In place of the me.dirty = false, you can also do this, but it often will cause a lot more flicker and refreshing then you want.
But, the shortest code to ensure a save of the forms data, and then requery (same as form close then open), would thus be this:
Me.Refresh
me.Requery
However, often issues can arise if you have some dialog form open - so perhaps a closer look at how your code is updating is often imporant.
But, a me.Requery will re-load everything, but you of course want to ensure you force record saves of the data first.
I think the Requery function will serve you well.
https://learn.microsoft.com/en-us/office/vba/api/access.subform.requery

Refreshing form from timer in another form

I have a problem with the snippet of the code I have got to extend and improve. It is not my original code and I cannot change the logic that much of it, just to be clear.
I have one main form called MDIServer, which has a timer set for every second. In Timer.Tick I have some other code which works fine (timer is running okay). Newly, I had to check there, if one form is Active and if so, change some stuff (labels text and tags) in that form and refresh it.
I add there this code:
If IsActiveForm("frmName") Then
frmName.ChangeSomething()
End If
The Sub ChangeSomething is, how you can see, located in the form I want to refresh and do the changes. In that function I simply change the label text and tags of few controls.
My question is: Form is not refreshing => labels are not visible changed, why?
I think I tried already almost anything with Refresh() function in the ChangeSomething() function or in the timer after I called this function. Also I tried to add there new timer (in frmName) and do the changes there, which works perfectly with
Label.Text = "something new"
Label.Refresh()
So I guess problem is somewhere with the refreshing form from Timer in different form. I also tried to do it with my own InvokeReguired() function etc...
P.S. When I am debugging the code, labels and tags are changing and every single function which has to be called, is called, but it is just not visible on the form itself.
EDIT Info
formName is not declared in MDIServer explicitely and in this case and many other cases, forms are used as default instances. Timer is from System.Windows.Forms.Timer. Also MDIServer is not a MDIParent of the formName and I cannot use Me.ActiveMdiChild Is. Lets just say, these two forms are not dependent on each other in any way.. and everything is done through name of the form (default instance, so nothing like Dim frm As Form and frm = frmName).
I would be really glad for any tip or anything :D
Thanks guys,
Vojta
So, I fixed my problem after some research and the problem was (expected) that I am not calling the subroutine ChangeSOmething() for one specific instance of the form frmName. So I had to change my code, that I will call it exactly for the instance which is active and visible.
New code looks like this:
Dim frmCollection = Windows.Forms.Application.OpenForms
Dim listfrmname = frmCollection.OfType(Of frmName).ToList()
If listfrmName.Count > 0 Then
Dim tmpFrm As frmName = listVZT15.Last()
tmpFrm.ChangeSomething()
End If
I also could not use combination of frmCollection.OfType(Of frmName).Any and frmCollection.Item("frmName"), because when I was closing the form and opening again, it created new and new instances (I dont know, why it is not closing the old one, but like I said, it is not my code). So the logic is, to list all open forms of the needed type, and then take the last instance from that list and for that instance call the subroutine. Also Me.Refresh() is placed in the subroutine ChangeSomething() itself.
Thanks everyone for help, it surely helped me to understand, how the instances works here.

MS Access Database form not updating after SQL requery

I have a report that runs from a query. The query does use a global variable but this is not the problem but needed for the explanation. The function for the variable is:
Function Var1() As String
Var1 = strVar1
End Function
The query where statement is:
WHERE (((IIf([MinOfDueDayMin]<0,0,Int([MinOfDueDayMin]/7)+1))<Var1()+1) AND ((tblEquipment.Retired)=False))
which uses the var1 function
The criteria is on a field that is actually a calculation and that is where I think the problem starts.
The report is run for a command on another form using the following code:
strVar1 = InputBox("Enter Number of Weeks for report")
If strVar1 = "" Then Exit Sub
DoCmd.OpenReport "rptEquipPmSchedule", acViewReport
Everything works just fine
On the report I have a double click event that opens a form. This form uses part of the same query. (not the same one but two levels higher) thiS allow the user to change things so i expect to use requery for the report.
If i double click and then not even change anything and then go back to the report I have #ERROR in the fields that have the calculations
i put a me.requery in the activate event of the report. this did not work.
So I tried a work around.
When I double click the report field, i close the report and send the strVar1 value to the form that is opened. then when I close the form I reasign the strVar1 just in case it is lost be an assignment by another user (currently I am the only one using this but did it just to be sure it had the correct value.) Then I open the report again but still get the errors. I did not expect this at all. thought starting the report from scratch would certainly work. I even closed the form just after assigning strVar1.
then in final effort. When I close the form I run the exact same code:
strVar1 = InputBox("Enter Number of Weeks for report")
If strVar1 = "" Then Exit Sub
DoCmd.OpenReport "rptEquipPmSchedule", acViewReport
Which will force the user to input the value for strVal1. Even though this is not what I want but tried this for troubleshooting and I still get #ERROR.
When I run the report for a form that does not have any of the same field, no issues. When I run the report or keep it open with a requery from the form that has the same data, the report will not give the correct results. Note that if I run the query itself, the data in the query is correct.
i also tried using a number instead of Val1() in the query and got the same results.
i also tried the refresh button in the ribbon and get Unknown Function Name and all the data in the report is lost.
Anyone got any ideas??
While your textual explanation is difficult to understand the entire scope, consider re-assessing your workflow. The entire objective is to allow users to run customized criteria for reporting. And your main issue is the strVal does not persist in memory so all references to it fails.
Consider the following points:
Have users set criteria on a dedicated unbound form with button click for report where that report instance is immutable for viewing/printing only and if needed to be changed must be re-run (i.e., button re-clicked).
Access has no need for VBA's InputBox() as strVal can be an unbound textbox on this unbound form whose value remains intact for all open windows.
Have function and all its references point to form field: Form!myFormName!strValuetextbox
Because reports on pretty much any software/web app system is not used as a GUI interface to run actions, users will know if they intend to change report criteria, close current report or go back to entry point and change strVal then re-click button to re-run report.
Keep data entry/input (primary use of forms) separate from data export/output (like reports). From developer and user standpoint this compartmentalization will save you headaches down the road.

VB.Net ComboBox (as Dropdown) not translating text to DisplayMember with Databinding

I inherited a fairly large project at work that is undocumented and written in VB (originally started pre .NET, ended around .NET 2). I'm in the process of updating / refreshing a lot of the code, but have run into an annoying issue that I haven't found the solution for yet. This system utilizes a UI, a Web Service, and a SQL DB.
Problem: I have a Databound Combobox (originally set to DropDownList - I'm changing it to DropDown, which is what started this mess - going back isn't an option) that is tied to a DataSet that comes from a Web Service. When a user types in the item they want manually, the data from the text field doesn't seem to associate itself with the DisplayMember, which forces the WS/SQL query to fail (it is sent a blank value when it's expecting a ValueMember). If the user types in a partial selection and then chooses the value they want from the DisplayMember list using the arrow keys or tab, the query goes off without a problem.
My Question: How do I get the text field to translate to the DisplayMember which will then properly tie itself to the ValueMember which will then allow the query to execute correctly? Sorry for making this sound complicated or convoluted; I'm sure the answer is easy and I'm just glazing over it.
The relevant bit of code is:
With cmbDID
If dtsLU.Tables.Contains(reqTable) = True Then
.DataSource = dtsLU.Tables(reqTable)
.DisplayMember = "zip"
.ValueMember = "gridID"
End If
End With
cmbDID.DataBindings.Clear()
cmbDID.DataBindings.Add("SelectedValue", dtsData, strDT & ".gridID")
I've tried changing "SelectedValue" to "Text", which almost works - but it directly translates to gridID and skips zip which ends up with an incorrect Web Service response since the zip and gridID field values are not synced (zip (DisplayMember) may be 5123 while gridID (ValueMember) may be 6047). I've tried changing "SelectedValue" to "SelectedIndex", and that got me no where.
Any help is greatly appreciated.
EDIT
To add some clarification to the process, the below pseudo code / description is roughly what happens. I could post the whole module, but I feel that would just muddy the whole question even more.
Private Sub A
FormAlpha is created with 1 ComboBox in the form of a DropDown
This DropDown is populated with a DataSet
DataBinding with a blank DataSet is added to the control to keep track of the users input
End Sub
lblSubmit_Click event is triggered on FormAlpha by the user after they have populated the DropDown with their data. lblSubmit_Click calls Private Sub Submit
Private Sub Submit
BindingContext(DropDown DataSet, tableName).EndCurrentEdit() is called
DataSet.HasChanges() is processed
If changes are present, changes are processed
HERE lies the problem
If the user has manually typed in the DropDown field, but not hit an arrow key or tab, then the DataSet registers a change, but returns a null value in all fields - it knows something was entered, but that data apparently didn't pass through the DataSet for the ComboBox (ListItems or SelectedIndex didn't change / fire I'm guessing). If the user selects the item with the arrow keys, the DataSet has the proper input (I'm assuming the Data was validated by the control at this point).
If the processed data is good, a value is entered into the database
If the processed data is bad (empty), an error is returned
End Sub
If the above can't be solved with what I've provided, but someone still knows a better way to handle this type of situation, I'm all ears. Rewriting the module isn't ideal, but fixing this problem is a necessity.
Alright, while this fix may not be ideal, it is a fix none the less.
The bare bones problem was that the text value of the DropDown wasn't causing the data to actually affect the SelectedIndex / SelectedValue of the control unless you interacted with it using the arrow keys or a mouse click. So, while the DropDown would read "1234", in reality the control saw "".
The fix I have in place for this is simply calling comboBox.text = comboBox.text whenever the user hits the submit button.

Updating a control on another form with the results of a dialog box

I made a windows form which contains a listbox (named VenueList). The listbox is pulling it's values from a list provided by a binding source VenuesBindingSource which is pulling in a ID for the value and a name for the text.
There is a button which is causing a DialogBox to appear which is asking for values to store in the database for a NEW venue. Once the values are filled and insert the database, what's supposed to happen is that the dialog box closes and goes back to the original form which invoked it.
However, instead of updating the list. The list stays the same. But if you close the form and reopen it, you see that a new value was added.
TournamentSettings.VenuesTableAdapter.InsertVenueQuery(Trim(VenueNameTxt.Text), Trim(VenueAddress1Txt.Text), Trim(VenueAddress2Txt.Text), Trim(VenueCityTxt.Text), Trim(VenueProvinceTxt.Text), Trim(VenueZipTxt.Text), Trim(CountryBox.SelectedValue), Trim(VenuePhoneNo.Text), VenueType.SelectedText, VenueWebAddress)
TournamentSettings.VenuesTableAdapter.Fill(TournamentSettings.VenueNameList.Venues)
In the above code, InsertVenueQuery is the name of a query from the designer which is invoked to add the values onto the tableadapter VenuesTableAdapter which is used to fill the combo box on load. I also sent the Fill command to refill the table with the new value.
So the question is, should I go about doing this another way, rather than feeding the Table adapter and sending a fill command on to the datatable? Or is there something that I'm not doing here which I should to force that value into the list. I'm tempted to redo everything outside of the designer but that's a lot of code since I have to essentially run two commands (one to insert the data, and another to get the ##IDENTITY value since this is run on an access database.)
Okay. This one I had to think about for a moment.
Instead of me creating a block of done on the load event, I instead created a sub function called "FillVenueList".
I used the following block of code:
Public Sub FillVenueList()
' Adding values from database to a datatable.
' From there will add to the list box.
Dim VenueConnection As New OleDb.OleDbConnection(DBconnection)
VenueConnection.Open()
Dim VenueConnectionQuery As New OleDb.OleDbCommand("SELECT VenueID, VenueName FROM Venues", VenueConnection)
Dim VenueDataAdapter As New OleDb.OleDbDataAdapter(VenueConnectionQuery)
Dim VenueDataSet As New DataSet
VenueDataAdapter.Fill(VenueDataSet, "Venues")
TrnVenueLst.DataSource = VenueDataSet.Tables("Venues")
TrnVenueLst.DisplayMember = "VenueName"
TrnVenueLst.ValueMember = "VenueID"
VenueConnection.Close()
End Sub
From there I called THIS sub on both the form AND the Add Venue window and I can safely see that this works. SO THAT is how you get a new value onto the form, don't use it as a part of the Load Event but rather call it from the load event block, then call it when you want to add to the list.