Results 1 to 11 of 11
  1. #1
    pdanes is offline Competent Performer
    Windows 10 Access 2007
    Join Date
    Sep 2019
    Posts
    213

    Pass information back from called form

    I have frequently seen questions about how to get information back from a form called via the DoCmd.OpenForm command and opened in dialog mode. I have had the same problem myself, and after experimenting with numerous constructs, have hit upon a system that has been working for me quite nicely. The short answer is to create a public variable that the called form can write into, but there is a good bit more to it to create a comprehensive system that is clean, simple to use, and flexible. This is how I have done it.

    Essentially, it is a systematic method of passing back a string, the same as the OpenArgs parameter. OpenArgs is strictly a string, with the added provision that it may be null. My method does not allow nulls, although it could easily be modified to allow that. The variable could be a variant, which would allow all sorts of things, including nulls, or even a user-defined class, for maximum flexibility. However, for my purposes, it is preferable to stick with strings, the same as the OpenArgs parameter – less possibility for confusion, and the ability to set a null or more complex constructs would not really provide any benefit to the way I am using it.

    I use a specific format for these strings, in both the calls to forms, via OpenArgs, and back to the calling form, via my public variables. Those are string variables, called mdl_PassBack, always and everywhere, just like OpenArgs always has the same name. Using a string variable rather than a class variable also means it does not need to be initialized.

    I pass parameters to the called form by constructing a string into the OpenArgs parameter. It always uses the syntax:

    Param1=Value1<vbCrLf>
    Param2=Value2<vbCrLf>
    Param3=Value3<vbCrLf>
    Param4<vbCrLf>
    Param5=Value5<vbCrLf>

    ParamN=ValueN

    much like the classic .INI file format, although I did not add the ability to include comments and blocks, as the .INI does. I only pass a few parameters, so I did not see any point in such complications. I do allow parameters with no value, as in line 4, because sometimes I pass things like 'NewRecord', indicating that the called form should immediately start a new record, and there may not be any values associated with that instruction. Blank lines and spaces are ignored, and the return string from the called form to the calling form is constructed exactly the same way.

    Each called form's Load event contains a small loop as the first item in the Load module, which one by one, picks off each line in OpenArgs and feeds the extracted parameters into a Select Case statement. This extraction is done by a small public sub, named Parse_OAPB, set up to handle exactly this format – single lines, containing either a parameter or a parameter=value pair, delimited by vbCrLf. The last line does not need a trailing vbCrLf, but it can have one – it is ignored. The code for Parse_OAPB is at the end of this post.

    There is a Case clause for each parameter that the form is able to handle, and a Case Else: Stop command as the last item in the Select Case block, as a safety measure if I screw up and pass a parameter it doesn't expect. Once the OpenArgs string is completely dismantled and parameters handled, the Load event goes on – sometimes just ending, sometimes doing additional prep work based on those parameters. It would of course be possible to do the prep work right in the Select Case block, but I prefer to parse the entire OpenArgs string and set environment variables as the parameters are identified, then after the parsing is complete, do the things that the parameters direct. It is slightly more code, but the difference in performance is undetectable, and such separation makes the structure much clearer. Each called form has such code in its Load event, which parses the OpenArgs string and sets up various conditions in the form before giving control to the user. An example of such a Load procedure is here:

    Code:
    Private Sub Form_Load()
    Dim x$, xl$, xr$, Pridat As Boolean, Upravit As Boolean, KodHledat&
    Set gbl_frmClovek = Me
    If Not IsNull(OpenArgs) Then
    ' Loop to dismantle OpenArgs
        x = OpenArgs
        Do Until x = ""
            Parse_OAPB xl, xr, x
            Select Case xl
                Case "Add": Pridat = True
                Case "Edit": Upravit = True: KodHledat = Val(xr)
                Case "CallingForm": lcl_CallingForm = xr: Forms(lcl_CallingForm).mdl_Passback = ""
                Case Else: Stop
            End Select
        Loop
    ' Set up form according to OpenArgs parameters
        Select Case True
            Case Pridat
                With gbl_frmClovekEdit
                    .tglPridat = True
                    .tglPridat_AfterUpdate
                End With
            Case Upravit
                gbl_frmClovekDS.Recordset.FindFirst "KodClovek = " & CStr(KodHledat)
            Case Else: Stop
        End Select
    End If
    End Sub
    The second half of the routine does what the passed parameters dictate. Here it is a Select Case True structure. It could also be an If Then ElseIf Then ElseIf Then… End If series, or serial If Then or If Then End If statements, if the parameters are not mutually exclusive, or anything, really. What any particular called form does after parsing the parameters out of the OpenArgs string is not actually relevant to the structure I have developed, but I included this example just for show.

    I eventually realized that everything I wanted to pass back could also be expressed this way, making life quite simple. To this end, I define public variables: mdl_PassBack$. I define global variables with the prefix gbl_, public module variables with mdl_, and private module variables with lcl_. Variables inside procedures I simply name by whatever they're doing, but this prefix convention serves me well for keeping track of where a variable with wider scope is defined and what could possibly affect it.

    This public mdl_PassBack string variable is defined in every form that wants some information back from a form it calls. One of the parameters I pass to the called form is the name of the calling form, for example:

    Code:
    DoCmd.OpenForm FormName:="MyPopupForm", OpenArgs:="CallingForm=" & Me.Name & vbCrLf & "PublicationAutoID=" & CStr(txtPublicationAutoID)
    would tell MyPopupForm that it should somehow address the record specified by PublicationAutoID, and that it had been called by the form whose name is paired with the CallingForm parameter.

    Code:
    DoCmd.OpenForm FormName:="MyPopupForm", OpenArgs:="CallingForm=" & Me.Name & vbCrLf & "NewPublication"
    would indicate that the called form should immediately start a blank new record, and again, that it had been called by the form whose name is paired with the CallingForm parameter.

    Each called form has a module-wide private variable defined to hold the calling form name:

    Code:
    Private lcl_CallingForm$
    and one of the Case statements in the initial parsing loop will be:

    Code:
    Case "CallingForm": lcl_CallingForm = xr: Forms(lcl_CallingForm).mdl_Passback = ""
    This stores the name of the calling form in the module-wide private variable, and clears out the calling form's mdl_Passback variable, to remove any ballast from possible previous calls. It also has the debugging benefit of immediately crashing the called form if I forget to declare the mdl_Passback variable in the calling form. Before the called form closes, or actually, any time during its life, it will execute the command:



    Code:
    Forms(lcl_CallingForm).mdl_Passback = "…some information…"
    This refers back to the calling form by the name it passed in OpenArgs. Obviously, this does not need to be in only one place. Many of my forms have this action in the form's Close event, but this is not necessary. A button that the user clicks might be to select a specific record, and the code in that button event would load the mdl_Passback variable with the ID of that record, using something like mdl_Passback = "SelectedRecordID=" & CStr(PublikaceAutoID) and immediately close the form. Simply closing the form would leave the mdl_Passback variable blank, and the calling form would know that the user had left the form without doing anything.

    Changes to data I handle using custom events and WithEvents variables, to broadcast to anyone who cares that the contents of a table have changed, but this construct I built to inform about a user's actions or choices while a dialog form is open, not about changes to data. The calling form's public module variable always being named that same name makes remembering how to return information easy. Usually in the Close event (but could be elsewhere), a string is assembled that contains all the information that the called form wants to send back to the caller. The returned information is again a string assembled of one or more lines, using the Parameter=Value or just Parameter syntax, separated by vbCrLf, just like what arrives in OpenArgs. This lets me use the same construct that I use in the Load event procedures to pick apart the returned string - a loop containing calls to the Parse_OAPB routine. There is no requirement that the information string be constructed that way, or that I use that Parse_OAPB routine for both tasks, but sticking to this standard has made my life much simpler than before, when I had an unbelievable mess – each OpenArgs assembly had whatever had occurred to me at the time I wrote it, and every change made for a lot of work in matching the construction in the caller to the handling in the called, parameters sometimes separated by commas, sometimes semicolons, sometimes colons, order of parameters crucial, and no easy way to vary parameters by who the caller is and what the caller wanted, nor any easy way to pass back information to the caller. This new method allows any number of parameters, in any order, all in the same layout, and in both directions. Often the information passed back is trivial, and could easily be handled with less code, but again, this standardization makes things very clear, easy to locate when editing, and allows for very simple expansion of capabilities should the requirements grow more complex. To handle a new parameter, it is one new Case clause, into a Select Case block of code already in place. I have to write the code to deal with the new parameter, but I would have to do that anyway. It is obviously a bit slower, but once again, undetectable by a human. Compared to the delay of a user clicking a control and waiting for a form to open or close, the additional computation time required by this extra code is less than trivial.

    This construct might not be adequate to service more complex communication requirements, but it has worked fine for everything I've needed so far, and I somewhat suspect that more complex requirements would likely arise from an improper isolation of tasks. Proper modularity and encapsulation require minimal passing of information between modules. Needing to pass a lot probably indicates that a restructuring of task allotments is a good idea, or at least worth considering.

    ----------------

    Here is the Parse_OAPB routine. All three parameters must be passed ByRef, since they are all modified here and returned to the caller. It picks off the top line, separates it into left and right, if there is an equal sign, or just left, if there is not. The trailing string gets whatever is left after picking off the top line. When the last line has been parsed, an empty string (NOT a Null) is returned in the Trailing parameter. An empty line is discarded without action, and the next one is picked up. All spaces are simply discarded. Since it does not interact with the user, there is no error handling – it is up to me to properly test everything I pass it.

    In the Load events of called forms, the OpenArgs information must first be moved to a string variable, since OpenArgs is a read-only parameter - Parse_OAPB would not be able to modify it. When dismantling the information brought back in mdl_PassBack, I can use that variable directly as the third parameter in the calls to Parse_OAPB, since it is a user-defined variable. It also has the handy side effect of clearing out that variable, so that it is ready for the next such call. The called form's Load routine clears it anyway, but this ensures that information does not linger where it is no longer needed, possibly triggering something unwanted.

    Code:
    Public Sub Parse_OAPB(ByRef OAL$, ByRef OAR$, ByRef OAT$)
    ' Parse (O)pen(A)rgs(P)ass(B)ack
    ' OpenArgs Left, OpenArgs Right, OpenArgs Trailing
    Dim i&
    OAT = Replace$(OAT, " ", "")
    Do
        i = InStr(OAT, vbCrLf)
        If i = 0 Then
            OAL = OAT: OAT = ""
        Else
            OAL = Left$(OAT, i - 1): OAT = Mid$(OAT, i + 2)
        End If
    Loop Until (OAL <> "") Or (OAT = "")
    i = InStr(OAL, "=")
    If i = 0 Then
        OAR = ""
    Else
        OAR = Mid$(OAL, i + 1): OAL = Left$(OAL, i - 1)
    End If
    End Sub

  2. #2
    moke123's Avatar
    moke123 is online now Me.Dirty=True
    Windows 11 Office 365
    Join Date
    Oct 2012
    Location
    Ma.
    Posts
    1,858
    I tend to use withevents to pass info back from a called form.

    In the declarations of the calling form I add a form variable

    Code:
    Dim WithEvents MyForm as access.form
    Then in the command to open the new form

    Code:
    DoCmd.OpenForm "SomeForm"
    
    Set MyForm = Forms("SomeForm")
    
    'you can then manipulate the form if needed
    
    MyForm.SomeField.defaultvalue = "X"
    
    'Set an event to use
    MyForm.OnClose = "[Event Procedure]"
    Then in the calling form add a custom event

    Code:
    Private Sub MyForm_Close()
    
    Me.SomeField = MyForm.Somefield   'grab the value from the form variable.
    
    set MyForm = nothing
    
    end sub
    It is not much different than using a custom class with form instances.
    If this helped, please click the star * at the bottom left and add to my reputation- Thanks

  3. #3
    pdanes is offline Competent Performer
    Windows 10 Office 365
    Join Date
    Sep 2019
    Posts
    213
    Quote Originally Posted by moke123 View Post
    I tend to use withevents to pass info back from a called form.

    In the declarations of the calling form I add a form variable

    Code:
    Dim WithEvents MyForm as access.form
    Then in the command to open the new form

    Code:
    DoCmd.OpenForm "SomeForm"
    
    Set MyForm = Forms("SomeForm")
    
    'you can then manipulate the form if needed
    
    MyForm.SomeField.defaultvalue = "X"
    
    'Set an event to use
    MyForm.OnClose = "[Event Procedure]"
    Then in the calling form add a custom event

    Code:
    Private Sub MyForm_Close()
    
    Me.SomeField = MyForm.Somefield   'grab the value from the form variable.
    
    set MyForm = nothing
    
    end sub
    It is not much different than using a custom class with form instances.
    Well, that would also work, but then you could easily have unwanted code running in the calling form at possibly inappropriate moments. Almost always, I want the calling form to do something AFTER the called form is closed, not while it's still open and possibly interacting with the user.

    Again, I use custom events to broadcast to other parts of the application that data in a table has changed, so that comboboxes can be refreshed and similar data-centric objects can stay up to date on current events. This system that I describe here is specifically for a called form to pass other sorts of information back to its caller in a structured and orderly fashion, after it has finished its own tasks.

    Neither method I think is necessarily more correct, but I find it suits me better to keep that distinction - events strictly for data changes, public variables for other information.

    One point, though - it seems to me the method you illustrate here will not work if the called form is open in Dialog mode, which is what I use almost exclusively. All the code you have would run AFTER the called form closes, which renders it moot. Is there some reason you do not use Dialog mode?

  4. #4
    CJ_London is offline VIP
    Windows 10 Access 2010 32bit
    Join Date
    Mar 2015
    Posts
    11,840
    I use a function to open the dialog form, then closes it

    The dialog form has a sub that when called sets the public variable called result and the form visible to false - this returns control back to the calling routine which can then grab the results before closing it

    I use it for my version of the the inputBox and msgBox, plus I also have an 'optionBox' and 'multiOptionBox'. No reason they could not be extended to take alternative values

    The calling function is (for the msgbox)

    Code:
    Function myMsgBox(Optional Cat = "", Optional target As String = "", Optional adjX As Long = 0, Optional adjY As Long = 0) As Long
    'OArgs is in the format title;message;buttons - e.g. "Warning;Don't do anything stupid;vbExclamation+vbOKCancel", adjX and adjY are to adjust the opening position of the option form from the cursor position
        
        DoCmd.OpenForm "frmMsgBox", , , , , acDialog, adjX & ";" & adjY & ";" & Cat & ";" & target
        myMsgBox = Forms("frmMsgBox").result
        DoCmd.Close acForm, "frmMsgBox", acSaveNo
       
    End Function
    the sub in the form is

    Code:
    Private Sub passResult(r As String)
    'called when a user clicks a button
        Select Case r 'based on the vb numbering returned by msgbox. If you wanted options such as agree/disagree/don't know, make up your own numbers
        
            Case "Yes"
                result = 6
            
            Case "No"
                result = 7
            
            Case "OK"
                result = 1
            
            Case "Retry"
                result = 4
            
            Case "Cancel"
                result = 2
        
        End Select
            
        Me.Visible = False
        
    End Sub
    The multi option returns an array rather than a single value

  5. #5
    moke123's Avatar
    moke123 is online now Me.Dirty=True
    Windows 11 Office 365
    Join Date
    Oct 2012
    Location
    Ma.
    Posts
    1,858
    Quote Originally Posted by pdanes View Post
    Well, that would also work, but then you could easily have unwanted code running in the calling form at possibly inappropriate moments. Almost always, I want the calling form to do something AFTER the called form is closed, not while it's still open and possibly interacting with the user.

    Again, I use custom events to broadcast to other parts of the application that data in a table has changed, so that comboboxes can be refreshed and similar data-centric objects can stay up to date on current events. This system that I describe here is specifically for a called form to pass other sorts of information back to its caller in a structured and orderly fashion, after it has finished its own tasks.

    Neither method I think is necessarily more correct, but I find it suits me better to keep that distinction - events strictly for data changes, public variables for other information.

    One point, though - it seems to me the method you illustrate here will not work if the called form is open in Dialog mode, which is what I use almost exclusively. All the code you have would run AFTER the called form closes, which renders it moot. Is there some reason you do not use Dialog mode?
    I use modal rather than dialog as I want code to keep running. I like to tightly control what's happening. Very often I'll use classes exclusively with 0 code in the called form or form instance, all handled by the class.

    Different strokes for different folks.
    If this helped, please click the star * at the bottom left and add to my reputation- Thanks

  6. #6
    moke123's Avatar
    moke123 is online now Me.Dirty=True
    Windows 11 Office 365
    Join Date
    Oct 2012
    Location
    Ma.
    Posts
    1,858
    oops, double posted.
    If this helped, please click the star * at the bottom left and add to my reputation- Thanks

  7. #7
    pdanes is offline Competent Performer
    Windows 10 Office 365
    Join Date
    Sep 2019
    Posts
    213
    Quote Originally Posted by moke123 View Post
    I use modal rather than dialog as I want code to keep running. I like to tightly control what's happening. Very often I'll use classes exclusively with 0 code in the called form or form instance, all handled by the class.

    Different strokes for different folks.
    So you have the calling form react to events in the called form, and the called form empty of code? Why? That seems to me an unnecessary level of extra complexity, and makes your calling from bloated with code that 'should' be elsewhere. What benefit do you get from doing it that way?

    And what if the called form can be called from different places? I have that situation fairly often. How would the called form act if it can have multiple callers. Do you have to duplicate the code that would normally be in the called form into every form that can potentially call it? And what if you use recursive forms, when a form opens another instance of itself? I don't do that often, but it is an extremely useful trick in some weird situations.

    I must be missing something, because what you're doing looks like an awful lot of extra work and extra code for no gain that I can see. Can you explain a bit, please? I'm really curious.

  8. #8
    moke123's Avatar
    moke123 is online now Me.Dirty=True
    Windows 11 Office 365
    Join Date
    Oct 2012
    Location
    Ma.
    Posts
    1,858
    So you have the calling form react to events in the called form, and the called form empty of code? Why? That seems to me an unnecessary level of extra complexity, and makes your calling from bloated with code that 'should' be elsewhere. What benefit do you get from doing it that way?
    Your confusing a few things. I use a lot of classes. A forms module is also a class module, unlike a standard module. Therefore you can do the same things you can do in a class module in a forms module. When I'm using code like what I posted above it is usually very simple basic stuff. The called form is bound, has code in it, just a normal form. Using the form variable allows me to easily manipulate the form to do various things easily, like set default values, and get return values and such. No need to pass openargs or concatenate anything and then parse it out. It's just pretty straight forward and easy with very little code. Your forms can still be called anywhere and function as designed without setting the form variable. The form variable is a separate object

    When I use classes I'll often use unbound forms and keep all the variables within the class. Generally these classes will be portable and can be dropped into any database and just work. For instance I have several date picker classes. No code in the date picker form. Everything is handled in the class. Another class i use is for adding people and businesses and to insure that I am not adding any duplicates. In that class I use several forms to gather different pieces of information. Those forms are re-used and manipulated depending on the type of entity I'm entering. The class handles all the validation, calculations, and events. Of course they are more complex than normal form modules but the beauty is that you only need to write them once and can use them anywhere.

    Well, that would also work, but then you could easily have unwanted code running in the calling form at possibly inappropriate moments. Almost always, I want the calling form to do something AFTER the called form is closed, not while it's still open and possibly interacting with the user.
    There's no unwanted code running anywhere as you're the one setting the events. If you want the code to run when the called form is closed you create your own close event "private sub MyForm_Close()" If you want the code to run in the after update event of a control on the called form, you set a variable to that control and create that event "Private Sub MyControl_AfterUpdate()" You have complete control over the events. Also note that the events are Private in scope so they cannot accidentally be run from another form as opposed to public subs.

    The other benefit of the above method is readability. Very easy to understand what the code is doing and what is being returned.

    Code:
    Private Sub MyForm_Close()
    
         Me.PersonFK = MyForm.PersonID
    
         Me.DateOfBirth = MyForm.DateOfBirth
    
         Me.Caption = MyForm.FirstName & " " & MyForm.LastName
    
    End Sub
    If this helped, please click the star * at the bottom left and add to my reputation- Thanks

  9. #9
    pdanes is offline Competent Performer
    Windows 10 Office 365
    Join Date
    Sep 2019
    Posts
    213
    Interesting approach, but it still seems overly complicated to me. Any time I see that one of my forms needs more than two or three parameters, it makes me think I probably don't have tasks divided and allocated properly. And having one form reach into the guts of another just seems wrong. I get the rationale that a form is just another type of class, but it's not exactly the same – an ordinary class does not interact with the user, while a form does.

    I also get the bit about writing a class only once. I try to do that when I can, but I don't have all that much stuff that repeats – almost everything I build is one-off, and lots of it has some pretty strange requirements.

    But it clearly works for you, and I would be the last one to tell someone they 'should' be doing things some other way, when they have a system that they're satisfied with. Lots of different ways to do things, and it's always cool to see how someone else has done it. Thank you for sharing your methods.

  10. #10
    moke123's Avatar
    moke123 is online now Me.Dirty=True
    Windows 11 Office 365
    Join Date
    Oct 2012
    Location
    Ma.
    Posts
    1,858
    Interesting approach, but it still seems overly complicated to me.
    It really isn't. Took me a long time to wrap my head around it, still learning, but it really opens up a lot of different paths.
    If this helped, please click the star * at the bottom left and add to my reputation- Thanks

  11. #11
    pdanes is offline Competent Performer
    Windows 10 Office 365
    Join Date
    Sep 2019
    Posts
    213
    Quote Originally Posted by moke123 View Post
    It really isn't. Took me a long time to wrap my head around it, still learning, but it really opens up a lot of different paths.
    Well, maybe. It took me quite a while to get the hang of using custom events, but now that I've finally figured it out, I wonder how I ever lived without them. Maybe this is similar. I'll give it a try and see how I fare. Thank you again.

Please reply to this thread with any new information or opinions.

Similar Threads

  1. Replies: 1
    Last Post: 06-03-2016, 08:23 PM
  2. Replies: 5
    Last Post: 04-27-2015, 02:40 PM
  3. Replies: 7
    Last Post: 03-11-2015, 12:48 PM
  4. Replies: 4
    Last Post: 05-21-2012, 08:21 AM
  5. Replies: 0
    Last Post: 05-04-2010, 06:39 AM

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Other Forums: Microsoft Office Forums