Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Change a Value / Call a Method in Form2 From within Form1?

Forms & Screen

Change a Value / Call a Method in Form2 From within Form1?

by  wgcs  Posted    (Edited  )
This question seems simple, and it really is, but it can be answered in so many different ways that it is not always easy to answer.

It is often asked in various ways, too, such as:
How can I change a value/property on Form2 from Form1?
How can I Pass a Parameter from Form1 to Form2?
How can I execute Code in Form2 from Form1?
etc...


This question is so fundamental that it has been addressed in several Design Patterns, such as are listed at:

http://fox.wikis.com/wc.dll?Wiki~VFPDesignPatternCatalog

Particularly relevant are:
CommandPattern
MediatorPattern
ObserverPattern
And others....


In VFP, this problem can then, clearly be solved in a number of ways.

So, tell me the simplest way! you say, But The simplest way is not always the right way despite Occam's razor.

Okay, maybe occam's razor has nothing to do with it, but if you choose the simplest way now, you may be severely limiting yourself in the future, and you may end up rewriting (not just refactoring but rearchitecting) major parts of your application down the road.

So, Here are the methods that I'll consider below:

1) Passing a parameter to the called form
2) Getting one or more values back from the called form
3) Passing form references around
4) Global References to Forms
5) Using the Automatic _SCREEN.FORMS array
6) (My Favorite) Internal Application Messaging
7) (New in VFP8) Binding Events!


In all examples here, I will refer to the form that originates the value or needs to call a method as "Form1" and the form that gets the value (or that has one of its methods called) as "Form2".

This "occurance" has to happen when the command button "cmdDoIt" is clicked in Form1.

I hope this will keep things clear!

----
1) Passing a parameter to the called form
The simplest way to give a value to another form is to just provide the value as a parameter. This is useful mainly when Form1 launches Form2. Form2 is Usually then Modal, but doesn't have to be.

Code:
Inside Form1.cmdDoIt.Click:
DO FORM Form2 WITH "any value you want", "Any other value you want"

Inside Form2.Init:
LPARAMETERS pcParam1, pcParam2
* Here, we can do whatever we need to
*  with the parameters we were given, such as:
THISFORM.Caption = pcParam1
* It's usually good practice to make your code
*  safe from mistaken calls... better to have an
*  incorrect label than a crash! 
IF VARTYPE(pcParam2)='C'
  THISFORM.lblWelcomeMessage.Caption = pcParam2
ENDIF

----
2) Getting one or more values back from the called form
This isn't very difficult at all; however, the form must be modal, or else there is no defined point in time that you will get the value back, because execution continues in Form1 immediately after the "DO FORM" command without waiting for the non-modal form to close.

I include the "WITH" clause here just to illustrate that you can both pass a parameter to a form and get one back at the same time.

Code:
Inside Form1.cmdDoIt.Click:
LOCAL lcResult
DO FORM Form2 WITH "any parameter" TO lcResult

Inside Form2.Init:
LPARAMETERS pcParam1
IF VARTYPE(pcParam1)='C'
  THISFORM.Caption = pcParam1
ENDIF

Inside Form2.Unload:
LOCAL lcResult
* Calculate what value you need to return, then return it:
IF THISFORM.I_Am_Happy
  RETURN .T.
ELSE
  lcResult = "This is a bad value: "+TRANSFORM(THISFORM.I_Am_Happy)
  RETURN .T.
ENDIF
Note:
* If you neglect to have a RETURN statement, you will by default return .T.
* There is no requirement to return any particular data type, but the code receiving the result must know what to expect, or test!

----
3) Passing form references around
Before starting to explain this one, I must say I do not recommend doing this! Having form references here and there can make cleaning up all the object references very difficult and can lead to forms not going away when you RELEASE them. That being said, this can still be very useful at times:

Code:
Inside Form1.cmdDoIt.Click:
LOCAL loForm2
DO FORM Form2 NAME loForm2 WITH THISFORM
loForm2.Caption = "I can change whatever in Form2 I want!"


Inside Form2.Init:
LPARAMETERS poForm1
THISFORM.lblWelcomeMessage.Caption = "I was called by "+poForm1.Caption
*If you create a custom property in Form2 called "FormThatCreatedMe" you can:
THISFORM.FormThatCreatedMe = poForm1
* HOWEVER, Form1 will not be able to release until this reference is Cleared.
* This is OK if Form2 is Modal, because it will be sure to clear before returning
*   to Form1.cmdDoIt.Click; But if Form2 is ModeLESS, then this creates a Mess!
* BUT, Throughout the rest of Form2's methods, Form1 can be referred to 
*   as THISFORM.FormThatCreatedMe.{any property or method on form1}

----
4) Global References to Forms
Code:
In Main.PRG:

* This is assuming Form1 and Form2 are NON MODAL
DO FORM Form1 NAME goForm1
DO FORM Form2 NAME goForm2

In ANY METHOD of Form1, such as Form1.cmdDoIt.Click:
goForm2.Caption = "Ha Ha, I changed your Caption!"
goForm2.CustomMethod && Run a custom method of Form2

In ANY METHOD of Form2, such as Form2.cmdDoIt.Click:
goForm1.Caption = "I can change yours, too!"
goForm1.Release && Close Form1

----
5) Using the Automatic _SCREEN.FORMS array
There is no need to create global reference variables to forms, though, because VFP does it for you: Each form has a reference placed in each element of the _SCREEN.FORMS[] array. You can cycle through that array and find the form you want:

Code:
Inside Main.PRG:
DO FORM Form1
DO FORM Form2

Inside Form1.cmdDoIt.Click:
LOCAL loFrm
FOR EACH loFrm IN _SCREEN.FORMS
  if upper(loFrm.Name)="FORM2"
    * Do whatever you want with Form2 by using:
    loFrm.MethodOrProperty
  endif
ENDFOR

----
6) (My Favorite) Internal Application Messaging
This is absolutely my favorite, because it allows you to use non-modal forms, and doesn't require anything to be tightly coupled. Form2 could exist, or not. There may even be multiple instances of Form2, if the user opened it several times. This is great for situations such as if you're trying to every form that the program is about to shutdown, or reindex, or if the user just added another record and you need any other forms displaying the data in a tree or other un-bound view to update themselves.

However, this isn't for every situation. It's not very good for getting answers Back from forms, though it is possible: The SendMessage routine IS Synchronous, so any form that needs to give information back could do so by filling a global variable, or by setting a property in your global Application Object, etc.

Code:
In your application object create a method called SendMessage, or as a seperate file SendMessage.prg:
LPARAMETERS pcMessage,pvData
* pvData could be any data type.
LOCAL loMsg, loFrm
loMsg = NEWOBJECT('Session')
loMsg.AddProperty('Message',pcMessage)
loMsg.AddProperty('Data',pvData)
FOR EACH loFrm IN _SCREEN.Forms
  IF PEMSTATUS(loFrm,'ListenToMessages',5)
     AND VARTYPE(loFrm.ListenToMessages)='L';
     AND loFrm.ListenToMessages
    * Do NOT put two PEMSTATUS calls on the same expression...
    *   It can lead to problems.
    IF PEMSTATUS(loFrm,'GotMessage',5)
      loFrm.GotMessage(loMsg) && Send the Message!
    ENDIF
  ENDIF
ENDFOR


In any forms that you want to hear the messages:
1) Create a custom property ListenToMessages; 
   Set it .T. if you want to get messages.
2) Create a Custom Method GotMessage with 1 parameter.
Inside that GotMessage:
LPARAMETERS poMsg
IF VARTYPE(poMsg)='O' and VARTYPE(poMsg.Message)='C'
  DO CASE
    CASE upper(poMsg.Message)="REFRESH ALL TREEVIEWS OF PEOPLE"
      THISFORM.RefreshPeopleTreeview && or whatever is appropriate
    CASE upper(poMsg.Message)="SOME OTHER MESSAGE"
      THISFORM.RefreshPeopleTreeview && or whatever is appropriate
  ENDCASE
ENDIF


Finally, to Send a Message:
oApp.SendMessage( "My Message", "Any value you want, or even an object!" )
* OR:
DO SendMessage WITH "My Message", "Any value you want, or even an object!"

To send multiple values, just create an object, such as:
SCATTER NAME loRec
DO SendMessage WITH "This Person is Updated", loRec

----
7) (New in VFP8) Binding Events!
Now, this is new to me and I've never implemented anything using it.
You can find an example of how we Will use it in VFP8 Here:

http://fox.wikis.com/wc.dll?Wiki~NativeEventBinding

----
So, there you have it! A ton of ways to communicate between forms.

This covers all the ways that I can think of right now... If you come up with a way that significantly differs from these, drop me a Feedback!
Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top