Accessing a variable on another form without using get_open_form or self.parent

What I’m trying to do:

Access variable x which exists in the code of Form 1, from code on Form 2

My app has a repeating panel within a data grid, which is fed by a data table.

  • Form 1 (‘Main_form’) is the standard ‘Main_form’ which contains the repeating panel
  • Form 2 (‘Table_row_template’) is the Form which was automatically created when I double clicked on my repeating panel to populate its contents, this form defines what is shown within each row of the repeating panel within the data grid.

The problem:

I can’t use get_open_form() or self.parent to access variable x from Form 2 because Form 2 does not sit underneath Form 1 in a hierarchy structure, instead it is a parallel form.

What is the simplest way to pass variable x from Form 1 to Form 2 when they are not hierarchically structured? The variable to be passed is the contents of a component on Form 1.

I’m hoping to avoid a restructure of the data grid / repeating panel setup as the data grid and repeating panel are working nicely connected to a data table.

Thanks,

Richard

You could create a module called Globals and import it on all your forms, then you can use Globals.x = x to assign a value and x = Globals.x to read it. It will work on any form.

This thread discusses some details about a similar problem.

If you have time to butcher and rebuild your app, this post describes another way to manage the info visible by all the forms in one single variable in the Globals module. It would require tons of restructuring (which you don’t want to do :slight_smile:) but you would end up with all the info stored in and managed by one global object.

2 Likes

You could create a module called Globals and import it on all your forms, then you can use Globals.x = x to assign a value and x = Globals.x to read it. It will work on any form.

Thank you for this answer, it worked for accessing a simple variable between the forms.

However, I now realise that I not only need to be able to access a Form 1 variable from Form 2, but also to then set that variable (modified in Form 2’s code) as an attribute of a component on Form 1. For this, I need to do something like ‘self.parent.component.attribute = x’ but obviously can’t do this as the forms are not hierarchically organised in a way which would allow this.

Similarly to using a ‘Globals’ module to let all forms know about a variable ‘x’ set on one form, is there a similar method for doing this for components too, so that all forms will be able to see and manipulate that component’s attributes, or any other simple way to do this?

Some context:

  • I am using the ‘default’ / ‘pre-set’ way of populating a repeating panel with a data grid, which I created by double clicking on the repeating panel.
  • The new form (Form 2) which was created for the repeating panel’s contents upon this double click is not hierarchically arranged under Form 1 in the App Browser window, hence me being unable to use ‘get_open_form()’ or parent - but, the connection to the data table is fast and works well, so I don’t want to move away from this setup.
  • When I tried to manually build a repeating panel myself without using the default ‘pre-set’ doubleclick method, the performance of the data tables connection was unacceptably slow, even though doing it this way might have allowed better communication between the forms.

You could do something like:

# in Form1
Globals.form_1_label = self.summary_label

# in Form2
Globals.form_1_label.text = 'something'

This works, but it can get messy in the long run.

A cleaner way to do it would be by setting up some custom events on the container form and firing the events from the contained forms. The documentation describes how to do it here, and this tutorial uses the technique in the delete button.

Hi @richard.lewis,

Not sure if this helps your specific problem - yet it might still be usefull to you.
something I do often if I have a repeating panel (or similar) which has to communicate back to the parent form is to give it a reference of the parent form when I populate it.

###In Your Home Form:

l = [1,2,3,4]

#populate with reference
self.repeating_panel_1.items = [(self,i) for i in l] #usually this would be just self.repeating_panel_1.items =  l


###In the Form of the repeated row:
self.home,self.item = self.item


#Something like this should also work:
for c in self.repeating_panel_1.get_components(): c.home=self

Thats a simple trick but each repeated form has now access to its parent instance. This way is independent of any nesting level (unlike .parent) and does not require any outside module. Since self is only a reference i guess performance should also not matter to much.

-Mark

3 Likes

You could have a look at the messaging module of Anvil Extras.

You might also be interested in the (experimental) atomic module of Anvil Labs.

2 Likes

Here is another option: use this DataGridJson custom component and you don’t have the problem at all.

Your problem is accessing components on the form from a repeating panel template form.
If you use the DataGridJson, you don’t have row template forms (well, you do, but they are created under the hood for you by the custom compoent).
And you don’t need to define the custom events (well, you do, but the custom component does it for you).

You add the custom component, define the columns by specifying what columns have buttons (links in my implementation) and what callback function to use when those buttons are clicked, define the callback functions in the main form, right there next to everything else and everything is cleaner.

The advantages are that you don’t need to define a row template form, you don’t have code in two forms and you don’t need to manage the communication between the two forms.

The disadvantage is that the row template form is defined automatically for you and it may not be flexible enough. If you are lucky it’s good enough for you, if you are less lucky, you may be able to improve the custom component so it includes all the features you need, if you are less less lucky… you will need another of the options listed above.

1 Like

I think the ‘Globals’ approach is still the right way of doing this. To take a step back, if taking this approach, you should think of your form as representing the state of your data, and that data should be stored in Globals (and ONLY stored in Globals), materialized through bindings to the Global data.

So, for example, you can add the following to Globals:

my_state = dict()

registered_forms = []

def update_forms():
    for form in registered_forms:
        form.refresh_data_bindings()

And in each form where you want to show or interact with state, you make use of bindings with write-back where needed. Append any form that reflects state (self) to the registered_forms. Whenever you make a change to state, you call that Globals function, and the registered forms updated. Here’s an example:

Example

There’s good discussion here:

Thank you to everyone who contributed to this discussion!

When I made the original post, my issue was understanding how to pass variables between 2 forms in general. I tried the first solution posted, which worked (thanks @stefano.menci)

My problem then became ‘how will the component on Form 1 know that a component on Form 2 has been clicked’, as what I was trying to do was allow the user to click a button on Form 2 (row for repeating panel) which would then set some text on Form 1.

This would require something on Form 1 to be actively ‘listening’ for things happening on form 2.

The first solution I tried was using the Anvil Extras Messaging Module suggested by @owen.campbell, and it worked.

Here are the steps I followed using the messaging module:

  1. Create a client module called ‘common’ with this code:

from anvil_extras.messaging import Publisher
publisher = Publisher()

  1. On Form 2 (the form where the user would click the button), set up the ‘sender’:
from .common import publisher

def add_button_click(self, **event_args):
  text = self.item['Brand_Name'] # This is a piece of text taken from the data table
  publisher.publish(channel='general', title=text)
  1. On Form 1 (the form where the user would need to see the text appear after clicking the button) set up the ‘receiver’:
from .common import publisher

# In the Form's __init__
publisher.subscribe(channel='general', subscriber=self, handler=self.text_message_handler)

# Then, make a function within Form 1, this effectively listens for the button being clicked on the other form:

def text_message_handler(self, message):
  self.text_label.text = message.title

My app now works as desired and no data-grids were harmed in the process.

Thanks all!

Richard

4 Likes