Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Label text not displayed until end of Button action
-
With the UI Designer's help, I have a simple button and also a label (Record_label).
In the button's Action code:
...
view["Record_label"].text = "START" # write to the label
time.sleep (2) # delay for a bit
view["Record_label"].text = "END" # write to the label
...The problem that I'm having is that I only ever see the text "END" in the label.
However, if I put in a line of code between "START" and "END" that causes an error, the script halts and then I do see the "START" text.
So, I know that the "START" text is getting into the label, but the label's text field is only displaying the last modification (i.e., when the button's action terminates).
Does anyone know what I can do to have the label's text field immediately show what has been written to it?
Thanks much,
Bob -
The issue here is that by default all UI action functions and delegate methods are called in the main UI thread. This means that when you say
time.sleep(2)
in an action function, that gets called in the main UI thread, which makes the entire app hang for two seconds.To fix this, you can add the
ui.in_background
decorator to your action function, like this:import ui @ui.in_background def myaction(sender): sender.text = "Hello!" time.sleep(2) sender.text = "Bye!" ui.load_view().present()
The
in_background
decorator makes the function run on the Python thread instead of the main UI thread, which means that Python waits for two seconds while the UI thread is still running and can update the label text. -
When I add the decorator in front of any button's 'def' then that button becomes inactive. I must be doing something wrong.
Also, you used thread.sleep() in your code. I can't find any documentation on that.
Thanks for your help on this.
Bob
-
Use ui.delay.
import ui from functools import partial def delay_action(sender): sender.title = 'bye' def button_tapped(sender): sender.title = 'Hello' ui.delay(partial(delay_action, sender), 3) view = ui.View(frame=(0,0,200,200)) # [1] view.name = 'Demo' # [2] view.background_color = 'white' # [3] button = ui.Button(title='Tap me!') # [4] button.center = (view.width * 0.5, view.height * 0.5) # [5] button.flex = 'LRTB' # [6] button.action = button_tapped # [7] view.add_subview(button) # [8] view.present('sheet') # [9]
-
Likely @dgelessus meant time.sleep, not thread.sleep. Either that, or ui.delay are good methods, either approach will work, although the @ui.in_background method only works if you have one in_background method running at a time, since these are placed in a queue. So if you had two buttons using this approach, and pressed them at the same time, the first would run, then the second would not start until the first completes.
A third approach is to define a threaded decorator:
def run_async(func): from threading import Thread from functools import wraps @wraps(func) def async_func(*args, **kwargs): func_hl = Thread(target = func, args = args, kwargs = kwargs) func_hl.start() return func_hl return async_func
and decorate your action thusly
@run_async def myaction(sender): your code here
-
Yes, I meant
time.sleep
of course. In Java it's calledThread.sleep
and I was talking about threads so much, so I got confused. -
Yeah... And maybe you did not get enough sleep. ;-)
-
We should probably stop derailing this thread now...
:P
-
I really appreciate all of the tips. I'm a hardware engineer and am learning a lot about programming thanks to people like y'all.
What I ended up doing was just to have the Button action routine set a semaphore that the main routine acts on. This works perfectly and is probably the right thing to do anyway so that main is not bogged down by delays in any of the background tasks.
Bob
-
My approach would follow @dgelessus advise...
import time, ui @ui.in_background def myaction(sender): sender.superview["Record_label"].text = "START" # write to the label time.sleep(2) # delay for a bit sender.superview["Record_label"].text = "END" # write to the label def make_button(title='Click me'): button = ui.Button(name=title, title=title) button.action = myaction return button def make_label(text='Who cuts your hair?'): label = ui.Label(name='Record_label', frame=(50, 50, 200, 32)) label.text = text label.text_color = 'blue' return label if __name__ == '__main__': view = ui.View() view.add_subview(makes_button()) view.add_subview(make_label()) view.present() view['Click me'].center = view.center
Although it does not solve the two button problem that @JonB raises,
ui.in_background
does run myaction in a way that does not degrade the responsiveness of your UI. See: http://omz-software.com/pythonista/docs/ios/ui.html#ui.in_background