4

So I've been working with a large access database recently and I've been thinking about the best way to begin refactoring it. One of the primary difficulties in refactoring, is most of the forms rely on data in other forms.

The way it is currently done is like so;

Form1 has a button on it called Cmd1 that opens up a new form (DoCmd.OpenForm "Form2") and pre-populates some controls on the form using Forms!Form2.Control = Me.Control. When closing Form2, data is returned to Form1 by calling Forms!Form1.Control = Me.Control.

This presents a problem if I want to change either forms' names, or which form opens the popup form. It also requires both forms to be open, which you can't really rely on when you can't use modal popup forms because users want to be able to swap between forms.

I see 5 methods of passing values back and forth between forms, each with their problems, and I was wondering what was recommended.

  1. The method already in place as explained above.
  2. Using OpenArgs - makes lots of values difficult as you have to parse a string and is uni-directional.
  3. Using global variables - This would mean tons of variables in a random module and presents a scoping nightmare.
  4. Storing values to be passed between forms in a temporary table.
  5. Maintaining a reference to the first form using

    Public master As Form
    Private Sub Form_Load()
        Set master = Screen.ActiveForm
        Me.Control = master.Control
    End Sub
    

    Which is kind of nice in that you can reference grandparent forms like master.master.control but could cause some major problems if a control on a parent form disappears in future.

So yeah, is there a recommended method?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
wackozacko
  • 682
  • 1
  • 8
  • 18
  • since all of your data saved somewhere, why don't you prepare query and pull/push data between forms? i mean instead of reading in one form sending it to a second form to edit and retrieve it back, why don't you tell the second form where to find the data to edit and edit it? after second form closes just refresh the first form to get the changes? – Krish Nov 04 '14 at 17:22
  • Unfortunately, I'm not working on this project any more, but the reason we weren't doing that was that the data in question was intermediate data that we were calculating other data from, not data we were actually storing. – wackozacko Nov 06 '14 at 16:42

2 Answers2

-1

This is such a critical question for developing a professional, maintainable Access application.

First, some comments on your list of methods:

  • I agree with your assessment of the drawbacks of methods #1 and #2
  • #3 and #4 share the same problem: a global table is just as global as a global variable. You can't fundamentally limit access to either.
  • #5 will inevitably get you into problems. The extra long-term reference to the form will, in certain circumstances, keep it from closing.

I would propose moving all of your business logic into separate modules, broken out by form. This lets you avoid the global issues with #3 and #4, because modules can have private module-level variables and functions.

For instance, a form called frmFoo would get a matching module named modFrmFoo. The module would contain all of the public methods for using frmFoo:

Here is a simplified modFrmFoo:

Private mvID As Variant

' read-only (no Let)
Public Property Get frmFoo_ID() As Variant
    frmFoo_ID = mvID
End Property

Public Sub frmFoo_Show(ID As Variant, Name As Variant)
    ' note use of temporary object reference
    Dim rFoo As Form_frmFoo
    mvID = ID
    DoCmd.Open acForm, "frmFoo"
    Set rFoo = Forms("frmFoo")
    rFoo.ShowName Name
End Sub

The frmFoo form module needs one public method:

' only frmFoo should refer to its internal controls and code
Public Sub ShowName(Name As Variant)
    lblName.Caption = Name
End Sub

And, the query for frmFoo:

SELECT ID, [other stuff] FROM tblFoo WHERE tblFoo.ID = frmFoo_ID() 

The query syncs the results to the ID value returned by the property frmFoo_ID().

Showing frmFoo from another form is easy:

' frmBar
Private Sub cmdShowFoo_Click() 
    frmFoo_Show 123, "your name"
End Sub

Advantages of this method:

  1. Defines a public interface for all public operations on a form. External forms call the module, never the form directly.
  2. Keeps all form's private business private -- form controls can be renamed or deleted without upsetting all other forms that depend on it.
  3. Defines in one call what is needed for each method to work -- for instance, frmFoo_Show() requires ID and Name.

This is along the lines of how I would do a new application from scratch.

Whether it is practical for retrofitting an old, tangled app is your call.

kismert
  • 1,662
  • 1
  • 13
  • 19
  • @wackozacko: if you get some time, please read my answer. I would like your feedback! – kismert Apr 23 '15 at 17:25
  • I'm not keen on this approach. I can't see the benefits. Form modules can have private procedures and variables. The form module is actually part of the form class and in the event that your code opens the same form more than once, it will be instantiated multiple times (using a second module will break the ability to do this). You can still add procedures to the form module for external programs to use (a "rule" to no refer to form controls directly would be needed - I agree), but this would be more simple than a second module. – HarveyFrench Jun 07 '15 at 07:12
  • Perhaps name these procedures "aa_myProcname" and they'll be at the top of any list.... Please also see my answer regarding the use of open args (which IMO is a rubbish way of doing things, and form modules proceures like those mentioned above shoudl be used instead) – HarveyFrench Jun 07 '15 at 07:12
  • @HarveyFrench: 2 Points: (1) Instantiating multiple Access Form objects is way more trouble than it is worth. If you _must_ do that, move to a real programming environment. 2. Unlike Report objects, where you can modify the underlying SQL/Query on the Report_Open event, the underlying SQL/Query for Access Forms is already opened by the time the the Form_Open event fires. This results in a performance-sucking requery if you modify your query on Form_Open. The simplest way around this is to have a companion module that modifies the form's query before it is opened, so the query only runs once. – kismert Jun 08 '15 at 15:22
  • @kismert. I set the rowsource of the form using Form_Open all the time. I simply ensure the form is saved either with an empty value in the rowsouce or with the criteria "WHERE 1=2" added. Take your point about access being a bit crappy! Sometimes multiple instances are great, not often needed admittedly. I'm really trying to like the idea of having an extra module per form, but so far I'm not sold on it. (It would get around only being able to having one window open on each module at a time ;-) – HarveyFrench Jun 09 '15 at 16:40
-1

This might be of help:

You can open forms and control them Without using Open Args

You do this by opening another form as if it were a class

Create a module called GlobalVars which includes this line:

Public Form_MyFormNameToOpen as Form_MyFormNameToOpen

(The form you open will remain open until this variable "dies", so make it global to keep it alive)

MyFormNameToOpen is the name of the form you want to open and has Form_ in front of it in some of my example code. This tells access to get a "form class" ie one of the forms you created.

Then in code that you want to open a form use:

' Opens the form using it as it were a class
Set GlobalVars.Form_MyFormNameToOpen = New Form_MyFormNameToOpen

' The modify the form you have just opened however you want to.
With Form_MyFormNameToOpen
    .Visible = True

    ' This relies ont he control names never changing
    .Controls("ProviderID") = 10
    .Controls("ProviderFileID") = 11


    ' it's better to use properties created on the called form
    ' eg
    .MyLetProviderID = 10
    .MyProviderFileID = 11
End With

I hope this helps.

I find using open args a real pain except for the most basic of things. Using forms like this make life much easier.

(perhaps you can use a similar technique for reports, subforms etc...)

HarveyFrench
  • 4,440
  • 4
  • 20
  • 36
  • Problems with this code: (1) You are maintaining a global variable instance to a form, which is bad programming practice. Any code anywhere could close this form, and you can't debug this with a breakpoint on the global itself. Why not concentrate control of this form to a single module, given that you are using external code anyway? (2) You are binding this code to internal form details, but in a late-bound way. Change "ProviderID" to "foo_ProviderID" in your _form_. The code above will compile, but it will fail at _runtime_. This is why form details should be private to the form. – kismert Jun 08 '15 at 15:35
  • Ok points taken. BUT. (1A) Instead of a global variable I could use a "form factory" class to manage forms I've opened, this would allow more control (and breakpoints if desired). (1B) if you use a module it will make opening the same form more than once impossible - I think and where is the advantage of using an external module as you can do the form's own module (which behaves as part of the form class). – HarveyFrench Jun 09 '15 at 16:33
  • (2) When I use this technique I add properties to the form's module so that the calling code can use these properties to change internal form controls etc. I wouldn't use .Controls("ProviderID") = 10 but use .MyLetProviderID = 10. I'm all ears. As I really want to work out the best /right / alternative ways of doing this. I'm using the above technique and have some forms that open many times. It seems to work well. There's a few things I've not understood yet, like the form's module not being able to access objects on the calling form through a pointer to it, but otherwise it's good. – HarveyFrench Jun 09 '15 at 16:37
  • Using a central form factory class would be better than globals but takes more time to set up - and it's not always needed. – HarveyFrench Jun 09 '15 at 16:37