Form reference technique in Access VBA - vba

As per a suggestion in the comments from here, I am starting a thread to learn the reference technique for passing values and variables from form to form, also discussed here and here. As a side note, to the best of my knowledge, it's not the technique referred to here and here.
Those previous questions demonstrated that the following code in the called form, sets up this technique.
Dim prevForm As Form
Private Sub Form_Load()
Set prevForm = Screen.ActiveForm
End Sub
For an Access beginner such as myself, the commenter from the first link also suggests using a dialog form for the form being called, for simplicity. Should it already be set that way in the form's Property sheet, or handled on-the-fly using the DoCmd.OpenForm's acDialog argument?

Ok, there is several issues here.
As always, one particular suggestion applies to a particular case and goal you are trying to achieve.
So, for example:
I need to get a simple yes/no from the user.
So, for a simple yes/no, or “do you want to do this” type of prompt?
Then you can use this:
Eg this:
If MsgBox("Do you want to create a invoice", vbQuestion + vbYesNo, _
"Create") = vbYes Then
'
' code here to create a invoice, or maybe launch a invoice form.
Else
' user cancled - clikced no
' code here to do what you want if user answers no,
' or maybe we do nothing
End If
And the prompt is like this:
So, for some simple types of UI interaction you don’t need a form.
Ok, the next scenario?
I want to pass some values?
Well, in some cases, if you use a sub form, then you don’t really have to pass values, since you can with ease reference both forms.
For code in the sub form?
You can go:
Me.parent!InvoiceNumber
So, in a sub form, you can “always” reference the parent form with “me.Parent”. This is built in, and VERY nice, since you NEVER thus hard code the forms name.
So, if you copy the form, rename it etc., then your code will not break. So in this example, we don’t’ have to pass values that the sub form might need, since there is already a built in means and approach to get at, and reference values/controls in the parent form.
Not necessary – only if you need to!!!
It will depend on what you are doing.
Say, I am on a customer form, and I now want to add a new invoice for that customer.
I can do this two ways.
Simply launch a new “create/edit invoice” form to a new record. However, in this case I would have to pass to the new form WHICH customer ID the new record will be attached to (we assume that the invoice form is a child table of the customer form).
So in this case:
We will launch the invoice form in “add mode”.
We will have to pass to the form the correct customer ID that this new invoice record belongs to.
In this case? VERY little need to make this a dialog form.
And very little need exists to have calling code wait.
So, in the main form, we would have a button called create new invoice.
It would/could go:
DoCmd.OpenForm "frmEditInvoice", , , , acFormAdd, , Me!ID
So, we simply launch the next form. And there is no real other code in that “main” customer form that has to run. Or any code that has to “wait” until the user is done adding/creating invoices. So, there is little or no need to use a dialog form.
But, we did need ONE very important bit of information passed to the invoice create form. That is the primary key “ID” of the customer that the invoice will be attached to (related to).
So, I used the “open Args” of the open form method. This is a nice a simple option to pass a value to a form you open. The limitation of course is that “open args” is ONLY good for passing one value. But, it often will suffice.
So, now in the invoice forms on-load event, we have to setup the related record. We could go:
Me!Customer_id = me.OpenArgs
However, there is ONE tip I would suggest:
Make the invoice form “modal”. What modal means is that the user MUST close the invoice form when done, and thus user returns to the main customer form upon closing the invoice working form?
If you don’t make the invoice form modal? Well, a user might start typing into the invoice form, decide they don’t like or want the invoice form, and will simply go back to the customer form, and edit, or even search/find a different customer. Now you have an open invoice form, one that not completed, and not saved, and if the user starts jumping around to different customers? Well, we don’t want that.
So, if you set the invoice edit form as “modal”, then the user must close that form to return to the previous form. So using modal forms (not dialog – very different), is a great way to control the user and “flow” of your application. If you don’t have “some” control, then you and your code will fast spiral out of control, since you have a customer form open, and the user might well be trying to add/edit an invoice that does not even belong to that customer. So, using modal will force the user to finish or close the form to return back to where he came from.
Also, the above is “air code”, I VERY MUCH STRONG recommend you don’t use the on-load event to set (connect) the record using the on-open, or on-load event. You actually want to use the on-insert event. But this post is too long, and I’ll need 2-3 pages of explain as to why this makes sense.
Anyway, so, the above case?
We did not need a dialog form (usually for answering yes/no kinds of questions).
We ONLY has to pass one little value – so, passing that value we used the “open args” parameter of the open form.
And, we set that form as “modal”, since we do not want the user to jump and sneak back to the customer form until such time they are “done” entering the invoice, and they MUST close that invoice form to return back to the customer form.
However, this “concept” of separate windows forms being launched and users having to “move around” re-size and worry about messing with a window? We don’t’ do that much anymore!
Due to smartphone, and tablets? And how all the web browsers work? Well, a separate window is fast becoming “old school”. And that is why Access has the new “tabbed” interface – so it works much like a browser now.
As a result of tablets and UI trends in our industry? (And what users now expect how software works?). Well, then the new “tabbed” interface option in access is a common choice and this choice will often effect how you cobble together forms flow for the UI.
Also, I don’t recommended dialog forms unless you wanting to prompt the user, and then take action as a result.
Ok, so in our above example? Let’s say when we create the invoice, we want and need a FEW more values from that customer form. Say we want the default shipping address (a single foreign key value to the shipping address table), maybe the default invoice terms (you know, net 30 days etc. – again a FK value to some table), and of course the PK customer ID value.
So, now we need to pass say 3 values.
Well, we can’t use openargs, since that is quite much only good for one value (some people do pass a delimited string, and parse out the values – so you can with some hoops pass multiple values with openargs, but it tends to be messy for anything more than one value).
So, so, now let’s pass 3 values to that invoice form we open.
Well, first, we can ask do we really need to pass values.
We could do this code:
Dim f As String
f = "frmEditInvoice"
DoCmd.OpenForm f, , , , acFormAdd
Forms(f)!customer_id = Me!ID
Forms(f)!ShippingAddress = Me!ShipID
Forms(f)!InvoiceTerms = Me!InvoiceTermsID
So, in above, the main form (calling form) launched the new form, and then simply set some values in that target (2nd) form.
So, in many cases you really don’t and did not need to pass all that stuff. Just open the form, and set the textboxes or values you want right after you launch/open that form.
However, for an example, let’s pass the values to the invoice form.
In this case, we are now going to use the “frmPrevious” trick. In effect, we don’t pass all the values, but ONLY pass the calling form, and thus the invoice form is free to reference and use “any thing” from that calling form. (Welcome to the world of using objects here! So, the old school idea of passing a whole bunch of values? You really don’t need to. Once that invoice form has a reference (a pointer) to the previous form? Well then it can grab/use/see as many values from that calling form. And this INCLUDES the user of variables you declared in your main form!
So, if you can get/grab controls, values, and even variables from the calling form? Well, then you really don’t have to pass much of anything, and there is little need.
So, to save world poverty and not have to try and pass a gazillion values? Let’s use the suggestion of screen active.
So, our code becomes this:
DoCmd.OpenForm "frmEditInvoice", , , , acFormAdd
And in the form invoice?
At the main module level, we have this:
Option Compare Database
Option Explicit
public frmPrevious as form
And the forms on-load:
we can go:
Dim frmPrevious As Form
Set frmPrevous = Screen.ActiveForm
Me!customer_id = frmPrevious!ID
Me!ShippingAddress = frmPrevious!ShipID
Me!InvoiceTerms = Me!InvoiceTermsID
So, note again, by grabbing a reference to the calling form? Well now I can grab 2 or 20 values. So, in object programming, you don’t pass a whole bunch of values, but a reference to the object in question. While Access is not a full OO language? It has good use of objects, and you can even create custom objects.
So, up to this point, we not needed, nor wanted, nor even warranted the use of a dialog form.
I tend to only suggest a dialog form WHEN you need to prompt the user and you need “several” things for the user to enter as opposed to a simple yes/no.
So, the idea here about passing values? Well, the whole idea is that you don’t need to pass values when you can get a reference to the previous “object”, and thus grab as many values as you please.
So, adopting an object approach will require some change from traditional programming where we think of everything as some subroutine we call and we pass values to that sub. In this case, we are in effect picking up the previous forms object, and once we do that, then we can quite much grab anything from that previous form – including VBA variables if you wish.
Keep in mind, that what you reference in that previous form does not necessary have to be controls. You can also reference variables. So, you could setup 3 variables, set their values, and then in the form you called/opened? It can get/grab reference those values. So, you might have a case in which one special type of form is called by MANY forms, and those forms will have all kinds of different controls and field names, but as long as ANY of the calling forms follows your new “made up” standard of having say 3 known variables declared in the calling form? Then the receiving form can reference those VBA values.
I would certainly admit that in most cases the values you are passing are going to be values in controls on the form, but you are not limited to just controls – variables declared at the module level and as public in the calling form can be used in the target form.
Eg;
frmPrevous.SomeVBAVariableNameGoesHere
Final notes and clearing up the use of screen.ActiveForm.
You do NOT want willy nilly to just use screen.ActiveForm all over the place, as it can change without you really controlling when and how.
However, VERY important:
You PICK UP, GET/GRAB the screen.ActiveForm in the on-open event of the target form. You wnat to do this first, and fast.
You can even wait and do this previous form pick up trick in the on-load event. The current form DOES NOT become active until such time both the on-open event, and the on-load event have 100% completed.
I have used this approach for about 20 years and I am not aware of ONE failure in regards to having picked up the previous calling form by grabbing the previous form reference in the on-load, or on-open event.
It is VERY reliable. Once that reference has been picked up, then screen focus changes etc. will NOT matter, since you grabbed a working reference ONE TIME and only one time in the forms on-open event, or as I noted, you can even do this as late in the on-load event.
So a HUGE difference of a suggesting here. I am not suggesting or advocating using screen.ActiveForm any old place, but I am suggesting a ONE TIME grabbing of screen.ActiveForm to get the calling form's reference - I have found this approach to be dead 100% reliable.

Try to avoid Screen.ActiveForm: it is very dangerous, because asynchronous events can generate wrong information, even errors due to the form not being open yet
Try, in the second form, to declare a public variable, or better a property declared as Form (example FrmOldForm as Access. Form)
Private m_objFrmForm As Access.Form
Public Property Get FrmForm() As Access.Form
Set FrmForm = m_objFrmForm
End Property
Public Property Set FrmForm(ByVal objNewValue As Access.Form)
Set m_objFrmFormu = objNewValue
End Property
Open the new form, and set FrmOldForm in new Form as the old Form.
set Forms("NewForm").frmForm = me.form
Now, and whenever the old form is open, you will have a "window" to it, and you can call the old form from the new one.
I use this technique with classes, but it should work directly

Related

Can Access ensure a new form record displays all fields?

I have a database where I don't want some fields showing depending on data in other fields. I'm still new to VBA, having learnt how to do things I need via the internet (there's not much call for it in my job so like to try it out on side projects) for the things I need and have managed to create some code that hides certain fields that aren't needed, depending on what's been entered in another field and that works okay, if not perfectly (I'd like it to only work on the current record and not all of them at once but will worry about that later). My problem is, if I'm entering information onto a record and any of those fields become invisible exactly as I would want them to, then if I have more records to complete and load a new record, those hidden fields are also hidden on the blank record before any data has been entered and I want each new record to show all fields from the outset.
Another thing I've noticed is that if I close the database, next time I go into it the hidden fields have unhidden themselves again so I know I'm missing something important.
Here's a screenshot of a bit of the code where I want 2 other fields (What_reason and Date_sent_to_new_owning_School) to be visible depending on whether the answer in the current field after update is "Standard" or "Non-standard":
I'm sorry if this is really entry-level stuff but I AM entry level and trying to learn. This bit does work, albeit not perfectly as I'd like it to only work on the record I'm in at the time, and not go through and hide that field in all the other records at once (which it's doing).
I've searched everywhere but can't find the answer and although I've tried, I'm nowhere near good enough at VBA to try and use common sense to work it out. Is this something that can be done? I'm okay with computers generally and with Access too but I'm aware there's an awful lot I don't know and this is why I'm trying to do new things and learn stuff that I've not used before. I have tried all day to get this to work but am admitting defeat and am hoping somebody here will be able to help me. I'll probably need 'idiot level' advice if that's possible, I know my limitations. :)
Do you know how to use the Event tab in the Property Sheet? You can set all of your fields to [field].Visible = True on either: On Current, On Load, or On Open
Screenshot of the Property Sheet and for the field that determines the visibility of all of the other fields; you can use the Event: After Update so that way when you click/tab away from that field, it'll make those changes for you!
Property setting affects ALL instances of control. Control will be visible/not visible for all records depending on conditions of current record. Therefore, dynamically hiding controls on form set in Continuous or Datasheet will NOT give the desired result of
only work on the current record and not all of them at once
Db is not going to 'remember' dynamic setting - code needs to be executed when form opens and/or navigating records - so it is needed in OnCurrent event as well as control's AfterUpdate.
Conditional Formatting can dynamically enable/disable textbox/combobox by record although control is still visible.

why does selecting "refresh fields on keyword change" load a whole new document?

Notes 9.01
Why does a new document on the web change unique IDs every time you refresh it?!? this causes all kinds of issues. Obviously it is a different document, so maybe I should re-phrase that, but I think you know what I am saying.
I have a listbox field, with the setting to "refresh fields on keyword change" selected. This allows hide-whens to recalc, and other fields to recalc. I also have a computed text showing the current #DocumentUniqueID.
choices are: "Select one":"one":"two":"three"
When using this form on the web, in a new document, and I pick something in this field from the drop-down, it refreshes the form, and the choice I just picked is removed and "Select one" is what is showing again.
Once the document is saved, this stabilizes and you do not switch documents, and field values do not get cleared. I just want to understand the logic of this and find out how other people work around this.
Any feedback would be great. If I am doing something stupid, please tell me, I can take it.
Matt
I don't know what the "all kinds of issues" that you're dealing with are, but for as far back as I can remember, Notes documents have not had a stable #DocumentUniqueID value (or any at all, actually) prior to being saved for the first time. It's been my practice, and I think pretty widely accepted practice, not to write code that would have issues with that. If it's been necessary to depend on some unique value in the document prior to the first save, I've always used a computed-when-composed field with #Unique for its value.
My suggestion would be to not use "refresh fields on keyword change" for a form used on the web. The way I would handle it is to use some JavaScript to handle that.
Personally I would do this, in your situation:
Add jQuery to the form, you can easily put a CDN link in the page
header.
Write a JavaScript function called (for example)brecalculateFields(). This function would calculate field valuesband perform hide/show of fields/sections of the form.
Set a class for all fields where you want to trigger a recalc of the fields when the value is changed. I would call the class recalcForm.
Bind the function recalculateFields() to the changed event of all fields with the class recalcForm.
You may want to bind the function to a few other events as well, depending on what type of fields you have on the form.
$(".recalcForm").on("change", function(e) {
recalculateFields();
});
$(".recalcForm").on("blur", function(e) {
recalculateFields();
});
I have blogged about this in the past, hopefully you can use some of the info there:
http://blog.texasswede.com/using-jquery-to-emulate-notes-hide-when/
http://blog.texasswede.com/jquery-a-flexible-way-to-showhide-sections/

MS access event load form

I have about 20 MS-Access databases with about 400 Forms and wish to perform an action whenever any form is loaded.
I would need, perhaps, an event at database or application level that would trigger at any form being opened/loaded. Then I might need only to add code once to each of the 20 databases, but not to each of the 400 forms.
And: it has to be in VBA (Access >= 2010)!
My Question: Is this even possible? And if so, does someone have a hint?
Thanks,
Pete
There are multiple ways to go about this, I think.
What I'd probably do is the following:
Create a hidden form that opens whenever your database opens (using an autoexec macro) with a timer.
When loading the hidden form, initialize a collection/multi-level array of all available forms in the database, and set their status to closed (somewhat difficult, probably would go with multilevel array or a collection inside a collection to be able to store form name + boolean open or closed).
Periodically iterate the collection, check for changes, so you can see whenever a form closes and trigger your event.
You could also use VBA to iterate through all the forms, add a module to them if there is none, and then add your desired Form_Load code to that module. (Would probably be wise to simultaneously create code to undo that action, so you can actually maintain the code). While refining that, you could check if the form already has a Form_Load action, and append code to that if it exists.
Alltogether, possible, but difficult. If you want pointers on some of the steps, I can give them, but for major issues on the implementation, I'd ask a separate question.

restricting a combobox to list values and outdated list values

I am creating a program that for simplicity's sake records the name of a staff member that receives a phone call. This program is designed to show old entries along with creating new ones.
The problem is that I want a user to only be able to select a listed name from the drop box when creating a new entry. But this list will only show current employees. Yet, when viewing older calls this combobox field also needs to display former employees that took a call that may no longer be in this list.
As far as I can tell with the Microsoft control and properties there is only 2 options that relate to this matter.
DropDownStyle as DropDown or DropDownList.
When using DropDown the user can submit any name (which is not wanted).
With DropDownList the user can only submit names on the list, but when browsing through old entries any names that are no longer on that list will not appear on their respective calls (which is also not wanted).
I'm aware I could end up having to implement my own combobox class but I wanted to see if anyone knew of a more elegant fix that combined both of these functionalities. Thanks!
It seems to me you have two modes. Add mode adds a new call record, while view mode displays old records.
Use the drop down list to restrict the user to what you load. When in Add mode, load the control with only current employees. When the form is in view mode, load with all employees.
Use DropDown. In the Validating event, set e.Cancel = True and instruct an ErrorProvider control to put up a warning with its SetError method if SelectedItem Is Nothing, but clear the error (by passing Nothing to SetError) otherwise. Then, in the combo box's SelectionChangeCommitted event, call the form's Validate method.

Parent form and child User Control communication in WinForms

I have my form with a menu bar and space underneath to display my controls. One of the buttons in my menu bar is suppose to be a print button that prints a graph that's currently in a User Control I display in the form. If the graph was on the form in the print button's eventhandler I could just simply call
graph.printing.print(true)
which isn't going to work in my case since the graph is in the control and not the form.
How do I communicate with a User Control from the containing form and access or pass its variables when needed? I also have a status bar on the bottom of the form which would also need to get updated from the User Control, but I'll be able to deal with that if I got help with just this one part. Please bear in mind, I also have another User Control I'm going to add to the form which will also contain a graph which will need the same treatment as the other graph on the first control when the print button is pressed. I plan on swapping these two out so I have one form displaying one control at a time.
I got this idea from this answer: https://stackoverflow.com/a/18191630/2567273 but after further research I can't find anyone asking about the actual communication process between a form and the control it contains.
I think this answer is close to what I'm looking for, but I think it's leading me down the path to using panels instead of User Controls.
After typing this I noticed the closest answer to my question may be this, but that question has the child raising events and the parent responding while I have the parent raising the event and the parent has to get information from the child.
One way to think about this is Roles. Presumably you built this UserControl to handle the management of the data related to the graphs. As such you can think of them in the Role of a Graphs Specialist . Once you do that, printing them is actually just one more thing it should perhaps do.
The form on the other hand, is not special just because it happens to get receive the command from the user to print. Its role in this might simply to be to know which usercontrol to contact and which method to invoke:
Sub PrintGraphMenuClick....
Select Case something ' determinant as to which UC to contact
Case operation.Foo
ucFoo.PrintGraph
Case operation.Bar
ucBar.PrintGraph
Other menu options like Clear, NewGraph, Save and whatever else there is somewhat the same way. The Form's Role here may be to receive the command from the user and pass it along to the right control, invoking the correct right method and passing the correct parameters - that is not a trivial task.
Of course, rather than a MainMenu, the usercontols could alternatively implement a ContextMenu and even receive those commands directly.
Very often offloading an operation to something else results in so many properties, filenames, streams etc having to be moved from here to there that it becomes burdensome. In this case it is not like the MainForm has some special ability regarding printers that the UserControl cannot handle.
There is only one right solution:
1) Add an event to your user control.
2) Raise the event when the particular "thing" happens in the user control.
3) Attach a handler to the event in Form code.
4) Add code to update the bottom bar in the event handler.