As a programmer, your Spidey-senses should start tingling any time you find yourself copying and pasting.
It doesn't matter whether the thing you are copying and pasting is code, form or report controls, or entire form or report objects. There is no more literal way to break the adage, "Don't repeat yourself," than by making exact duplicates of existing stuff.
Copying and pasting is not always wrong.
In fact, used strategically, I actually recommend it in some cases. But those instances are the exception, not the rule. Your default position whenever you are tempted to copy and paste is to assume that it's a bad decision unless you can prove otherwise.
Creating multiple copies of a form in Access is almost universally a bad practice.
Multiple Copies vs. Multiple Instances
Let's start by clarifying some terminology:
- Multiple Copies of a Form: more than one item in the "Forms" section of the Access navigation pane
- Multiple Instances of a Single Form: more than one open form window but only a single item in the "Forms" section of the Access navigation pane
Why Not Just Use Multiple Copies of a Form?
This issue came up in the context of my ShowForm() function series.
In Part Two of that series, I added code to the function to handle the creation of multiple form instances. Here's an excerpt from that article:
To make [multiple form instances] work, there are a few rules you need to be aware of:
- The form must have a code-behind module (i.e., no "lightweight" forms)
- Each new instance of the form must be assigned to a different object variable
- The object variables must remain "in scope" (when the object variable is released, the form is closed)
Each of the above restrictions can be easily avoided by simply creating multiple copies of a single form. And that led to a great question from a reader on LinkedIn:
Why would I go through all that trouble to instantiate a form multiple times when I can just rename a cloned form and open it?
Let Me Count the Ways
In my response, I provided three reasons that using form instances is a better practice than using form copies. I added a fourth to the list below:
- Easier ongoing maintenance
- More scalable
- Easier to track which form object to open
- Long-term investment
Easier Ongoing Maintenance
What happens when–after making several copies of a form–you need to make design changes to the original form?
Those changes have to be applied equally to all forms, otherwise you'll have inconsistencies among them. As Frank points out in a follow-up comment on LinkedIn, this situation is more manageable than you might think at first since you don't actually need to make the same change on each copy of the form.
I dont feel maintenance would be an issue. If mods are made to the original form you clone it again and overwrite the previous versions.
In other words, the change process for multiple form copies looks like this:
- Make changes to the first form
- Delete the other form copies
- Make new copies of the first form
- Rename the copies to match the original naming convention
That's not terrible, but it's definitely more steps than the multiple-instances-of-a-single-form alternative:
- Make changes to the form
How many records will your user want to be able to compare at one time?
If comparing two records at once is useful, it stands to reason that the user might also want to be able to have three records open at once. Or four records. Or five records. How many is too many? What's the right number?
The multiple copies approach requires that you have as many form objects as the user might want to have open at one time.
Each of these objects takes up disk space, bloating the size of the front-end. Each object is another three steps (delete, clone, rename) when making changes to the base object. Each object is another item to clutter up your navigation pane.
The multiple instances approach avoids these issues entirely. With this approach, one form object is all you need to allow your user to open as many records as they want at one time (up to the memory limits of Access itself).
Choosing Which Object to Open
Using the screen shot above, let's assume we have four form objects:
Let's further assume that we have a continuous lookup form with information about each record in the MSysObjects system table. Each line includes a button that says [Show Details]. When the user clicks on the button, we open one of the four forms listed above.
Which one of the four forms listed above should we open? The first time the user clicks [Show Details] we could open frmMSysObjects_1. Easy enough.
But how do we know if it's the first time the user is clicking the [Show Details] form? Maybe we could keep a counter in a static variable. When the user clicks [Show Details], we increment the counter (0 -> 1) then use the counter to build the form name.
The second time the user clicks [Show Details], we increment the counter again (1 -> 2). We open frmMSysObjects_2. The third and fourth time through we open forms 3 and 4. So far so good.
The fifth time through we have a decision to make. We could use the modulo operator and reset the counter (4 -> 1). We would then close form 1 if it was open and re-open it with the new record information.
But wait! What if form 1 was still open but the user had closed form 3? Wouldn't it be better to leave form 1 open and open form 3? So now we have to loop through the forms collection and check to see the first available form that's not currently open and open that one.
Now that we've got that sorted out, what do we do when all four forms are open? Do we close and reopen form 1? What if that's the most recently opened form? Wouldn't it be better to close and reopen the form that's been open the longest? From a UX perspective the answer is yes. OK, now we need a collection to keep track of the order the forms were opened in.
Or we could use multiple instances of a single form and let the user compare as many records at a time as they want.
Anyway, my point is not to try to solve this problem. My point is that seemingly simple solutions have a way of sprouting complexity where you least expect it.
The devil is in the details, and all that.
Finally, there is great value in encapsulating complexity to produce simplified calling code.
The original comment on LinkedIn focused on the upfront time needed to support multiple form instances, "Why would I go through all that trouble..." The short answer is, "Because you only have to 'go through all that trouble' one time."
All the complexity of maintaining the collection of form objects to support multiple form instances is encapsulated in the
ShowForm function. Here is the calling code needed to generate the four form instances in the screenshot above:
ShowForm "frmMSysObjects", "Name='Reports'" ShowForm "frmMSysObjects", "Name='Forms'" ShowForm "frmMSysObjects", "Name='Modules'" ShowForm "frmMSysObjects", "Name='Tables'"
ShowForm() function itself will live in your code library. Unless you are adding features to the function, you never need to touch its code again. You will import it into all of your future projects. Instead of calling
DoCmd.OpenForm, you'll call
ShowForm (as shown above).
If you look closely, you'll notice that the above calling code is even cleaner than the equivalent
DoCmd.OpenForm calling code.
And that brings me all the way back to one of my favorite articles, Eliminating Friction.
If you want to do things The Right Way™, then invest time upfront to develop a low-friction approach that you can use forever.
Make it so The Right Way is also The Easy Way.