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 gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Exiting from a form in its Init() method. 1

Status
Not open for further replies.

AndrewMozley

Programmer
Oct 15, 2005
621
GB
I have a form which searches for customers; this is invoked from many parts of an application.
The parameters supplied to this form may include a string to search for customers with a range of names or postcodes or indeed customers satisfying other criteria (outstanding balance, say).

In the Init() method of the form I establish which are the relevant customers, create a cursor of these and display a grid where the user double-clicks on the one he wants.

The form works fine; it displays a grid with the relevant customers, and in due course the unload() method of the form returns a suitable id to the calling form.

If however there is only one customer meeting the criteria I do not wish to invoke the grid. So (and this is where I am going wrong), in my Init() method I say, effectively,

IF <there is only one customer)
Thisform.unload()
<some other instruction>
RETURN
ENDIF

This does not however achieve the result. The (empty) grid is still displayed. I have included other instructions as <some other instruction> including RETURN .F., and NODEFAULT.

None of these achieve the desired result. How can I exit from a form in the Init() method, returning a value to the calling?

Thanks. Andrew
 
Make sure you do this in the init()

if only Onecustoemr
RETURN .F.​
endif

Ez Logic
Michigan
 
Init is too late to be setting up data. That should be done in Load. Init is when all of a form's controls are initialized and they all expect data to already be in place. If any of the controls cannot initialize, the form's init will RETURN .f. and the form will not run.

In the init, check to see if conditions are OK to proceed. If not, RETURN .F.

Don't try to add anything more complicated. As EZ says,

Code:
If <condition>
   Return .F.
Endif
 
EzLogic has put one thing right, you need to return .F. from either Init or Load of a form to not display it at all.

The second wrong assumption is, that calling Unload will cause the form to do it's unload and return behaviour. You can't call events, well you can, but it will not cause the default behavior. To trigger events you need to do what triggers them, eg Unload is triggered by thisform.Release(), so you'd do that. I don't find if that'll work both. The help topic on Init states more generally RETURN .F. does prevent an object to be created, including that the Destroy() event therefore also doesn't occur, as the object never really gets alive.

You make an overall error of putting data access and business logic together with the form, the UI. This is not separation of concerns. There is no easy way out, as you'll need either more components, the clean design, or a more complicated way to stay with the form.

1. To stay with the form you put the decision to stay with the form or release it into it's Activate event, not in the Load or Init. So you stay as you are in Init and neither call Unload as you did nor Return .F., but you check the RECCOUNT() of your query in the Activate and if RECCOUNT("queryresultalias")<1 thiisform.release(). Furthermore you might not want to check this more than once, Activate can happen many times, if users switch between forms. So create some property lActivateRan and set it .f. at design time and .t. in the activate. If it's .t. don't execute the Activate code again.

2. For the more elegant way in conformance with N-tier separation of concerns you have one problem with the way VFP forms and datasessions work. If you put your query into a separate class and call that instead of the form, it runs in the datasession of the current form, but the form displaying the result normally will have it's own new private datasession. One way to overcome this is to base you business logic on the SESSION class. That way you first create a new datasession without generating a form, then can decide whether you want the result to be shown with a form or not. If you make this a major concept you can let all forms have the general datasession as their setting, but decide what datasession they start in by calling them from the session object you want the form to run in. This is a bit complicated and fragile, as you need a little session and form management/handling.

3. Another way to not depend on the form mechanism to return a choice is also more elegant in allowing the selection form to be non modal. You pass in a parameter object with a property for the return value. Then you don't depend on the Unload event to return some value. You can set the return value at any time and release the form. If you use the advantage of the selection form not being moal anymore that now means you have to signal in some way to the calling form, that a choice has been made. The calling form will not wait for the selection form to return, as it's not modal anymore. So the parameter object might have a method to call to make this feedback. If the parameter object for example is instanciated as a child object of the calling form, you can have code in it, using THISFORM.selection(), which will call the selection method of the calling form, not of the selection form, as the object is a child of the calling form. You can even make setting the return value property trigger some action, as you can define the returnvalue property with an assign method and define what's done with the return value in there.

This is just one idea of how to work with a parameter object. This is also something for advanced developers, but the ideas of how to implement it are only limited by your imagination. You have to make sure about the scope and therefore the lifetime of the parameter object. It ideally should be a child object of the calling form, to still be alive after the called form is released. To make some object a child, you of course don't create it by CREATEOBJECT or NEWOBJECT, but you create it in the calling form by THISFORM.ADDOBJECT. Or you simply let some container or textbox or whatever control you add via the form designer be the parameter object, then it's by default a child object. You can make this control invisible, as it's just there to receive an answer of the user. As return value you might "misuse" a textbox.value and also can bind it via the controlsource. As said, ideas are just limited by your imagination.

The overall lesson is, just because the Unload event is foreseen as one easy way to let a form return a value you don't have to use it. It's bound to a form, it's bound to a modal form, and it limits you in many ways, as you now experience. As developer you should make the code work for you, not vice versa.

Bye, Olaf.
 
There is yet another solution, in addition to those suggested above.

Instead of doing the search in the same form that displays the grid, do it in the form that captures the user's search criteria. If the search results in more than one customer, launch the form with the grid as at present. If it returns a single customer, go straight to the customer form. And it if returns zero customers, stay in the search form so that the user can edit the search criteria.

As well as solving the problem you described, this has the advantage of keeping the search logic and the search user interface together, which will make to easier to validate the search parameters and inform the user of any errors or inconsistencies.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Mike, unless you make the result list form work in the same datasession, that'll introduce the need to do the query twice, which might or might not have some performance impact.

You could also put the search/filter part of the form into the result form as page1 of a pageframe and let it hide the grid in page2 without showing the tabs, then either switch to page2 or go into the single customer form directly.

Bye, Olaf.
 
unless you make the result list form work in the same datasession, that'll introduce the need to do the query twice, which might or might not have some performance impact.

In fact, I was thinking it would be the same datasession: you create the cursor in the search form, and use it to populate the grid in the second.

In practice, I don't do any of this. I have a single form that captures the search criteria and displays the result. That could be done in the way that you suggest, Olaf, with two pageframes. But I usually put the results grid on the same page as the search criteria, and keep the grid invisible until there is a result to show.

Mike



__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Thank you all for your replies. Maybe I did not explain the requirement very clearly! To recapitulate, I am trying to return a value from a form without displaying the form. The reason being that the logic of the form has determined the return value without the need to interact with the user.

Ezlogic. Thanks. Your suggestion does indeed exit without displaying the form. However it does not permit returning a value to the calling form. At present this value is returned by the Unload() event which is invoked by the Release() method. I tried including this code in the Init() method :

If <Only one customer>
Thisform.Release()
RETURN .F
ENDIF​

However (as far as I can see), the Release() and Unload() methods do not get called, because the form did not successfully initialise.

Dan. Thanks. Unfortunately I need to include my logic in the Init() method. Since a parameter (effectively a search key) is being passed through to the form, that parameter is not available during Load(), only during Init(). Again, as mentioned above, returning .F. from Init() does not allow a value to be returned to the calling form.

Olaf. Thanks. There is a lot of material in your answer! I am not qualified to judge about N-tier separation, data sessions and business logic. However in your point (1) you mentioned about the Activate() method - I had only vaguely been aware of this method. I have now included this code in the Activate() method :

IF RECCOUNT() < 2
.Release()
ENDIF
This achieves the objective: The form is not displayed, and the Release() method invokes the Unload() event which returns the value - which had been calculated in the Init() method.

I think I also understand your suggestion of passing a parameter as an object. Do you have a worked example? I take it that the calling form would need to have agreed the name(s) of a variable within the passed object in which the called form could place the returned value(s)

Mike. I take your suggestion about doing some of the logic in the form that captures the data. However it may be that there are several forms in the application where it is necessary to identify a customer, and I was hoping not to have to duplicate the code in each form.

I am grateful for the thought you have all given to this matter.

Andrew

 
IF RECCOUNT() < 2

If that's working for you, that's fine. I would, however, not do it with a pure RECCOUNT(). In case the grid alias reccount>=2 this'll work at start, but if the selected alias changes later, you have the risc of closing the form due to some other alias just having 0 or 1 records. So make it RECCOUNT("queryresultalias"), as suggested, whatever is your query alias.

And/or make it run just once, eg using the FORM.TAG proeprty:

Code:
If NOT(THISFORM.TAG=="ACTIVATED")
         THISFORM.TAG=="ACTIVATED"
    IF RECCOUNT("youralias")<2
       THISFORM.RELEASE()
    ENDIF
ENDIF

In regard to parameter objects, you don't need to have an agreed name. The INIT code of the form would have LPARAMETERS loParameter or however you want to name it. What you need to decide is how you call the property receiving the result value, you won't be able to set loParameter=3 to return 3, you'll set loParameter.value = 3 later, for example.

To let this parameter object live throughout the form life until it is released, you'd store loParameter as a form property, eg in init do
Code:
LPARAMETERS loParameter

Thisform.Addproperty("oParameter",loParameter)

Then later on set Thisform.oParameter.value = x

In the form, which makes the call you could put an invisible textbox as the parameter object, then you DO FORM xyz WITH THISFORM.Text1 for example and later on will receive back the result in Thisform.Text1.Value. If you use a textbox you even don't need to define an assign method, you can react to the .value being set by code in the Text1.ProgrammaticChange event.

Bye, Olaf.



Bye, Olaf.
 
Returning values from a form

Thanks Olaf. I may be making rather heavy weather of this. I have an application form which has a property Thisform.cAccount, and I want to call a search form and get a value (eventually) back into this variable. I am trying to do this by passing an object as a parameter

At present I am doing something like this (I realise that I could equally have used an invisible text box):

oReturn = CREATEOBJECT("Custom")
ADDPROPERTY(oReturn,"Account")
DO FORM SearchAccount WITH oReturn
Thisform.cAccount = oReturn.account
RELEASE oReturn
. . . .

Then in SearchAccount.Init() I say :
PARAMETERS oParam
. . . .

(If I do not need to activate the grid, because there is only one relevant account)
oParam.account = <value the form wishes to return>
. . . .

Realize that I could have passed the complete form, “Thisform” as a parameter to the “SearchAccount” form, but that did not seem quite right. I do need to be able to call “SearchAccount” from other places in my application.

Thanks again

 
Could I suggest a slightly more generic approach (at risk of confusing the issue).

1. In the SearchAccount form, create a custom property called, say, oCaller.

2. In the Load event of SearchAccount, add this line: [tt]THISFORM.oCaller = _SCREEN.ActiveForm[/tt]. This will store a reference to the calling form in the custom property.

3. In any forms that call SearchAccount, optionally create a custom property to hold the information you want to receive back from SearchAccount. Call that property, say, uReturnVal.

4. From anywhere within SearchAccount (including the Init), whenever you want to return a value to the calling form, test to see if oCaller has a property named uReturnVal (use PEMSTATUS() to do that test). If it has, store the value that you want to return in oCaller.uReturnVal. If it hasn't, do nothing.

I don't know whether this is easier than the approach you have been attempting. But it has two advantages:

(i) The caller does not have to pass any parameters or explicitly receive a returned value.

(ii) If you call SearchAccount from a form that is not expecting a returned value, the system will still work.

Just something to think about.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
DO FORM SearchAccount WITH oReturn
Thisform.cAccount = oReturn.account

You should know why this won'T work with a nonmodal form by now (reading your new thread). Using an object this way you don't make use of the possibility of triggering something within that object, you could simply stay with a normal variable, this still needs a modal form.

The thing you miss is, that you can't wait for the called form, instead your object has to have some event waiting for an action from the called form, and ProgrammaticChange is such an event.

You have the right idea, passing THISFORM is also a valid thing to do. That in itself doesn't bind the SearchAccount form to only work with this calling form, other places in your application can also pass in THISFORM, being another form, you only have to agree on the passed in object having an cAccount property that is set. I find the textbox so easy, as it has a value property and the Programmaticchangeevent, you simply use that and don't need to reinvent the mechanism.

You can agree on some specific property, some generic property as Value or also some specific method instead of using an event. So you could define a class based on custom and add a method you name "callback", then the return value can also be passed back as a parameter of that method. How you return is fully in your hands, you have to agree on something, but only with yourself, don't you? If you are only the developer of the SearchAccount form, you have to stay with returning a value, as you can't change the DO FORM calls, but then you can only stay with a modal form, there is no way to both have the caller wait and not wait without changing both the SearchAccount form and the calls to it.

For this thread your solution can be the Activate solution, for your other question about making such SearchAccount and similar forms work nonmodal you can only have that when changing both the forms and the calls to them.

Bye, Olaf.
 
Sorry, I overlooked something. You and AlastairP have posted rather similar problems and I threw you into the same pot.

Nevertheless have a look at thread184-1736647

Bye, Olaf.
 
That is all right Olaf! No offence taken. In fact my SearchAccount form is (rightly or wrongly) a modal form, so by the time I get to the next instruction in the calling program the value has been stored in the property.

Mike, I will certainly consider your approach. I had forgotten the concept of _SCREEN.ActiveForm. It certainly saves having to create a specific object rather than just the property (returned account code) that I am interested in.

Andrew
 
FWIW, I think returning a value from the called form is better than requiring every caller to have a property with a certain name. That is, I think the way Andrew is doing it is the right approach, though Olaf correctly points out that in this case, the return value doesn't need to be an object.

Tamar
 
If we're talking preferences <g>, I'd rather make the form modal remembering that 1) modal forms halt code execution, 2) when you hide a modal form execution continues but it's still in memory, and 3) when execution continues you can query a property of the hidden modal form before releasing it.

That places "having a property" responsibility on the called form, where it probably should be.

Code:
loForm = Createobject('theform') && or DO FORM
With loForm
   *Here you could set the necessary form properties/call methods
   *to do your queries instead of passing params.
   .Show(1) && modal
   * execution pauses until the form hides itself, then:
   Theresult = .MyProperty  && pull out the result
   .Release()
Endwith

You just have to construct the modal form so that it Hides itself rather than RELEASEing itself.
 
Thank you Tamar (it is worth it!) and Dan for your advice.

I am inclined to make my search form modal, so that when the calling form gets to its next instruction, the search form has done its thing.

Another thing (I realise that I may be rambling!) : at present my search form uses the default data session, so in this case the customers table is deemed to be already open. I find this convenient, since the calling form (whichever it is) is interested in customers, so has that table open. But perhaps purists would say that for true encapsulation I should be using the (default) private data session, in case the calling form was already doing something to another customer record. Just something I need to be careful about I guess.
 
For a lookup form used from another form that's already doing business in a specific table, I see no reason not to share the datasession between them. If you have your lookup form use the default datasession, it will share the calling form's datasession even if it's a private datasession. When something like that works "automatically", it's not usually by accident. [rednose]

Yes you need to take care that the user doesn't carelessly abandon work being done but you should be doing that already, no?
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top