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
When deleting records within the platform, this action is not reversible via the front end. Is there a way to allow users to remove a record from their view without actually deleting the record?
You can simulate recycle bin functionality within Archer GRC by adding a record permission field that grants read access to "Everyone". If read access is no longer required then an editor of the record can go in and change "Everyone" to a group called "Recycle Bin."
Please note that if there are other record permission fields in the application, users or groups may still have access if they are selected in those fields. Perhaps You can set up a dropdown status field for the user to select "Recycle Bin" and use this condition for automatic record permissions to revoke permission to the record depending on the requirements or workflow of the application.
Solution shared by Igritte might be somewhat confusing for end users.
End user will see greyed out "Delete" button in the top toolbar, but he has to select "Recycle Bin" in the form. This solution was not accepted by my business owner at some point.
As a work around for "Soft delete", I wrote a custom object overriding "Delete" button functionality.
1. User doesn't have delete access to the record, so JavaScript code will make "Delete" button look like active and available.
2. Once the button is clicked, custom object will populate value in the
hidden value list and simulate the click on the "Save" button.
Update: Note that Custom object needs to hide the value list first once the page is loaded. Here you will need to use a JavaScript and do the following: [a] locate the value list DOM object and [b] set display attribute to none. I used jQuery library to do both. This way your value list is not displayed, but you still can use it to control data driven events.
3. With hidden value populated and submitted, record permission will hide this record from the end user.
Note that custom object hides one value list on the layout as well.
If for some reason JavaScript doesn't load properly, user simply will not be able to click on the grayed out "Delete" button.
Update: Hidden value list can be populated by custom object using JavaScript code as well. You need to identify the form tag "input" in HTML code of the page and set attribute "value" to the desired state. I used jQuery library to do this as well.
I have this solution in production working fine with IE11, FF and Chrome.
I can't share the code, but with WC3Schools JavaScript guides and 4 hours you can write and test it yourself with very little JavaScript skills.
Sometimes you have to use custom objects when you want to get a user-friendly solution of not available functionality.
Good luck!
I am using Access for a quick and dirty (ADP) interface for an SQL (Express 2012) database so data entry can begin before the MVC web app interface is complete.
There is one field I want to be varchar, I would like this field to either allow the user to type in a value or select from a distinct list of values previously used in that field.
I have that part down, but the problem is when it happens, I have to refresh the recordset to see the new item in the list, so if they choose add a new record, then the last item added is not visible in the list.
So I can get the distinct list, populate the box, allow for new entry, and save that to the DB, do I have to write a code behind to repopulate the recordset, do I need to write a code behind to maintain the list paralleled to the recordset, or is there just a property I am missing?
Thanks
(Added screen showing event)
As suggested, using the on current event on the form and the after update solved the problem.
Clicking the form section detail selected the detail sections property page not the form. Selecting the form from the drop down on the property page displayed the events I was suggested to use.
Many thanks to those contributing.
As the first suggestion of this was from Remau, with assist in locating that event from hansup, I will mark remau's post as answer. Thank you to both.
Don't requery the form, just requery the combo. The best bet is probably the current event which will work if more than one person is doing data entry. It will also work if people are editing the table as well as entering data. Events that only fire when a record is added will not pick up changes to the combo contents.
Private Sub Form_Current()
Me.MyCombo.Requery
End Sub
I am coding an invoicing application. The user should be able to add a new line to the billing section of the invoice by selecting a product from a ComboBox, which subsequently fills out various other fields (price, description, etc) based on the selected product.
My problem is this: When the user adds a product, I would like the program to automatically generate a new line for the next product. It seems like the only way to accomplish this would be to create a new set of controls with an incremented name, i.e. Product1, Price1, Desc1 for the first product, Product2, Price2, Desc2 for the second product, etc. Is this possible, or am I going about it all wrong?
I had to do something like this at one point and what I did was create a custome user control, in your case it would consist of a combo box, that is filled with products on load, and all the related controls, which would be filled out on the selectedindexchanged event of the combo box. On your main form where you want to display this info place a panel and add your custom user control to the panels.controls collection.
since all the functionality will basically be contained in the usercontrol itself you shouldn't run into naming issues.
As for adding the user control when the user makes a selection, you could dim the control withevents and attach a custom event to the user control and handle it in the main form. since the controls would be added at run time you would need to attach the event handlers using the AddHandler call.
So to break it down you would need:
A Custom user control with all the controls you want for each product and an ID in the code behind
An event on your custom user control to tell the main form that the combo box has a selected value
A panel on your main form to hold the custom user controls
An event handler to add a new customer user control to the panel when a selection is made in the combo box.
if you want to interact with a certain product with the same button you can attach an ID in the code behind of the user control and set that everytime a new user control is added. this is not as hard it I think I make it sound, you can actually run linq on the panels control collection and get all the panels where any condition you designate is met and then do what you want from there.
As for your second question, I would assume your user control is pulling a bogus connection string from somewhere. I would try to track down where that is happening and go from there. Anything beyond that would just be a WAG and that doesn't really help you.