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!

Use events from another Already Opened Form 1

Status
Not open for further replies.

Hong Jun

Programmer
Dec 26, 2023
3
PY
Hey guys im having trouble trying to do a simple action

I have a form called modulo_tesoreria, wich contains a grid, and a search button that contains the code to fill the grid with a query.

The issue i have is, i call a second form named cargar_formas_de_pago, the first form "modulo_tesoreria" stays on the background, and i make changes on the database using "cargar_formas_de_pago, i need the first form "modulo_tesoreria" to execute the Click event on the search button each time i commit a change on the database.

I tried using

DO FORM modulo_tesoreria
WITH _SCREEN.ActiveForm
.paginas.estado_ventas.grid_estado_ventas.Refresh
ENDWITH

But this opens a new modulo_tesoreria window and i want to use the one already open on the background

 
Hello Hong Jun and welcome to the fourm.

What you need is an object reference to the form holding the grid. You can achieve that by addin gthe NAME clause to the DO FORM:


Code:
DO FORM modulo_tesoreria [b]NAME oForm[/b]

(oForm is just an example; you can of course give it any name you like.)

From now on, you can reference any of the properties, methods or controls of modulo_tesoreria simply by referring to oForm. So to execute the Click event of a button on modulo_tesoreria, you could do this:

Code:
oForm.cmdSearch.Click

This will work for as long as oForm remains in scope. If the above two lines of code are in the same method, that will work fine. If they are in different parts of the program, oForm will have gone out of scope by the time you execute the second of the above lines. One quick solution to that would be to declare oForm as a public variable. That's not ideal, for various reasons, but it should work OK in this case.

Mike
__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Although calling an event in this way should work, it is not the best way to handle this situation. Most developers would prefer to create a new method of the called form (modulo_tesoreria in this case), and to call that directly, rather than going via the button.

So you would create a custom method of modulo_tesoreria, called, say SearchGrid. Copy the code from the Click of the Search button to that new method. Then, in the Click of the Search button, all you need is:

Code:
THISFORM.SearchGrid

Finally, in place of

Code:
oForm.cmdSearch.Click

do:

Code:
oForm.SearchGrid

Mike


__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Hello Mike, thanks for the welcoming.

Modulo_tesoreria is not callled using a Do FORM is there a way to add the object reference on the init or load event of the form? or formset?

 
Mike has explained two aspects, but I fear you won't be able to make it work for you, as you're confronted with the scope of things: The question is what is known to be what it is under which name.

Your own code does DO FORM, so yes, of course this starts the form again, that's what DO FORM does. What you instead need at that stage of your code is an expression that refers to the already started form.

Mike has shown how to create such an expression you use the NAME clause of DO FORM. But it has one weaknesss, still, Directly after the form is started it is now known under the name given by Mike as oForm, you better make it oForm_modulo_tesoreria, instead, so it is more explicitly telling what form it refers to and code using that name makes clear what form it makes use of. And one more thing to know is that this actually does not only create a name that you can later refer to, it actually creates a variable with that name, oForm or as I suggest oForm_modulo_tesoreria, that variable also only has a limited scope where it is known, so it's still nopt a name that is valid as long as the form exists in any other code of the application. It is only available local to the code that initially started the form with the DO FORM command extended by the NAME clause.

So, now you have two problems, even knowing this part of the solution:
1. When the time comes you want to refre4sh the existing form, first of all, you don't yet know whether the form already is started and even if - because the second form can only be started from the first form, the first form could still be closed.
2. You may not have access to the variable oForm_modulo_tesoreria, because where it was created by the DO FORM command it's scope was limited to that method.


You already know one other way to refer to an already started form, you used it in your code: _SCREEN.ActiveForm.
The problem with that is, that it would refer to the second form, as that is active. You can only use it in that code, because the previous DO FORM creates that and thereby also activates that. So that code is disserving you, it does not act on the already started form, it always acts on a new instance of that form. It should work anway, so you could also finish this job by closing the older version of the form. Just you don't have hands on it.

Well, what is the solution to all this? It's in short: Planning ahead, establish the necessary structures you need in your coding to act on already started forms. A form handler, that is an object which would be used in every place you want to start a form instead of the DO FORM command, would make it possible to refer to any started form. You'll say that's like shooting a sparrow with a connan. Yes, there is a simpler solution that you need to apply where you start the second form, not where you start the first form.

Starting the second from within the running first form is the place and moment in your code, where you should plan ahead for later. That is the moment you know a simple reference to the already form, that is simply THISFORM. And you also are able to pass in something to the second form, the form you start, you can make THISFORM a parameter to the second form, you just have to add a parameter line to the INIT event of the second form.

So now, at the start (INIT) of the second form you get passed into a parameter what you later need to refer back to the form that called and needs to be refreshed. To have that available also after the INIT is finished, you need to store that parameter iunto a form property, for example. You could call that parameter to_modulo_tesoreria, store that value into a form property you call o_modulo_tesoreria and later the code that refreshes this still running form would refer to it by thisform.o_modulo_tesoreria.

So in order of what to change, where and how:

You first need to extend the cargar_formas_de_pago with a property that will store the referencce to the modulo_tesoreria form, call that property omodulo_tesoreria with the prefix letter o for object.

Then change the init of cargar_formas_de_pago to accept a form reference and store it into this new form property:
Code:
Lparameters tomodulo_tesoreri
Thisform.omodulo_tesoreri = tomodulo_tesoreri

And then still two things need to change: When you start the cargar_formas_de_pago form from the modulo_tesoreri form this code needs to be

Code:
Do Form cargar_formas_de_pago with Thisform

The WITH part of the Do form is for specifying with which parameters to call the form, and in this case you make the form a parameter.

And the third thing to do then is the actual place where you wish to cause the refreshing of a page of the modulo_tesoreri form in the cargar_formas_de_pago form:

Code:
Thisform.omodulo_tesoreri.paginas.estado_ventas.grid_estado_ventas.Refresh()

I bet you could solve the problem by only changing that part of the code that should call the refresh, but sorry. You have a problem that is caused by not acting in advance and planning ahead. While Windows knows all forms that run, your code can't simply refer to the form you know is running by its name only.

You have to plan ahead, you have to create the ability of the called form to remember who called it by establishing a storage placwe for it in the form of a property and to pass it in as a parameter.

Is it really not solvable simpler? It surely is solvable without establishing that parameter construct, too, but judge for yourself, if you find this simpler code: You can go through all currently open forms with _screen.Forms collection and loook into the name of the forms, until you find one that is called modulo_tesoreri. Well, if you actually set the name property. By default the name property of a form will be "form1", no matter if the form fie is form1.scx or modulo_tesoreri.scx, and that's only one of the problems you face, if you try it this route.

If modulo_tesoreri is a form that runs twice, you will not refresh the form that called this instance of cargar_formas_de_pago, and even if that never is the case anyway, the way to make a new form know who started it is a generally more useful way to be able to refer back to it than any other mechanism.

You could also tackle this on a much higher level, when you would establish a form handler, who does not only start all forms, but can have the ncessary memory about which forms already run and even which form caused which other form to be started. It's beyond the scope of the help I could give you.

But in very short, sorry, the solution to this needs changes in more than one place only. You have to provide the ability of the cargar_formas_de_pago form to know the tomodulo_tesoreri that called it to be able to then cause the refresh of it.

Well, in the end there also is a quick and dirty simple solution when it comes to refreshing, you could simply refgresh all forms that currently run, you don't need to concentrate on one page of one form only, though it surely is the more decent and faster solution not causing too much refreshing where it's unnecessary. But in general, as said you can go through the list of all forms:

Code:
Local loForm, lnI
For lnI = 1 To _screen.FormCount
   loForm = _screen.Forms[lnI]
   loForm.Refresh()
Endfor lnI

If all else fails, try that.

Chriss
 
Thank you Chris Miller i managed to execute the events using your method and i will take note for future codes to not commit the same mistakes and think ahead so i can avoid problems like this
 
The last solution, by the way, can cause struggles. Especially if you program into the Refresh() events to do more than is done by default. There's nothing wrong about that, when the default refresh behavior is not enough, but this can easily lead to never ending feedback loops that finally just cause everything to flicker or make the whole application freeze. So be aware, that this is where you would need to establish a mechanism against too frequent repeated refreshs, refresh code could for example store SECONDS() and only act again, if the last time it was called was at max SECONDS()-.1 to disallow two or more refreshs within the same 10th of a second.

Just one idea. Also notice, while this sounds terrible, the system - and I mean Windows, not only the VFP runtime - often already knows it won't need to repaint areas of a form, so it's not a big overhead to simply refersh everything.

Chriss
 
Hing Jun said:
think ahead so i can avoid problems like this

A good New Years resolution. Obviously, you can't think ahead of anything you might want to do at some point in the future. That's not what I mean by appealing to that. But now that you know you have a problem and you can't solve it in the one place you need the solution, think where else in the code you have the knowledge you need. That is in the place you started the second form, so that's also a place to visit in code and to change.

So from the perspective of someone who now knows what new feature you want, go back to where it can be solved. You have not only the one place you look at and don't figure out what to do there, you have the luxury of seeing all code and know in which order it runs and evolves, and you can make your interventions at multiple places to have a less hard problem.

So the lesson to take away is, that as a developer you can't see into the future, too, but you have a time machine as you can go anywhere in code, not only at the exact one place and moment you think the solution should be placed. You can always go back from there and thin ahead as the aftermath, that's what you can as the developer. You don't just have the one place at your hand in which you fail to express what you need, you can have the necessary information at hand in earlier code.

We all had that lesson a lot of times, being told to think outside of the box. Well, it's not even necessary here, if you think of all code as the box. Don't just stare into the one corner, look where you have the information you need at hand easily and then change that to forward or store that information in time, so you have it when it's needed. Or, as the second solution, just don't care about the details and solve things on a bigger level.

Chriss
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top