MS access event load form - vba

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.

Related

Form reference technique in Access 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

Access runs function in control's RowSource when form opened in Design View

I have an Access 2007 form which contains a combo box with the following Rowsource:
SELECT qryProjectsIHaveAccessTo.projID, qryProjectsIHaveAccessTo.projName
FROM qryProjectsIHaveAccessTo
WHERE (((qryProjectsIHaveAccessTo.projSupportTracker)=False));
The query qryProjectsIHaveAccessTo uses a user-defined function as the criteria for one of the columns. This function detects if a Startup routine has been run, and if not runs it.
The problem I have is this: if I SHIFT+open the database and open the form in DESIGN mode, for some reason the user-defined function starts running. This then causes errors as the Startup routine it calls tries to open a form (presumably Access cannot open a form while it is in the process of opening another form in Design mode) and sometimes I am not able to CTRL+BREAK out of it.
The same thing happens when I go to save the form in Design mode.
If I remove the RowSource string from the combo box, this no longer happens. But why would a function which is called in a query included in a control RowSource get fired when the form opens in Design Mode?
Any ideas anyone? Thanks for reading!
When you open a form in design view, Access validates that the form record source is still there, and that all the query fields that are bound to form controls are still there.
If some of these checks fail, Access shows the small green triangle on the bound controls with problems.
While doing this check, it runs the UDF in the query.
As was said in the comments, calling a startup function does very much not belong into UDFs that are called from a query. Put it into a function that is called by an AutoExec macro.

Reset usercontrol placed on form

Probably simple question but couldn't find any working solution. I have User control on windows form. I would like on button click placed on user control to reload user control. By reload i mean to reset all its variables and show again. How to achieve that?
There is nothing that does that. You could dispose the existing control and create a new one but noone would do that. Basically, it's a manual process. If you want the control's fields reset to defaults then you need to write code to do that. You might declare a Reset method and put all the code in there if that's appropriate and then you can simply call that method from the form.

How to update an Access VBA app with 30 forms?

I need to update an Access VBA app with around 30 forms in it.
I have to amend a screen that seems to have been set up right at the start of the app, it uses a lot of SQL tables. Is there an way of finding my way to the start of the code?
I come from a procedural coding background and I am unused to code that doesn't have a start and an end; I also know a bit of VB, some ASP, some .Net and general computing.
When something "automagically" happens upon opening an Access database, it is almost always because
A "startup form" has been specified. (In Access_2010 that's done in File > Options > Current Database > Display Form.) ...or...
The database has a Macro named AutoExec which is automatically run when the database is opened (unless you bypass it by holding the [Shift] key down while opening).
In addition to #Gord's answer, there's a few things you need to know. I'm going to give you the quick & dirty version.
First, there's 2 types of code in Access. VBA & macros. Sometimes what's called a macro, is really VBA.
In Access, a macro is a set of instructions to do something to the database. It's very limited in what it can do. These are often used by novices who don't know how to program in VBA.
VBA is the real powerhouse behind the scenes. It can do everything a macro can do, but a whole lot more.
Access uses an Event-Driven / Object-Oriented (at least close enough for this discussion) interface. Do a Google search on those meanings. But very quickly, the listbox on a form is an object. It has properties (like width), methods (add an item), and events (click on an item).
To see the code, for macros look to to your navigation window to your left. For VBA (modules), look to the same window, or just press Alt-F11. VBA can be used standalone in a module, or behind the scenes of a form or report.
Once you get the hang of it, you'll find Access to be a handy RAD tool for small projects.
Good luck.
It appears that you already have found the form that opens when the app starts (if not, check out Gord Thompson's answer).
The first things that happen when an Access Form opens (the "start of the code", as you called it) are the Load and Open events.
If there is any code in this form that is connected to these events, then it's in the Form_Load() and Form_Open() functions in the code of the form.

ASP.NET Keep fileupload after postback

I'm writing an intranet ASP.NET page using VB.NET. I've run into a particularly nasty problem dealing with handling file uploads. I'll do my best to explain the problem, and perhaps someone can help.
My problem is almost a duplicate of this one, or this one, except (other than the filename) I don't care about sending the file to the server until the other data has been reviewed.
Here's the situation:
Joe Q. Dataentry inputs some data into several fields. The first 3 are drop down, and when he changes the selection, a postback event is fired that queries a database for valid entries for the other drop down selections. After selecting the values, he inputs some other data, chooses a file to accompany the data and clicks the "Update" button. When he hits the button, it fires a postback event that sends the current data to the server to be validated. The data will create a change in the database, so he is presented with a view of the current state, and what it will look like when his changes are made. He can now either confirm or cancel the operation for whatever reason.
Part of the data he will see involves the extension of the file which may be a PDF, or could also be some image file or other document.
Now here's where my problem is - on each postback event, the fileupload dialog is cleared. I was getting around it by creating a temporary file on the first postback and then renaming if he clicks OK or deleting on Cancel... but I need to do a variety of things, based on the previous state of data and the filename. I've tried to keep some session variables to retain the filename, and that works OK for just renaming the file, but for what I need to do it gets unwieldy.
What I want to do is be able to have the postback event to present the changes, and then when the user clicks "OK", submit the file. Is there any possible way to do that?
One of my thoughts was to do some of the validation client-side (I'm already re-validating server side so I'm not too worried about data security there), but I don't know how I could get the information from the database query.
Thanks for any help, and reading my slightly convoluted story/situation!
EDIT:
It appears that what I want to do is prevent a certain button from firing a full postback. Is there any way to do that?
EDIT II:
I have an update panel on the page already - is there any way for the button to only post what's in the update panel?
What you might want to do is place your drop-downs inside of an ASP.NET AJAX UpdatePanel, and keep your file upload control out of that.
Your update panel will do the post backs and allow your validation logic to happen without submitting the file, then when you hit your final "Save" button (which is also outside of your UpdatePanel) the entire form will be submitted back and you can work with your file then.