Why does wx.Yield() not work?

88 views
Skip to first unread message

nepix32

unread,
Feb 13, 2018, 4:05:51 AM2/13/18
to wxPython-users
Asked this question on stackoverflow, will synchronize with this thread for users who only use groups.
https://stackoverflow.com/questions/48762163/why-does-wx-yield-not-work

I am well aware/familiar how to handle [LongRunningTasks](https://wiki.wxpython.org/LongRunningTasks) in wxPython (using ``threading.Thread`` is just working fine). But I always wondered, why ``wx.Yield()`` and its siblings are not working (or how they shall be used properly).

Attached a (not so) minimal example, tested with ``4.0.0a2 msw (phoenix)``:

    from __future__ import print_function
    from time import sleep
    from datetime import datetime
    import wx
   
    def long_running(handler):
        for i in range(10):
            thetxt = '{0}: {1}'.format(str(datetime.now()), str(i))
            sleep(1) # using this as drop-in for something which is blocking
            wx.SafeYield()
            wx.CallAfter(handler, thetxt)
   
    class tst_frm(wx.Frame):
        def __init__(self, *args, **kwds):
            wx.Frame.__init__(self, *args, **kwds)
            self.btn = wx.Button(self, -1, 'Click to Status')
            self.btn.Bind(wx.EVT_BUTTON, self.on_btn)
           
        def on_btn(self, evt):
            the_txt = '{0}: EVT_BUTTON'.format(str(datetime.now()))
            self.update_prog(the_txt)
           
        def update_prog(self, update_txt):
            """Handler for task update (``str``)."""
            print(update_txt)
           
    if __name__ == '__main__':
        app = wx.App(redirect=False)
        frm = tst_frm(None, -1, 'test_long')
        frm.Show()
       
        handler = frm
       
        long_running(handler.update_prog)
       
        app.MainLoop()

The button clicks are being registered, but neither the ``wx.CallAfter`` events nor the button events are being processed until after ``long_running`` has completed.

My questions:

* ``wx.Yield`` should allow to process events which are piling up?
* Can this example being made work with ``wx.Yield`` and if yes, how?
* If not, What is the explanation why it does not work?

Dietmar Schwertberger

unread,
Feb 13, 2018, 5:18:05 AM2/13/18
to wxpytho...@googlegroups.com
I did not run your code, but I noticed these two:
 - SafeYield is supposed not to deliver user input events like button
clicks; try Yield instead if you need these
 - wx.CallAfter(handler, thetxt) will call your frame, but that does
not have a __call__ method

Regards,
Dietmar

nepix32

unread,
Feb 13, 2018, 7:26:39 AM2/13/18
to wxPython-users


On Tuesday, February 13, 2018 at 11:18:05 AM UTC+1, Dietmar Schwertberger wrote:
I did not run your code, but I noticed these two:
  - SafeYield is supposed not to deliver user input events like button
clicks; try Yield instead if you need these

Changing ``wx.SafeYield()`` to ``wx.Yield()`` gives the same result (or better said will not lead to the EVT_BUTTON events be processed (these will be processed after ``long_running`` has finished)
 
  - wx.CallAfter(handler, thetxt) will call your frame, but that does
not have a __call__ method

``handler`` in ``long_running`` is not the frame, but the ``update_prog`` method of the frame, this should be fine

You should definitely try the snippet (should work as copy/paste) so that we are on the same page.

Robert White

unread,
Feb 13, 2018, 10:27:09 AM2/13/18
to wxPython-users
Try this. It waits to start the `long_running` function until after the mainloop has started.

from __future__ import print_function
from time import sleep
from datetime import datetime
import wx

def long_running(handler):
    for i in range(10):
        thetxt = '{0}: {1}'.format(str(datetime.now()), str(i))
        sleep(1) # using this as drop-in for something which is blocking
        wx.CallAfter(handler, thetxt)
        wx.Yield()

class tst_frm(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        self.btn = wx.Button(self, -1, 'Click to Status')
        self.btn.Bind(wx.EVT_BUTTON, self.on_btn)

        wx.CallAfter(long_running, self.update_prog)
        
    def on_btn(self, evt):
        the_txt = '{0}: EVT_BUTTON'.format(str(datetime.now()))
        self.update_prog(the_txt)
        
    def update_prog(self, update_txt):
        """Handler for task update (``str``)."""
        print(update_txt)
        
if __name__ == '__main__':
    app = wx.App(redirect=False)
    frm = tst_frm(None, -1, 'test_long')
    frm.Show()
    
    handler = frm
    
    # long_running(handler.update_prog)
    
    app.MainLoop()

Tim Roberts

unread,
Feb 13, 2018, 12:46:57 PM2/13/18
to wxpytho...@googlegroups.com
Robert White wrote:
>
> Try this. It waits to start the `long_running` function until after
> the mainloop has started.

This is the Right Answer.  Remember, all of the setup you're doing
basically just sends a bunch of window messages, including frm.Show. 
None of those messages can get processed -- and hence the application
can't get property initialized -- until your app gets into its main loop
to drain the messages.  You are assuming in long_running that the
application and its main loop are up and running, but they're not.  That
won't happen until long_running returns.

--
Tim Roberts, ti...@probo.com
Providenza & Boekelheide, Inc.

nepix32

unread,
Feb 14, 2018, 2:02:13 AM2/14/18
to wxPython-users
Hello Robert, hello Tim,

yes that was indeed the right answer. I was not taking the fact seriously enough that the main loop was not started yet (mistake by my part because I had observed cases where the GUI was reacting even if there was no call to app.MainLoop() at all).

I slightly changed the Code by Robert and kept the call to long_running outside of the frame class.

So the verdict is: wx.Yield() works but only if used carefully/properly.

from __future__ import print_function
from time import sleep
from datetime import datetime
import wx

def long_running(handler):
    for i in range(10):
        thetxt = '{0}: {1}'.format(str(datetime.now()), str(i))
        sleep(1) # using this as drop-in for something which is blocking
        wx.CallAfter(handler, thetxt)
        wx.Yield()


class tst_frm(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        self.btn = wx.Button(self, -1, 'Click to Status')
        self.btn.Bind(wx.EVT_BUTTON, self.on_btn)

    def on_btn(self, evt):
        the_txt = '{0}: EVT_BUTTON'.format(str(datetime.now()))
        self.update_prog(the_txt)

    def update_prog(self, update_txt):
        """Handler for task update (``str``)."""
        print(update_txt)

if __name__ == '__main__':
    app = wx.App(redirect=False)
    frm = tst_frm(None, -1, 'test_long')
    frm.Show()

    handler = frm

    wx.CallLater(1500, long_running, handler.update_prog)

    app.MainLoop()

Reply all
Reply to author
Forward
0 new messages