Forum Archive

Another dialogue issue

Tel

I have a dialogue problem, and hope someone can help.
In a Model-View-Controller setup, my View has a TextLine and a TextView

When a line is typed, it passes to the controller, which echoes to the TextView.
All this works fine. (This is for a MUD-type adventure game)
The controller passes the line to a parser in the model.

On certain inputs I want the model to get more data from the user via a FormDialog, which I can do easily. But the dialogue does not block the main loop. I’m using this code which I found on your site.

def myform_dialog(title='', fields=None,sections=None, ```
Insert Code Here
```done_button_title='ok'):
    global c

    sections = [('', fields)]
    c = dialogs._FormDialogController(title, sections, done_button_title=done_button_title)

    c.container_view.frame = (0, 0, 500,900)
    c.container_view.present('sheet')
    c.container_view.wait_modal()
    # Get rid of the view to avoid a retain cycle:
    c.container_view = None
    if c.was_canceled:
        return None
    return c.values

Obviously, I don’t know what I’m doing. I tried some combinations of @ui.in_background, but nothing helps. Any ideas?

cvp

@Tel Why did you not use the standard method dialogs.form_dialog?

cvp

And sometimes, I've to put @ui.in_background in front of each def calling the form_dialog

JonB

Are you trying to create a form which does NOT block?

The blocking is caused by wait_modal. I gather you want is to return a view, not a value. You can't return values unless you block, since values will not exist yet, until the user interacts. With a view, you can have delegate methods on the textfield/textview, which then call some other callback that you define

Tel

@JonB I do want it to block, but if I call it with another view still active, it does not block. You see I have the wait_modal call?

Tel

@cvp can you point me to an example? I think I am using the dialog form_dialog

The call is
```
result = myform_dialog(title='Link design', done_button_title='ok',fields=fields, sections=None)

It seems that there might be a problem when another view is still active. I can send full code if you like...```=

JonB

Can you explain what your problem is? Are you getting an error? Or itbl doesn't show up? Are you calling your function from a callback of another UI ?

Are you trying to present a 'sheet' while another sheet is still there?

What might be helpful is to show a minimal example of a set of uis that display your issue. I.e you don't have to share your whole code, just mock up a textview and your actions, etc.

The trick, if you are trying to launch a dialog from, say, a textfield delegate, is that the delegate action must return before anything else can happen. That means, you need to show your form without a wait_modal, and instead must use a callback, maybe tied to will_close of your dialog. In some cases you may be able to use in_background, though a complication is that everything executed in_background all gets queued to the same thread, which can lead to strange things. Another approach that is more robust is to spawn your own thread. See for example this decorator

A better approach sometimes is to use a "shield view". @polymerchm pioneered this approach, nice shown in action here. This is a semi transparent view that gets added to your root view, but at the top of everything else, so no touches go down to your root view. But you can use it as a parent for dialogs.

cvp

@Tel I think that you will never receive a better explanation than @JonB 's one.
Sometimes, if a form_dialog needs to call another dialog, or even a console.input_alert, there are problems. In this case, I use the "shield" method, exactly like @omz does with the datepicker in its dialogs.py.

Tel

@JonB thanks for that. It’s very helpful
I will try to put together a minimal example. But I think the direction you point is good.

I am calling, from a delegate, a routine that calls another , say X, which put some up this form_dialog. The dialog appears, but the X thread continues before the form_dialog gets data. That is why I say the form dialog is not blocking.

The blue link dialogs.py did not work for me.

cvp

@Tel said:

The blue link dialogs.py did not work for me.

When you type xxxx.py, this forum put it in blue, even if there is no link 😢

Tel

@JonB here is a cut down version showing the problem. You will see that the calling thread continues while the dialog is still up.

```
import sys, dialogs
import ui
import console

import controller
global theView

class MyView(ui.View):
last_content_offset = (-1,-1)
def init(self, args, *kwargs):

    super().__init__(*args, **kwargs)
    self.make_view()
    self.value = None

def make_view(self):
    self.tv= ui.TextView(title='textview1', frame=self.bounds.inset(10,10),editable = False, border_width=2, bg_color='white', flex='hw')
    self.update_interval = 1/60
    self.tv.height = self.bounds.height-70
    self.add_subview(self.tv)

    tf = ui.TextField(frame = self.bounds.inset(10, 10), text_color='red')
    #print (self.bounds)
    tf.frame=(10,240,self.bounds.width-20,40)
    tf.height =32
    tf.delegate = self
    tf.flex = 'w'
    tf.begin_editing()
    self.add_subview(tf)

def update(self) :
    self.tv.content_offset = (0, self.tv.content_size[1] - self.tv.height)
    if self.tv.content_offset[1] < 0:
        self.tv.content_offset = (0,0)
    #if self.tv.content_offset != MyView.last_content_offset :
        #print ('*',self.tv.content_offset, self.tv.content_size, #self.height)
    MyView.last_content_offset = self.tv.content_offset

def feedback(self, st):
    self.tv.text += st +'\n'

def textfield_did_change(self, textfield):
    self.value = textfield.text

def textfield_should_return(self, textfield):
    st = textfield.text
    self.tv.text += '>'+ st + '\n'
    parse(st)
    textfield.text=''   
    return True

def delayed_close(self) :
    self.feedback('shutting down...')
    ui.delay(self.close, 5)

def feedback(st):
global theView
theView.feedback(st)

def startView():
global theView
print('starting View')
theView = MyView(frame = (0,0,800,300), bg_color='white')
theView.present(style='sheet', animated=False)
return theView

this routine copied from forum, without the decoration, the dialog never gets input, have to cancel Pythonista

@ui.in_background
def myform_dialog(title='', fields=None,sections=None, done_button_title='ok'):
global c

sections = [('', fields)]
c = dialogs._FormDialogController(title, sections, done_button_title=done_button_title)

c.container_view.frame = (0, 0, 500,900)
c.container_view.present('sheet')
c.container_view.wait_modal()
# Get rid of the view to avoid a retain cycle:
c.container_view = None
print('from myform_dialog:',str(c.values))
if c.was_canceled:
    return None
return c.values

this part actually in the model

def parse(st) :
fields = [{'title':'name','type':'text','value':st} ]
result = myform_dialog(title='Link design', done_button_title='ok',fields=fields, sections=None)
feedback(str(result))
print('from parse:',result)

if name == "main":
console.clear()
#show just ui
startView()
```

cvp

@Tel no module controller, sorry

Édit: no need, commented

cvp

@Tel Please, Try this

    result = dialogs.form_dialog(title='Link design', done_button_title='ok',fields=fields, sections=None)
    #result = myform_dialog(title='Link design', done_button_title='ok',fields=fields, sections=None)
Tel

@cvp that hangs up Pythonista

cvp

@Tel oups, I don't have that, please restart (NOT REINSTALL) Pythonista

cvp

@Tel said:

You will see that the calling thread continues while the dialog is still up.

The calling thread does a "print from parse" but I don't see it in console before I press ok in the dialog, thus that works, isn'it?

Tel

@cvp this is weird. It worked once, after I restarted. Now cannot repeat it, even after restart. Moreover, it seemed to affect Safari. I had to refresh the page before it allowed me to reply.

cvp

@Tel said:

it seemed to affect Safari. I had to refresh the page before it allowed me to reply.

Not sure of that, I have remarked such problems with this forum since some days.

cvp

@Tel I' e tried with and without @ui.in_background before def parse, no difference

cvp

In your "cut down" version, how are you sure that the calling thread continues while the dialog is still up?

Tel

@cvp system updated to 12.4.1 last night.
This program hangs for me every time, even after restart.

cvp

@Tel Good to know, I'm in 12.4, without any problem...

cvp

@Tel Are you in the beta version of Pythonista?

Tel

@cvp when I had the call to myform_dialog, there was a print in there. Now, of course I can’t do any thing.

Running just what I found in App Store.

cvp

@Tel said:

when I had the call to myform_dialog, there was a print in there

Sorry, I don't understand this sentence (obviously due to my English 😢)

Do you want to say that when the dialog is displayed, you immediately see a "from parse:" in the console, before tapping ok?

Tel

@cvp it hangs up Pythonista all the time now and I can’t check anything. C’est foutu.

cvp

@Tel First, reboot your iDevice

cvp

@Tel and perhaps, back to 12.4?

Tel

@cvp I rebooted device. It still hangs there. I don’t know how to go back to system 12.4

cvp

@Tel When you say "it hangs", that will say that the script hangs or Pythonista or the iDevice?

Tel

@cvp Pythonista hangs. The device seems fine.

cvp

@Tel Did you try to remove Pythonista from the active apps (what we call restart Pythonista) by swiping from bottom middle and then swiping the app above?
Sorry if it is an obvious question but sometimes, restart Pythonista doesn't have the same meaning for all people.

Tel

@cvp absolutely. It’s the only way to get rid of the running app. Unless you know another way...?

cvp

@Tel No, you're right.

Now, you say that Pythonista hangs, that will say that you can't do anything with it?

cvp

@Tel did you try "Pythonista3://" in Safari?

Tel

@cvp I don’t understand doing it in Safari, but it doesn’t help.
Cannot do anything with Pythonista when it hangs.

cvp

@Tel could you check this:
- remove Pythonista from active apps
- settings/Pythonista/safe mode

Tel

@cvp I did that. It still hangs.

cvp

@Tel Very strange.
I don't know if you have taken a backup before your upgrade to 12.4.1. If yes, restore it.
I don't know if you have vital scripts or data. If no, reinstall Pythonista.

Tel

@cvp thanks for your help. I’m going to see if anyone else has ideas before I give up on the project.

cvp

@Tel said:

Cannot do anything with Pythonista when it hangs.

Sorry, to insist.
You say "when it hangs".
But does Pythonista hang immediately or only when you start this script?

Tel

@cvp it hangs when I run this script, after it displays the form dialog

cvp

@Tel ok, sorry, I had understood that Pythonista did hang at its start and that you can't do anything. My fault, thus not so vital 😀

cvp

@Tel did you try

@ui.in_background
def parse(st) :
Tel

@cvp better, but not working correctly. Try this, you will see that the NewLink ()returns None before the input is processed

import sys, dialogs
import ui
import console

global theView


class MyView(ui.View):
    last_content_offset = (-1,-1)
    def __init__(self, *args, **kwargs):

        super().__init__(*args, **kwargs)
        self.make_view()
        self.value = None

    def make_view(self):
        self.tv= ui.TextView(title='textview1', frame=self.bounds.inset(10,10),editable = False, border_width=2, bg_color='white', flex='hw')
        self.update_interval = 1/60
        self.tv.height = self.bounds.height-70
        self.add_subview(self.tv)

        tf = ui.TextField(frame = self.bounds.inset(10, 10), text_color='red')
        #print (self.bounds)
        tf.frame=(10,240,self.bounds.width-20,40)
        tf.height =32
        tf.delegate = self
        tf.flex = 'w'
        tf.begin_editing()
        self.add_subview(tf)

    def update(self) :
        self.tv.content_offset = (0, self.tv.content_size[1] - self.tv.height)
        if self.tv.content_offset[1] < 0:
            self.tv.content_offset = (0,0)
        #if self.tv.content_offset != MyView.last_content_offset :
            #print ('*',self.tv.content_offset, self.tv.content_size, #self.height)
        MyView.last_content_offset = self.tv.content_offset

    def feedback(self, st):
        self.tv.text += st +'\n'

    def textfield_did_change(self, textfield):
        self.value = textfield.text

    def textfield_should_return(self, textfield):
        st = textfield.text
        self.tv.text += '>'+ st + '\n'
        theModel.parse(st)
        textfield.text=''   
        return True

    def delayed_close(self) :
        self.feedback('shutting down...')
        ui.delay(self.close, 5)

def feedback(st):
    global theView
    theView.feedback(st)

def startView():
    global theView
    theView = MyView(frame = (0,0,430,300),  bg_color='white')
    theView.present(style='sheet', animated=False)
    return theView

class Model() :

    def parse(self,st) :
        print('after calling NewLink:',NewLink(st))

@ui.in_background
def NewLink(st):
        fields = [{'title':'name','type':'text','value':st} ]
        result = dialogs.form_dialog(title='Link design', done_button_title='ok',fields=fields, sections=None)
        feedback(str(result))
        print('from parse after dialog:',result)


if __name__ == "__main__":
    console.clear()
    #show just ui
    theModel= Model()
    startView()

cvp

@Tel Try this 😂, it seems to work

#@ui.in_background
def NewLink(st):
Tel

@cvp nope, it hangs for me.

cvp

@Tel WoW, it runs ok for me

cvp

sorry for this obvious question, but you are sure you tap on ok?

Tel

@cvp yes. I am going to shut this topic down. No one will read a topic with 50 posts

cvp

@Tel Ok, but a topic is not specially for other people than the guy who created it.
I was still trying to find a solution but your script is ok for me with or without the @ui.in_background.
It prints the "after dialog" only when I press the ok.

Tel

@cvp but don’t you see that Newlink return None, before that?
This is the output when I type “This string” in the view

after calling NewLink: None
from parse after dialog: {'name': 'This string'}

cvp

@Tel Yes, I've seen but the only one print that proves, for me, that the wait_modal is ended is the "from parse", and this one comes only after I've pressed ok.
I agree it is strange

Tel

@cvp so, back to my original point, the dialog should block the return out of Newlink, but it doesn’t. And there is nothing for me to put a wait_modal() on.

cvp

@Tel Could you, please, for my pleasure, try this modification and tell me if the print "after dialog" comes after you pressed ok

    def textfield_should_return(self, textfield):
        st = textfield.text
        self.tv.text += '>'+ st + '\n'
        NewLink(st)
        #theModel.parse(st)
Tel

@cvp of course, but in order to see what’s happening you need to insert
print(NewLink(st))

And you will see the same thing happens.

cvp

@Tel I really don't understand why you need this print because it will print the result of newlink it the print "after dialog" also.

Tel

@cvp that print is just to prove that NewLink has completed before the dialog OK has been pressed. Therefore the dialog did not block.

Tel

So NewLink() has completed. Now if there is a return(result) at the end of NewLink(), after the print, where does it return to?

cvp

@Tel ok, I can't help more, I'm sorry

Tel

@cvp thanks for your patience.

cvp

@Tel Agree, and that's why I say it is strange because the result in the print"after dialog" is correct...

cvp

For me, this is ok

    @ui.in_background
    def parse(self,st) :
        r=NewLink(st)
        print('after calling NewLink:',r)

#@ui.in_background
def NewLink(st):

For the first time, console shows both line in the right order

from parse after dialog: {'name': 'Azert'}
after calling NewLink: None
Tel

@cvp [i was taking a nap for a while] I’ll try that.
There’s obviously a lot I don’t understand about ui.in_background.

Tel

@Tel that seems to do it!!! But I don’t understand it, do you?

cvp

@Tel The only thing I know is that there is a main thread with, normally all UI process, and another ui.in_background for other process (+ all threads you want, of course).
I know that wait_modal on a presented ui.View returns immediately if called in the main thread. That's why you should put the @ui.in_background in the function that calls form_dialog. But here you have a sequence of functions called until form_dialog, and thus not easy to say in which you have to put this décoration

Doc says:

Wait until the view is no longer on screen. This can be useful for creating modal dialogs. If this is called from the main (UI) thread, e.g. via an action, it returns immediately. It also returns immediately if the view is not (yet) on screen. In typical usage, this is called directly after View.present().

cvp

@Tel said:

i was taking a nap for a while

a nap can be beneficial, the proof is that the problem seems solved 😂

Tel

@cvp well, I just can’t find the right place to put it in my program.
I will have to try another way entirely.

cvp

@Tel Good thinking and good luck, sometimes needed 😀
If you think you need help, don't hesitate, this forum has a lot of smart and marvelous guys

JonB

I think ui.in_background is not what you want. I'll take a look later tonight, but you should try using @run_async from the script I linked to earlier.

Tel

I think I found the right place for it.

@ui.in_background
def got_input(st) :
    theModel.parse(st)

That works in my program too.
Thanks everyone for running such a patient and informative forum.