Solved Trouble opening multiple instances of a form

HillTJ

To train a dog, first know more than the dog..
Local time
Today, 00:20
Joined
Apr 1, 2019
Messages
731
Hi, I'm working on my CRM project. I have a problem where the wrong fields are hidden/displayed upon initial opening of the next form after the initial form is opened. Difficult to explain, but open frmMain, open either of the top two options & the correct controls are displayed (that is Nav options shown or hidden). Both forms use OpenArgs to pass the parameter that displays/hides the header options & I set a TempVar as a flag to determine whether the form is effectively "drilled" down from the previous form or not. Select 'Amend an existing persons record' and open it to display the edit form. All good.

Now, select the relationships tab for the first record, then select the hyperlink to open the related form. Notice that all controls are displayed. Then on that form select the relationships tab and select a hyperlink and see that this form opens as desired. This is what I want all 'drilled down' forms to look like.

It seems that the first form opened with the hyperlink is not 'clearing' the Openarg....I think..

Appreciate it if anyone has the time to decipher my issue.

Cheers
 

Attachments

"It seems that the first form opened with the hyperlink is not 'clearing' the Openarg....I think."

Think I followed your steps to open forms. Exactly what should I be seeing that is issue?

If you want to manage multiple instances of a form, review http://allenbrowne.com/ser-35.html
 
@June7 , i concluded same but could not figure why. All forms opened with the hyperlink should not display the nav buttons or record counter (after all only a single record is opened), also should display a different caption to those opened by either menu option on frmMain. I'd expected that each form opened by the hyperlink would clear the Openargs so the onload code behind the form would detect no openargs value and set the correct controls visible/invisible. If you open an additional form via the hyperlink , that is 'drill down twice' it displays correctly.

Hope i made sense. Appreciate any help you can provide.
 
You can find a working example of opening multiple instances of a form in the Northwind Developers Edition.

It supports multiple instances of the Orders form and the Purchase Orders form. You are free to use the code in this template for your own projects.
 
So issue is that first hyperlink opened form still has OpenArgs set by form opened from button? Subsequent hyperlink opened forms do not.

At first, options appear to be:

1. Null OpenArgs value when form is opened by hyperlink

2. use some other way to pass value to form (global variable, TempVars, reference textbox on form)

Since apparently OpenArgs can only be set by OpenForm (or OpenReport) command argument, seems option 1 is not possible with your code and that leaves option 2.

I've never tried to manage multiple instances of form.
 
Last edited:
So issue is that first hyperlink opened form still has OpenArgs set by form opened from button? Subsequent hyperlink opened forms do not.

At first, options appear to be:

1. Null OpenArgs value when form is opened by hyperlink

2. use some other way to pass value to form (global variable, TempVars, reference textbox on form)

Since apparently OpenArgs can only be set by OpenForm (or OpenReport) command argument, seems option 1 is not possible with your code and that leaves option 2.

I've never tried to manage multiple instances of form.
You can also check out the example in Northwind Dev ;) . https://support.microsoft.com/en-us...emplates-e14f25e4-78b6-41de-8278-1afcfc91a9cb
 
To keep a non-default instance open, you need one valid reference, and the simplest approach is to put that reference in the form itself. Consider this code...
Code:
Private me_ As Access.Form

Private Sub Form_Load()
    Set me_ = Me
End Sub
That is enough for the form to stay open. To open that instance, all we need to do is...
Code:
Private Sub Command0_Click()
    With New Form_fNonDefault
        .Visible = True
    End With
End Sub
In this code we never declare a variable of type Form_fNonDefault, and we never add an instance to any collection. Here's a Db that demonstrates this technique.
 

Attachments

@June7 , you hit the nail on the head in post #5. Exactly my observations. I don't understand why the openargs value is not nulled by opening the second instance, but is nulled for subsequent instances. Will have a go using tempvars instead of openargs. That way at least i can null the tempvars value. It just seemed neat the way i'd imagined the code to work!!

Will have a look at the other methods recommended too. My goal is to identify the selected method of opening the form be it, 'adddata', 'amenddata' or 'new instance' (triggered by the hyperlink) then hide/unhide irrelevant controls on that form

Will have a look at Northwind & the example posted.

Cheers
 
There is no need to open the form ACDIALOG, so just get rid of all open args. Only need to open a form ACDIALOG if you need code execution to stop in the calling form. No need otherwise.

Create a property
Code:
Private m_FormType As ftFormType
Public Enum ftFormType
  ftAddNew = 0
  ftAmend = 1
  ftDrillDown = 2
End Enum
Public Property Get FormType() As ftFormType
    FormType = m_FormType
End Property

Public Property Let FormType(ByVal eNewValue As ftFormType)
    m_FormType = eNewValue
End Property

Now simply set the property.

Code:
Private Sub BttnAmendPerson_Click()
    On Error GoTo ErrorHandler:

        DoCmd.OpenForm "frmPersons", acNormal, , , acFormEdit
        Forms!frmPersons.FormType = ftAmend
        TempVars!Drilldown = "No"

exitError:
    On Error Resume Next
    Exit Sub

ErrorHandler:
    Call DisplayErrorMessage(Err.Number, "frmMain bttnAmendPerson")

        Resume exitError
End Sub
Private Sub BttnListManagement_Click()

    DoCmd.OpenForm "frmListMaintenance", acNormal, "", "", , acNormal

End Sub

Private Sub bttnNewPerson_Click() ' add a new person
On Error GoTo ErrorHandler:

        DoCmd.OpenForm "frmPersons", acNormal, , , acFormAdd
        Forms!frmPersons.FormType = ftAddNew
        TempVars!Drilldown = "No"
exitError:
    On Error Resume Next
    Exit Sub

ErrorHandler:
    Call DisplayErrorMessage(Err.Number, "frmMain bttnNewPerson")

        Resume exitError
End Sub

Now change the select case
Code:
  Select Case Me.FormType
  Case ftFormType.ftAddNew
      Me.txtTitle.Caption = "Use This Form To Add A New Person"
        Me.Label45.Visible = False
        Me.bttnSearch.Visible = False
        Me.BttnShowAll.Visible = False
        Me.Label42.Visible = False
      ....
Case ftFormType.ftAmend
      ..
  Case ftFormType.ftDrillDown
    ...
  End Select
End Sub
Code:
Public Function OpenAClient(PartyID As Long, FormName As String)
    'Purpose:    Open an independent instance of a form.
 

    'Open a new instance, show it, and set a caption.
    Dim frm As Form
    Select Case FormName
        Case "frmPersons"
            Set frm = New Form_frmPersons
            frm.FormType = ftDrillDown
        Case "frmOrganisations"
            Set frm = New Form_frmOrganisations
    End Select

Works for me. Now get rid of the TempVars!drilldown.
 
I don't understand why the openargs value is not nulled by opening the second instance, but is nulled for subsequent instances.
This appears to be what is happening, but I cannot understand that either. My guess is that OpenArgs is not like other properties. Access form's unlike other application do not allow you to modify properties prior to opening. So what is special about OpenArgs. My guess is that OpenArgs is a memory location that the form looks at after opening. That is the only way I can think that a new instance can pull that value.
 
I actually modified this to simply configure the Form after instantiating it. You can leave the Enum but get rid of the properties. Call the ConfigureForm method directly after loading the form.
Now get rid of the onload event and call this after loading the form
Code:
Public Sub ConfigureForm(FormType As ftFormType)
Select Case FormType
  Case ftAddNew
    MsgBox "Add New Person Field type"
  Case ftAmend
    MsgBox "Amend field Type"
  Case ftDrillDown
    MsgBox "Drill down field type"
  End Select
  Select Case FormType

  Case ftFormType.ftAddNew
        Me.txtTitle.Caption = "Use This Form To Add A New Person"
        Me.Label45.Visible = False
        Me.bttnSearch.Visible = False
        Me.BttnShowAll.Visible = False
        Me.Label42.Visible = False
        Me.txtSearch.Visible = False
        Me.Box46.Visible = False
        Me.Box56.Visible = False
        Me.Label55.Visible = False
        Me.BttnFirst.Visible = False
        Me.BttnPrevious.Visible = False
        Me.BttnNext.Visible = False
        Me.BttnLast.Visible = False
        Me.BttnNew.Visible = False
  Case ftFormType.ftAmend
        Me.txtTitle.Caption = "Use This Form To Amend An Existing Persons Details"
        Me.Label45.Visible = True
        Me.bttnSearch.Visible = True
        Me.BttnShowAll.Visible = True
        Me.Label42.Visible = True
        Me.txtSearch.Visible = True
        Me.Box46.Visible = True
        Me.Box56.Visible = True
        Me.Label55.Visible = True
        Me.BttnFirst.Visible = True
        Me.BttnPrevious.Visible = True
        Me.BttnNext.Visible = True
        Me.BttnLast.Visible = True
        Me.BttnNew.Visible = True
  Case ftFormType.ftDrillDown
        Me.txtTitle.Caption = "Drill Down To A Related Person"
        Me.Label45.Visible = False
        Me.bttnSearch.Visible = False
        Me.BttnShowAll.Visible = False
        Me.Label42.Visible = False
        Me.txtSearch.Visible = False
        Me.Box46.Visible = False
        Me.Box56.Visible = False
        Me.Label55.Visible = False
        Me.BttnFirst.Visible = False
        Me.BttnPrevious.Visible = False
        Me.BttnNext.Visible = False
        Me.BttnLast.Visible = False
        Me.BttnNew.Visible = False
        Me.txtRecordNo.Visible = False
  End Select
End Sub

Something like
Code:
Dim frm As Form_frmPersons
           DoCmd.OpenForm "frmPersons", acNormal, , , acFormEdit
           Set frm = Forms!frmPersons
          frm.ConfigureForm ftAmend
or
Code:
Case "frmPersons"
      Set frm = New Form_frmPersons
      frm.ConfigureForm ftDrillDown
 
@MajP, will do. I've already had success by deleting all openargs & saving the form description (new, amend or drilldown) as tempvars. Will make a version with your code too. Thanks to all for the effort.
 
OpenArgs is pretty much a work around for when you open something ADIALOG. If you do not open it ACDIALOG it is far easier and direct in my opinion to set the properties or methods from the calling form directly. And you really only need ACDIALOG if you want code execution to stop in the calling form. In my example you can probably get rid of the tempvars.
Sorry forgot to post the code.
 

Attachments

@MajP , thanks a lot. I got a version running too & will have a look at your example. I've never had a need to open multiple instances of a form before. It's a clever/powerful technique but not covered very well. My philosophy is that if i'm not happy with my coding then don't settle for it. It forces me to get out of my comfort zone and learn something new! You mentors are much appreciated.
 
@MajP , I like your code & will adopt it for my other forms, nice & tight (if that's a way to describe code!). My only gripe, is how does an amateur like me learn these tricks? I got a working copy using tempvars, but your technique is so much cleaner. Thanks again
 
OpenArgs is pretty much a work around for when you open something ADIALOG
I would have to disagree with that. :(
I have used Openargs many times for what I believe it was created for, passing in parameters?

I actually use it on my Diabetes DB where I run the same form, but depending on the OpenArgs, it can be one of two sources. Weekly or Monthly. Now I might use acDialog, but that is just because that is the way I want to see them.
Actually looking at it now, not sure why I did use it in these cases. :)
They word just as fine without it.

Code:
Private Sub cmdMonthly_Click()
    DoCmd.OpenForm "frmPeriod", , , , , acDialog, "Monthly"
End Sub

Private Sub cmdWeekly_Click()
    DoCmd.OpenForm "frmPeriod", , , , , acDialog, "Weekly"
End Sub
 
I would have to disagree with that. :(
I have used Openargs many times for what I believe it was created for, passing in parameters?
@Gasman,
Well everything you said supports my point not refutes it. It is only a workaround needed in rare cases. and most of the times not required.
It is limited in capability and IMO using it is convoluted. It is like playing the old telephone game.

1. If the forms are set as Pop Up and Modal (modal optional) they will appear "the way you want to see them". ADCIALOG is Popup and Modal. Only benefit would be if sometimes you want to show a form not pop up and modal and other times you do. I never have this case.
2. In your example there is no reason you have to stop code execution, so no real reason to open ACDIALOG. Stopping code execution is important if for example you want to requery your form after closing the popup.

You have FAR more capability and flexibility by simply opening a form and Setting properties or running form methods directly. Without the cumbersome burden of passing Only a string and then having an if check in the frmPeriod to do something based on a string.

I can do this more directly by setting a property frmPeriod or calling a procedure in frmPeriod. Assume I have a procedure in frmPeriod.

Code:
Public Sub SetFormPeriod (Optional StartDate as Date = Date(), Optional PeriodType as string = "Monthly)
  'code to change period
end sub

Code:
Private Sub cmdWeekly_Click()
    DoCmd.OpenForm "frmPeriod"  'no need for ACDIALOG
   forms!FrmPeriod.SetFormPeriod #1/1/2024#, "Weekly"
End Sub

Way easier to pass 2 parameters and a real date not a string representation.

Or what if you want to pass an object. Cannot do that in openargs

In your frmPeriod maybe you want to pass back a value or values to the form that called it. In the frmPeriod you have a property or simply a class variable. I will demo with the full property but a simple public variable would work

Code:
Private mReturnControl as accessControl
Public Property GetReturnControl() As Access.control
  Set ReturnControl = m_ReturnControl
End Property


Public Property Set ReturnControl(ByVal TheControl As Access.control)
set  m_ReturnContro = TheControl
End Property

Now you can call frmPeriod Set the period type, the start date for the period, and where to return a value.
Code:
Private Sub cmdWeekly_Click()
    DoCmd.OpenForm "frmPeriod"  'no need for ACDIALOG
    forms!FrmPeriod.SetFormPeriod #1/1/2024#, "Weekly"
     set Forms!FrmPeriod.ReturnControl = me.txtDate
End Sub

Do that with openargs and see how convoluted it is.
 
My only gripe, is how does an amateur like me learn these tricks? I got a working copy using tempvars, but your technique is so much cleaner. Thanks again
I think the problem is that if you learn coding in Access VBA, you think that the Access limitations and workarounds are the standard way to do things. If you learned somewhere else, the Access approach would probably seem a little strange. If you are familiar in other development environments this approach s not the "trick" at all, but actually the more standard way.
See my thread above why people choose the extreme limitations and inflexibility of OpenArgs instead of doing it directly, out of familiarity.

Benefits of doing it directly
1. Pass/set mutliple parameters
2. Pass/set objects and real data types
3. Execute a method or multiple method in the called form/report
4. Never have to code in the calling form to handle a passed parameter. (you decouple the two forms). The called form has no idea what set the properties or ran the methods.

If you develop in other languages there is not some application method such as Docmd.Openform, and there is no additional window mode like ACDIALOG or a way to pass things with openargs. In other environments like vb.net or even Excel vba you would open the form in a method very similar to how you do an instance. So you get used to no openargs.
(pseudo code showing flow)

Dim frm as FormSomeForm
frm.setProperty
frm.setOtherProperty
frm.show (frm.open)
frm.Runsomemethod

I have worked in multiple form instances before and understand the differences.
1. You do not have a unique Name property so you cannot use the Forms collection to manage instances by name. Therefore you need to manage in their own collection. They will go out of scope unless placed into a collection or a pointer is module level.
2. You do not have a way to open an instance with Docmd.open form so that takes out the possibility of ACDIALOG and Openargs.
Since I have done this before and understand the impacts of 1,2 I already had a concept in mind to overcome number 2.
 
I think you should only rewden from multiple form instances if those instances are open AT THE SAME TIME. When done one after the other, these are just different designs of one instance each.

A form is a class. You can use any number of instances of a class at the same time. However, instructions like OpenForm from the DoCmd object (replicating the commands from the menu) don't have much to do with object-oriented programming (OOP). If you really have multiple instances, you should say goodbye to OpenForm and OpenArgs and use the classic methods there.
 
Gents, cheers. I will take my time to digest this. I move on.....
 

Users who are viewing this thread

Back
Top Bottom