Forum Archive

Label text not displayed until end of Button action

Bob Walance

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

dgelessus

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.

Bob Walance

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

abcabc

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]
JonB

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
dgelessus

Yes, I meant time.sleep of course. In Java it's called Thread.sleep and I was talking about threads so much, so I got confused.

ccc

Yeah... And maybe you did not get enough sleep. ;-)

dgelessus

We should probably stop derailing this thread now... :P

Bob Walance

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

ccc

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