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.
I have a form for viewing data in a table, which can open a pop up form to edit the data.
When data is edited in the pop up form, Access throws a write conflict error upon closing that form and returning to the original.
The strange behavior I'm experiencing is that when I make a change to the popup's class module in VBA, even if I immediately undo the change and save, the error will be resolved exactly once.
To be clear:
I make a change to the popup's class module in VBA (even a change I immediately undo and re-save)
I can open the pop up from my original form, edit records, then close the pop up. No error is thrown on closing the pop up.
I then open the pop up and edit records a second time. Then upon closing the pop up a write conflict error is displayed. This continues each subsequent time.
I now make a change to the popup's class module (again, even a trivial one), the error is resolved for one edit again, and the process repeats.
Does anybody know why this behavior could be occurring? Any help would be much appreciated.
Well the issue centers on if two users, or in this case two bits of code try to edit a record that is already dirty.
If the main form causes the record to become dirty (changed, but not saved), if then you run ANY other code that ALSO changes that data, then you get the conflict.
The simple solution then is to ALWAYS ensure that the the current forms record is safely tucked away and saved before you run + call code that might change that dirty record. And that includes that popup form + class.
So in your code that launches the form/class code? Do this RIGHT BEFORE you launch that 2nd form:
If me.Dirty = True then me.dirty = False
So, the above will safe write out the dirty record, and now you are free to launch/run any other code that might change that record. And because you run the above? Well, the record is not dirty anymore, and thus you should not see nor receive a write conflict error.
So adding a check to save if dirty before you launch/run that additional code/form will resolve this issue. In fact, as a habit, for just about "any" launching of additional UI forms, it a good idea to safe tuck away and save the current forms record when launching additional UI bits and parts.
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