FAQ Index - Search - Recent Changes - Everything - Add entry

<< Previous Entry | FAQ Entry 3.7 | Next Entry >>

3.7. How can I force updates to the application windows during a long callback or other internal operation?

If you have a long-running callback or internal operation that tries to modify the application windows incrementally during its execution, you will notice that this doesn't happen; the windows of your app freeze for the duration.

This is by design: all gtk events (including window refreshing and updates) are handled in the mainloop, and while your application or callback code is running the mainloop can't handle window update events. Therefore nothing will happen in the application windows.

The trick here is to realize where your operation can take a while to return, or where it is dynamically changing the window contents, and add a code fragment like this wherever you want an update forced out:

 while gtk.events_pending():
   gtk.main_iteration(False)
This tells gtk to process any window events that have been left pending. If your handler has a long loop, for instance, inserting this snippet as part of the loop will avoid it hanging the window till the callback has finished.

More eloquently, in the words of the great Malcolm Tredinnick, 'this requires using what should be called "Secret Technique #1 For Making Your Application Look Responsive"(tm):

If you want to avoid freezes in your application while you are doing large amounts of work in the background, remember to call gtk.mainiteration() from time to time.'

Of course, if your callback has a single instruction that takes a long time to process, this isn't an option. There is no easy alternative to solve this problem, as far as I can see. Doug Quale remarks on how this applies even to idle_add()ed functions:

Idle functions are scheduled when gtk+ doesn't have any other work to perform, but gtk+ doesn't preempt them. Each idle function will run to completion once it's started.

Note that running a mainiteration inside an idle, io (input_add) or timeout handler is generally considered `a bad idea' because it doesn't work very well. If you really need to do so, check FAQ 20.1.

Moreover if during the computation you want to lock the GUI (no operation can be performed) without making all the widget insensitive you can use the other secret technique called "Yeti"... [1]

 import gtk
 from time import sleep

 def response(widget, response_id):
     if response_id != gtk.RESPONSE_APPLY:
         gtk.main_quit()
     else:
         i=0
         n=1000

         progress = dialog.get_data("progress")
         progress.set_text("Calculating....")
         progress.grab_add()

         while i < n:
             sleep(0.005)
             progress.set_fraction(i/(n - 1.0))
             i += 1

             while gtk.events_pending():
                 gtk.main_iteration_do(False)

         progress.set_fraction(0.0)
         progress.set_text("")
         progress.grab_remove()

 dialog = gtk.Dialog("Modal Trick", None, 0, (gtk.STOCK_EXECUTE,  gtk.RESPONSE_APPLY, 
                         gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))

 dialog.connect("response", response)
 dialog.connect("destroy", gtk.main_quit)

 box = dialog.get_child()

 widget = gtk.CheckButton("Check me!")
 box.pack_start(widget, False, False, 0)

 widget = gtk.Entry()
 box.pack_start(widget, False, False, 0)

 adj = gtk.Adjustment(0.0, 0.0, 100.0, 1.0, 10.0, 0.0)
 widget = gtk.HScale(adj)
 box.pack_start(widget, False, False, 0)

 widget = gtk.ProgressBar()
 box.pack_start(widget, False, False, 0)

 dialog.set_data("progress", widget)

 dialog.show_all()
 gtk.main()
This way the GUI does not freeze and you cannot interact with widgets, if you like you can unlock one widget (e.g. you want to give the chance to block the long process) using gtk.grab_add() on that widget.

[1] [mail.gnome.org]

PyGTK FAQ Wizard | PyGTK Homepage | Feedback to faq at pygtk.org