Open_form() doesn't end the existing form

From the docs :

You can switch to displaying a different form by calling the function open_form() (from the anvil module). When you do this, the new form takes over entirely - the old form is no longer visible.

That bit (my emphasis) isn’t quite true, or probably more accurately it’s doesn’t work how I think it should work as any code after the open_form() on the calling form is still executed.

Is there a way to stop execution of the current form’s code and pass control completely to the new form, with no way back? Based on some return values from server functions I want to display an error message and force a login.

I might also be going about this all wrong, so open to suggestions…

EDIT -
actually, I can just put a return after the open form and this works most of the time. I can see how it might fail if I’m nested.

I suppose maybe I’m asking if it’s possible to “flush” the app and force it to start again.

The new form takes over visually. (The old form is removed from the page, all its components’ hide events fire, etc). It doesn’t do anything as drastic as causing random Python code to exit!

Yes, returning after open_form() is probably what you wanted to do :slight_smile:

I’m tired :slight_smile:

…and emotional? :wink:

I’ve been painting, so emulsional …

2 Likes

Does this mean that if I open_form(“one”), then open_form(“two”), then “one” then “two” etc could run into stack issues?

My use case is to have something like tabs that can be conrinuously open and closed at will. Does this approach lead to continuous nesting?

I think this is a question for Skulpt experts.

Related: It’s also possible you might end up with multiple instances of forms “one” and “two”. If you’re concerned about the latter, you could cache these forms and reuse them. This can be especially helpful if they’re supposed to retain some information from one “showing” to the next.

@simon.m.shapiro, your approach is absolutely fine, and won’t lead to nesting.

When you call open_form(...), the new form replaces the currently open form, but whatever code you write after open_form() carries on running until you return from whatever event handler (button click, timer tick, etc) triggered it. That’s all. (Once that code has finished running, the old form and all its data is available for garbage collection)

So just to add an explicit recommendation, always return after the open_form() call especially if performed as part of an if statement or something similar. That way you ensure no more code is run.

1 Like

@meredydd I have an customised event system that registers listeners in the __init__ of the form. Am I correct in thinking that open_form runs the __init__ of the form each time open_form is called?

__init__ is always called when the form is created and always works with open_form.

It also works if you add forms to a container with this:

get_open_form().content_panel.clear()
get_open_form().content_panel.add_component(Form1())

But it will not be called if you re-use a previously opened form. That’s when show is called. Here is a quick example to show the scenario I describe:

f1 = Form1()                                     # __init__() is called
get_open_form().content_panel.clear()
get_open_form().content_panel.add_component(f1)  # show() is called
[...] 
get_open_form().content_panel.add_component(another_form)
[...] 
get_open_form().content_panel.clear()
get_open_form().content_panel.add_component(f1)  # show() is called

(Here f1 should be stored either as a member of the main form or as a global of a module)

If the form name is given as a string, then yes.

If you’re using open_form(f) where f’s value is an existing form, then no. f’s __init__() was called earlier, when the its form was created, and will not be called again.

When I want to retain the [user-entered] data in existing forms, and simply switch between the forms, I use the latter approach.

Many thanks. This approach is providing the results I need.

I first create the form with:

self.my_form = MyForm()

and the I use it with open_form(self.my_form).

For some reason this technique is 100% not working for me :frowning_face: :frowning_face: :frowning_face:

I want to be able to switch to a confirm form if I get a confirm token when the page loads. To test, I created Form1 and Form2.

Forms are:

class Form1(Form1Template):
  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    goto_confirm = True
    if goto_confirm:
      print("Opening form...")
      confirm_form = Form2()
      open_form(confirm_form)
      confirm_form.say_hi()
      print("Form2 is open...")
      return
    self.init_components(**properties)
    print("... and I'm still here!")

  def confirm_button_click(self, **event_args):
    print("Opening Form1...")
    confirm_form = Form2()
    open_form(confirm_form)
    confirm_form.say_hi()
    print("Form2 is open...")
    return

class Form2(Form2Template):
  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)
    print("Hello I'm initializing Form 2")
    
  def say_hi(self):
    print("HI FROM FORM2!")
    # Any code you write here will run when the form opens.

I would expect this to open Form2 as soon as Form1 loads. Output is this:

Opening Form1...
Hello I'm initializing Form 2
HI FROM FORM2!
Form2 is open...

However, Form1 is still on the screen!!! If I click the button, Form2 opens as expected … using the same code :woman_shrugging: (I even tried going through the event handler by raising a click and got the same results!!!)

Hi @heidi,

Having Form1 as your startup form is equivalent to having a startup module with the code:

open_form('Form1')

which in turn is the same as:

f = Form1()  # <-- __init__ runs here
open_form(f) # <-- form appears on the page here

So you can see, the form isn’t actually opened on the screen until after Form1's __init__ has finished executing. This is why calling open_form() from your form’s __init__() function often doesn’t work as you’d expect! You open the new form, sure, but it’s immediately replaced as soon as the __init__() function returns.

You can get around this by instead calling open_form() from the form_show event. Although if you’re doing that, you should probably consider using a startup module instead, and deciding in that module which form to display.

3 Likes

Very clear as always @meredydd! Thanks for the help!