Tkinter: update_idletasks

J

Jeffrey Barish

I'm confused about how to use the update_idletasks method. In my
program, I have a handler for a button in which execution will linger.
During that time, I would like for the GUI to continue to show signs of
life. I have a Pmw MessageBar in which I display a status message. I
figured out that if I run update_idletasks on that MessageBar, then the
MessageBar will update the display as I update the message. However,
if I cover the GUI with some other window and then expose it again, the
GUI does not refresh until the handler finishes (except for the
MessageBar). Do I have to run the update_idletasks method for every
widget in the GUI? for all the frames? for just the root frame? Or is
it impossible to get the GUI to refresh in this situation?
 
R

Russell E. Owen

Jeffrey Barish said:
I'm confused about how to use the update_idletasks method. In my
program, I have a handler for a button in which execution will linger.
During that time, I would like for the GUI to continue to show signs of
life. I have a Pmw MessageBar in which I display a status message. I
figured out that if I run update_idletasks on that MessageBar, then the
MessageBar will update the display as I update the message. However,
if I cover the GUI with some other window and then expose it again, the
GUI does not refresh until the handler finishes (except for the
MessageBar). Do I have to run the update_idletasks method for every
widget in the GUI? for all the frames? for just the root frame? Or is
it impossible to get the GUI to refresh in this situation?

To make your GUI responsive, call update_idletasks occasionally from the
task that is taking a long time. Like most GUI systems, Tkinter is
basically a single threaded system. It'll run the current task until
finished, then process the next event. update_idletasks gives it a
chance to handle other events.

There are other ways to handle this sort of thing. Typically a
long-running task should be run as a separate background thread (or even
a separate process). The difficulty is that background threads cannot
safely interact with GUI elements, so how does the thread communicate?

The most straightforward technique is to transfer data from the
background thread to the main thread via a Queue object (as usual), then
poll the background thread's Queue (by repeatedly calling after to do a
nonblocking read on the Queue object).

However, a clever trick posted fairly recently is to have the background
thread generate an event. Apparently that is safe. Then have a handler
listen for that event. The handler will run in the main thread, and so
can safely update the GUI.

-- Russell
 
E

Eric Brunel

Jeffrey said:
I'm confused about how to use the update_idletasks method. In my
program, I have a handler for a button in which execution will linger.
During that time, I would like for the GUI to continue to show signs of
life. I have a Pmw MessageBar in which I display a status message. I
figured out that if I run update_idletasks on that MessageBar, then the
MessageBar will update the display as I update the message. However,
if I cover the GUI with some other window and then expose it again, the
GUI does not refresh until the handler finishes (except for the
MessageBar). Do I have to run the update_idletasks method for every
widget in the GUI? for all the frames? for just the root frame? Or is
it impossible to get the GUI to refresh in this situation?

At tcl level, update_idletasks isn't a "method", i.e. the tcl command doesn't
take any parameter telling which widget to refresh. So calling the Tkinter
update_idletasks method on any widget has exactly the same effect, which is to
refresh the whole GUI.

There are some issues on Windows however, where the newly created toplevel's may
not refresh until full control is returned to the GUI. To work around this
problem, use the wait_visibility method to wait until the newly created window
is displayed. But be careful: wait_visibility processes events, unlike
update_idletasks that does just a GUI refresh. There is apparently no simple way
of updating the display of newly created toplevel's on Windows without returning
full control to the GUI (at least with tk/Tkinter)

I never saw the problem you describe (some windows refreshing, some not), but I
mainly develop on Linux, which may show a different behaviour than the platform
you're working on (which BTW you don't mention...). Maybe you can post a small
piece of code showing the problem?

HTH
 
J

Jeffrey Barish

Eric said:
At tcl level, update_idletasks isn't a "method", i.e. the tcl command
doesn't take any parameter telling which widget to refresh. So calling
the Tkinter update_idletasks method on any widget has exactly the same
effect, which is to refresh the whole GUI.

There are some issues on Windows however, where the newly created
toplevel's may not refresh until full control is returned to the GUI.
To work around this problem, use the wait_visibility method to wait
until the newly created window is displayed. But be careful:
wait_visibility processes events, unlike update_idletasks that does
just a GUI refresh. There is apparently no simple way of updating the
display of newly created toplevel's on Windows without returning full
control to the GUI (at least with tk/Tkinter)

I never saw the problem you describe (some windows refreshing, some
not), but I mainly develop on Linux, which may show a different
behaviour than the platform you're working on (which BTW you don't
mention...). Maybe you can post a small piece of code showing the
problem?

HTH
Ah. I had a hunch that might be the case. However, that is not the
behavior that I am seeing. First, in response to your questions: (1) I
am on Linux (there are other platforms?); (2) I am using Python 2.3;
(3) it is difficult to extract a piece of the code, but I will attempt
to describe more clearly what I am doing. There is a button with a
handler. In the handler I use popen3 to launch a program that takes a
long time to execute. I monitor its progress in a while loop by
reading a status line that it produces on stderr. The status line
provides information about percentage complete; I use that information
to update the MessageBar in my GUI. When the status line indicates
that the process is done, I exit the while loop and return from the
button handler. I tried running update_idletasks on the MessageBar in
the while loop. I tried running it on the root window, even though the
information you provided indicated that it doesn't matter what class
owns the method -- and my experience certainly does not contradict that
statement. In every case, only the MessageBar updates. Well, that's
not entirely true. I also move a tag in a text widget; the previously
and newly tagged text redraws. The only technique I have found that
permits the main window to update is to run most of the handler in a
separate thread. The problem I am having with that approach is that
the MessageBar then flashes in an annoying way (the background seems to
go to white at every update and then gets redrawn to gray -- which
happens only when the while loop is in its own thread). Any other
thoughts would be much appreciated.
 
K

klappnase

Jeffrey Barish said:
Ah. I had a hunch that might be the case. However, that is not the
behavior that I am seeing. First, in response to your questions: (1) I
am on Linux (there are other platforms?); (2) I am using Python 2.3;
(3) it is difficult to extract a piece of the code, but I will attempt
to describe more clearly what I am doing. There is a button with a
handler. In the handler I use popen3 to launch a program that takes a
long time to execute. I monitor its progress in a while loop by
reading a status line that it produces on stderr. The status line
provides information about percentage complete; I use that information
to update the MessageBar in my GUI. When the status line indicates
that the process is done, I exit the while loop and return from the
button handler. I tried running update_idletasks on the MessageBar in
the while loop. I tried running it on the root window, even though the
information you provided indicated that it doesn't matter what class
owns the method -- and my experience certainly does not contradict that
statement. In every case, only the MessageBar updates. Well, that's
not entirely true. I also move a tag in a text widget; the previously
and newly tagged text redraws. The only technique I have found that
permits the main window to update is to run most of the handler in a
separate thread. The problem I am having with that approach is that
the MessageBar then flashes in an annoying way (the background seems to
go to white at every update and then gets redrawn to gray -- which
happens only when the while loop is in its own thread). Any other
thoughts would be much appreciated.

I've been using a Tkinter filehandler for similar tasks and didn't
have the problems you describe.
Here's a pseudo-code snippet to illustrate what I did:

from Tkinter import *
import os, fcntl, popen2

def button_callback(self, event):
#the function that's bound to the button
#dialog window with progress bar:
self.pw = ProgressWindow.ProgressWindow()
selectedfiles = ' '.join(tracklist)
normalizecmd = 'exec normalize -m ' + selectedfiles
self.pp = popen2.Popen4(normalizecmd)
self.mkfilehandler(self.pp, self.getprogress)

def mkfilehandler(self, popen4object, function):
#creates the filehandler
fileobject = popen4object.fromchild
filedescr = fileobject.fileno()
fcntl.fcntl(filedescr, fcntl.F_SETFL, os.O_NONBLOCK)
tkinter.createfilehandler(fileobject, READABLE, function)

def getprogress(self, fileobject, event_type):
message = self.pp.fromchild.read()
if message == '':
# process has finished
# this could be checked with "if self.pp.poll() != -1:" either
tkinter.deletefilehandler(self.pp.fromchild)
<...clean up stuff here...>
self.pp = None
else:
<...parse the output messages, extract progress values,
etc....>
# update the progress bar (update_idletasks() is called from
it's set() method):
self.pw.set(value=progress)

I hope this helps

Michael
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,049
Latest member
Allen00Reed

Latest Threads

Top