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

How to stop code execution unti form finished its task 2

Status
Not open for further replies.

AlastairP

Technical User
Feb 8, 2011
286
AU
I launch a form, I would like the form to perform its task then return a value: but I would like the calling method to pause until the form has completed its task
If I make the form model, this works, but this creates other problems. I would ike to do this with the form being modeless.
Just wondering what the approach would be to do this
 
I already gave two variants of a solution for that case. Sending in an object as parameter to the form, which is an object of the calling form, you can transport back a value in one or more of it's properties.
The calling form wouldn't wait in the sense of a DO FORM statement waiting for a return value into a variable, but the calling form could be triggered by the setting of the return value.

Eg you pass over a textbox and the called form sets the value of that textbox, then not only the value is set, also the programmaticchange event is triggered. And since the textbox is a part of the calling form, it can call any form method. It doesn't have to be a visible textbox, so your calling form does not need a textbox as it's visible UI for this to work, you can simply add an iinvisible one just for this mechanism.

Also you could use any object especially designed for this way of calling and having a two way vehicle to send in values and get back values. The object passed can also have methods to call. If you agree on same callback method the calling form should call when it's finished, you can use that instead of programmatic change, if you agree on some property having an assign method, that can be used and would work the same way as programmaticchange of a textbox.

The only thing important of course is, that the object passed in has to still be there, after the called form releases, so it's better something of the calling form.

Bye, Olaf.
 
The major change in calling a modal form now is, you don't continue after the DO FORM and you don't put a waiting loop there, you continue in another method, even of another object. So one problem which may arise is you depend on local variables after you DO FORM and they don't exist anymore. Then you need to change your ways, you need to put some things into properties instead of variables or persist them in tables or whatever else is applicable.

You have code like this:

Code:
Local somvariable
* some code setting somevariable
DO FORM xyz TO result
* some code needing both result and somevariable to work

As a sidenote, you may think of putting something like READ EVENTS or a WHILE loop after DO FORM. No, that introduces problems and you would not win something from that, in that case you could also stay with a modal form, that'd be much easier to wait.

So your new code has to end with the DO FORM, which calls the nonmodal form with the object getting back the return value and reacting to it, then the calling method ends. This time assume the passed over object is TExtbox Text100:
Code:
Local somvariable
* some code setting somevariable
DO FORM xyz WITH THISFORM.Text100

Now the called form retrieves this as parameter of it's Init, you have to have a LPARAMETERS toParam there to receive Text100, store it in some form property for later using it as return vehicle, eg store it into a property named oReturn.

If the called form sets oReturn.Value = returnvalue the Programchangeevent is triggered, as it's a textbox and setting a textbox.value does trigger that event.

So the Text100 textbox would have this code in programmaticchange:
Code:
* some code needing both result and somevariable to work

Instead of a variable "result" you had used before (vai DO FORM xyz TO result), this code now uses THIS.VALUE, it's own value property, as that is used to get the result value. But "somevariable" is not existing anymore. It was defined as LOCAL variable in the calling method and is already released. And don't solve this by using public variables instead. Remember inrtocucing non modal forms the user now has a free hand to do things in the order of his choice and you may reuse same public variable names and cause havoc this way, don't do that. So you have to store somevariable somewhere you can now get it back from, either you store it in a form property or a table, maybe in Text100.TAG or some other Text100 property. Or you change your original code so you only compute somevariable now and not beforehand. Depending on the situation the one or other way is the simplest, in many cases the simplest solution may be forwarding the values needed later as further properties of the object passed into the next form, which still will be there after coming back. Anyway, you have to make a code split in two parts, one before calling a non modal form and one triggered by the called form as continuation.

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

Nevertheless have a look at thread184-1736587

Bye, Olaf.
 
What kind of problems does making the form modal introduce for you? In my view, if you want to wait for the user to finish with the newly opened form, modal is the way to go.

Tamar
 
Not only is a modal form "the way to go", as Tamar points out, it's EXACTLY the mechanism provided within the VFP object model to do exactly what you've asked to do.

It's always easier (and usually more successful) to work WITH the product than against it. :)
 
Hi Tamar,
Only in that I was avoiding using a model form, as I thought I would have issues with calls back to the calling form object while the second form is model and active.

I have followed Olafs suggestion, of passing a control as a parameter, but this does not work while model, in this case.
Perhaps I will test his solution out with just two basic forms, and if works, work out where my current setup is not working.

I understand that a model form has to be resolved first - but
I am not sure to what extent, for example, should you still be able to access methods in other objects or procedures in programs?


 
I only agree with Tamar and Dan, if you accept modal states in general, I would avoid them as far as I can, so the user is never forced to a single form.

Of course users can't do 1000 things in parallel anyway and normally concentrate on one workflow, but actually we are used to parallel working more than we are aware of. Even a single form offers many options in parallel, you can use any control unless you disable them and only enable them in a certain order to guide the user through the form, but I dislike such guidance, and I think I am not in the minority with this. Often enough you get stuck as a user or as the developer of such guidance, as rules for the intelligent guiding are a moving target.

So normally you can act on any button or other control you like, you're not forced to sequentially working on something and modal states do that on the scope and granularity of forms. So a) the user isn't liking modal states and b) as a developer you're already used to program for parallelism without being an expert on threads and multi-threading or multi-processing. There is no reason to use and support the concept of modality, just because that's the intended way of MS for DO FORM ... TO Variable.

Your general idea of wanting a non modal form is welcome to me. Your reasons are pointing out lack of knowledge. All code is accessible at all times, that's just a matter of visibility of the code by set path, set procedure and set classlib, if a form is modal all other things still exist, all object instances are there, all forms. It's just the OS UI concept of modal blocking the user to switch the form, you can only switch to other applications, but within the current application the modal form asks for your attention until you're finished with it.

So "should you still be able to access methods in other objects or procedures in programs?" Yes, other things are available. So if the modal form calls back into the calling form or changes data displayed there, that's possible with modality. Modal states are only about the UI accessibility, modality is hindering deactivating the modal form and switching/activating another form. Calls to the other forms or procedures or anything else are not blocked, the only thing blocked is the focus/activation of other forms than the current modal form.

So modal states are hindering the user to start a new completely unrelated workflow, eg for answering someone on the phone. Also modal states hinder a shutdown, especially if you have such a form opened in the middle of a long running process while the user already went to lunch. A modal state can hinder you to react instantly to a phone call, because you first need to finish your workflow to start a new one. Modal states can be even frustrating, if you don't know what wants to have your attention, eg a Messagebox on the wrong screen, or some other modal form appearing behind some alwaysontop form.

I think of the modal form as a visually supported function. The machanism of a modal form returning a value is working like a function, just with a UI the user can interact with, it's just written differently, rather like STORE. So instead customeraccount = SearchAccount(filterparameters) you DO FORM SearchAccount WITH filterparameters TO customeraccount. It's normal, that code only continues after a function call, if the function returns back. From that perspective the VFP way to return from a modal form UNLOAD is fine, but only as long as that doesn't get in your way of doing several things in parallel.

In the case of picking one account that's not a major problem, but there can be modal forms with a longer initialising time, if looking up something more complex than just a customer account, and depending on the size of a company even that can take a while. A while, which a poweruser may use to switch to mail or something else, but not within the same application, which now is locked to the modal form.

So a major benefit would be, to change everything you can to non modal interactivity. The principle of a callback is sometimes not as easy to implement as a sequential program flow, but just because the workflow isn't thought through good enough. If you need interaction within some sequential program flow, it's better done at first or last, than in between.

If a process just sometimes need user interaction at all, it'd be nice if a user can still continue with what he does and react to the attention modal state without it being modal. To be able to do that you have to break your sequential program flow in at least two sequential parts with a continuation, so the wait state is there, but is no halt to anything else. It asks a developer to think more in terms of parallelism. But as said we are used to that already since moving away from a command prompt with interactions in the form of y/n questions, we have forms, that let you interact with several things at once. You just need to cut sequential program flows so that a interaction is not halting the application, but is just starting a non modal form and having an agreement on where and how to continue.

An agreement on a common return value property or a callback method does not hurt, just as the convention of returning a value from unload does not hurt. If you make your own parameter class for passing in and out values and offering a callback function, you already have a mechanism to decouple that for the general case. If you design a normal modal form you also agree on the number and type of parameters, so an additional agreement doesn't overcomplicate this. Providing a parameter class you make the former parameters properties, that's not making it more complicated. If you do a normal modal form you can pass back one value, so why not keep it that way, use a value property for that matter and set it. The only really new thing is, the called form needs to know what to do next, so there has to be an agreement on that. Well, a method of a parameter class can generically be used for that, and that's it. It's not a too complicated change of ways to win non modality from it.

My idea once was, like a function is specifying it's parameters and return value, a formparameter does that definition now, it could take the place of the form and call it. That's decoupling things further.

Let's start by reformulating the original DO FORM WITH param1,param2,param3 TO outputvariable to an OOP form call:

Code:
oForm = CREATEOBJECT("nonmodalform",param1,param2,param3,@outputvariable)
oForm.Show(1)

First of all, Show(1) makes the form act modal, no matter if it's designed modal or not. That's an advantage we don't actually want to make use of, but that's how far you can redo the DO FORM TO variable with a form class. A disadvantage now is, the variable passed in by reference after param3 indeed needs to be expected as another parameter of the nonmodalform, there is no TO variable construct in using form classes, the return value of unload will not arrive anywhere, you have to change that to act on the outputvariable. That's not possible with a string variable, as you can't keep the variable reference alive after the Init. So an object parameter comes to the rescue. An object lives as long as it lives, independ on how many times it's passed around. Doing that without caution it can get problematic, but I decide for it. And if I change the outputvariable to an object I can also get rid of param1-param3 and in general just pass one object, that's an optional decision, you can also stay with the param1-3, but I'll show the way with just one parameter:

Code:
oForm = CREATEOBJECT("nonmodalform",oParam)
oForm.Show(2)

Now the code execution continues, the nonmodalform acts on oParam when it's finished and that can be as simple as setting oParam.value = returnvalue or however you name the property for the return value. It could also be you additionally call a predefined method of the oParam object: oParam.callback(). In that case you may even get rid of the value property and do oParam.callback(returnvalue). So instead of returning (passively) you call something. What's cause by this is up to the developer of the oParam class and it can be different, when you inherit a generic oPAram class in different sub classes.

There's of course one thing the nonmodalform has to do with the passed in oParam: It has to store it into some form property (that again is just one idea, but a simple enough idea to program into a base form class):

Code:
*nonmodalform init:
LPARAMETERS toParam

Thisform.Addproperty("oParam",toParam)

Code:
*nonmodalform unload (or whenever you're read to return):
Thisform.oParam.value = returnvalue

* OR 
Thisform.oParam.value = returnvalue
Thisform.oParam.callback()

* OR

Thisform.oParam.callback(returnvalue)

* OR (you can't do that with Unload):
Thisform.oParam.callback(returnvalue1, returnvalue2)

* OR you define a property callbackcode, which contains something to execute by macro substitution:
lcCode = Thisform.oParam.callbackcode && again just your imagination is in the way of what you can put in here.
&lcCode(returnvalue)

Since oParam can be anything, eg a child object of another form, that call can now do anything in that context. You can also continue in a third form or another application.

So isn't that also introducing more options than you have with a return value from Unload?

Bye, Olaf.
 
A little unconventional demo using a parameter object to let a secondary form move an image of a parent form. This shows you don't even need to release the child form after returning feedback.

Code:
_screen.AddObject("oForms","collection")

o = CreateObject("gameform")
_screen.oForms.Add(o)
o.Show(2)

Define Class gameform as Form
    width  = 320
    height = 320
    Add Object image1 as image
    
    image1.picture = Home()+"fox.bmp"
    image1.top = 140
    image1.left = 140
    
    Procedure Init()
        o = CreateObject("joystick",This.image1)
        _screen.oForms.Add(o)
        o.Show(2)
    Endproc
EndDefine

Define Class joystick as Form
    width = 180
    height = 180
    alwaysontop = .t.
    
    Add Object cmdup as commandbutton
    Add Object cmddown as commandbutton
    Add Object cmdleft as commandbutton
    Add Object cmdright as commandbutton
    
    cmdup.caption = "UP"
    cmdup.top = 10
    cmdup.left = 70
    cmdup.width = 40
    cmdup.height = 40
    
    cmddown .caption = "DOWN"
    cmddown .top = 130
    cmddown .left = 70
    cmddown .width = 40
    cmddown .height = 40

    cmdleft.caption = "LEFT"
    cmdleft.top = 70
    cmdleft.left = 10
    cmdleft.width = 40
    cmdleft.height = 40

    cmdright.caption = "RIGHT"
    cmdright.top = 70
    cmdright.left = 130
    cmdright.width = 40
    cmdright.height = 40

    
    Procedure Init()
       Lparameters toFox
       
       Thisform.AddProperty("oFox",toFox)
    EndProc
    
    Procedure cmdup.Click()
       Thisform.oFox.Top = Thisform.oFox.Top - 8
    EndProc
    
    Procedure cmddown.Click()
       Thisform.oFox.Top = Thisform.oFox.Top + 8
    Endproc

    Procedure cmdleft.Click()
       Thisform.oFox.Left = Thisform.oFox.Left - 8
    EndProc

    Procedure cmdright.Click()
       Thisform.oFox.Left = Thisform.oFox.Left + 8
    EndProc
EndDefine
 
Hi Olaf,

I will digest this and report back.

With thanks

Alastair
 
I have a similar requirement in programs that I write. I have a parent form that will trigger a child form. The child form must be completed prior to any action being taken on the parent form. This can be solved with the modal form property being set; however, this locks the entire application. My requirement is that the child form is *ONLY* modal for the parent form; other forms (MDI) can still be accessed and actions (methods) be executed while the child form is active (but no activity allowed in the parent of the child). I refer to this as a semi-modal form (child).

To accomplish this, I use the LightBox concept of 'graying' out the parent form and then displaying the child form. The child form is passed the object reference of the parent form and in the click event of the "Ok" button (child form) I raise an event via RAISEEVENT() command in the parent and pass parameters from the child back to the parent.

The lightbox that I use is an image class (from Bernard Bout) that uses GDI to create an image of the parent form and then gray it via alpha blending (you have to make any OLE objects invisible or they will bleed through the image). In the click() event of the image class I have added code to set the focus back to the child form -- thus keeping it on-top of the parent. I also use BINDEVENTS command to bind to the child form's Release and QueryUnload events so that I can take action if the child is closed via the 'X' button in the upper right corner or the 'Cancel' button. When the RAISEEVENT method is executed, I clear the BINDEVENTS, close the child form, and then hide the lightbox and restore any hidden OLE controls. Now the method can continue as I need in the parent form (the method called from the RAISEEVENT).
 
Yes, the lightbox principle is another way to enforce usage of a form, it helps focusing the attention.
Bernard has also done something about activex:
I'm not nearly finished with my sample, but I think the idea is now explained clear enough. I just want to point out, if you try to close the game form before the joystick form, you'll see that won't work. That's because the joystick form stores a reference to the image control of the game form, so that can't release and therefore the whole form can't release. That needs further decoupling to be able to close forms in any order and of course a child form calling back into a parent form then either needs to close with the parent or at least needs to check if the object passed in still is active, before using it, eg changing Left/Top properties. So for now this mechanism saves you from errors, which would occur, if the game form would release and the up/doan/left/right buttons try to move a non existing image.

So one obvious way out is that you add a collection to store references of child forms and the parent form release all these child forms before releasing itself. A simple collection works, just like the one I added to _Screen.

I also talked about a parameter object/class which could be used instead of calling the child form directly, which I didn't introduced yet. I won't have the time, shortly, to give further samples, but of course this can decouple things further in that the parameter object holds the image1 reference and so the child form can release independently.

Bye, Olaf.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top