Sometimes, GUIs pop up quite unexpectedly. Perhaps you haven’t learned GUI programming yet; or perhaps you’re just pining for non-event-driven days past. But for whatever reason, you may have written a program to interact with a user in an interactive console, only to decide later that interaction in a real GUI would be much nicer. What to do?
Probably the real answer to converting a non-GUI program is to
truly convert it—restructure it to initialize widgets on startup, call
mainloop
once to start event
processing and display the main window, and move all program logic
into callback functions triggered by user actions. Your original
program’s actions become event handlers, and your original main flow
of control becomes a program that builds a main window, calls the
GUI’s event loop once, and waits.
This is the traditional way to structure a GUI program, and it
makes for a coherent user experience; windows pop up on request,
instead of showing up at seemingly random times. Until you’re ready to
bite the bullet and perform such a structural conversion, though,
there are other possibilities. For example, in the ShellGui
section earlier in this chapter, we
saw how to add windows to file packing scripts to collect inputs; we
also saw how to redirect their outputs to text widgets. This approach
works if the non-GUI operation we’re wrapping up in a GUI is a single
operation; for more dynamic user interaction, other techniques might
be needed.
It’s possible, for instance, to launch GUI windows from a
non-GUI main program, by calling the Tkinter mainloop
each time a window must be
displayed. It’s also possible to take a more grandiose approach and
add a completely separate program for the GUI portion of your
application. To wrap up this chapter, let’s briefly explore each
scheme.
If you just want to add a simple GUI user interaction
to an existing non-GUI script (e.g., to select files to open or
save), it is possible to do so by configuring widgets and calling
mainloop
from the non-GUI main
program when you need to interact with the user. This essentially
makes the program GUI capable, but without a persistent main window.
The trick is that mainloop
doesn’t return until the GUI main window is closed by the user (or
quit
method calls), so you cannot
retrieve user inputs from widgets after mainloop
returns. To work around this, all
you have to do is be sure to save user inputs in a Python object:
the object lives on, after the GUI is destroyed. Example 11-18 shows one way to
code this idea in Python.
Example 11-18. PP3EGuiToolsmainloopdemo.py
############################################################################# # demo running two distinct mainloop calls; # each returns after the main window is closed; save user results on # Python object: GUI is gone; GUIs normally configure widgets and then # run just one mainloop, and have all their logic in callbacks; this # demo uses mainloop calls to implement two modal user interactions # from a non-GUI main program; it shows one way to add a GUI component # to an existing non-GUI script, without restructuring code; ############################################################################# from Tkinter import * from tkFileDialog import askopenfilename, asksaveasfilename class Demo(Frame): def _ _init_ _(self,parent=None): Frame._ _init_ _(self,parent) self.pack( ) Label(self, text ="Basic demos").pack( ) Button(self, text='open', command=self.openfile).pack(fill=BOTH) Button(self, text='save', command=self.savefile).pack(fill=BOTH) self.open_name = self.save_name = "" def openfile(self): # save user results self.open_name = askopenfilename( ) # use dialog options here def savefile(self): self.save_name = asksaveasfilename(initialdir='D:\temp') if _ _name_ _ == "_ _main_ _": # display window once print 'popup1...' mydialog = Demo() # attaches Frame to default Tk( ) mydialog.mainloop( ) # display; returns after windows closed print mydialog.open_name # names still on object, though GUI gone print mydialog.save_name # Non GUI section of the program uses mydialog here # display window again print 'popup2...' mydialog = Demo( ) # re-create widgets again mydialog.mainloop( ) # window pops up again print mydialog.open_name # new values on the object again print mydialog.save_name # Non GUI section of the program uses mydialog again print 'ending...'
This program twice builds and displays a simple two-button main window that launches file selection dialogs, shown in Figure 11-13. Its output, printed as the GUI windows are closed, looks like this:
popup1... C:/Python23/python.exe D:/temp/new.txt popup2... C:/Python23/dir1/_ _init_ _.py D:/temp/public_html/calendar.html ending...
Notice how this program calls mainloop
twice, to implement two modal
user interactions from an otherwise non-GUI script. It’s OK to call
mainloop
more than once, but this
script takes care to re-create the GUI’s widgets before each call
because they are destroyed when the previous mainloop
call exits (widgets are destroyed
internally inside Tk, even though the corresponding Python dialog
object still exists). Again, this can make for an odd user
experience compared to a traditional GUI program structure—windows
seem to pop up from nowhere—but it’s a quick way to put a GUI face
on a script without reworking its code.
Note that this is different from using nested mainloop
calls to implement modal dialogs,
as we did in Chapter 9. In that
mode, the nested mainloop
call
returns when the dialog’s quit
method is called, but we return to the enclosing mainloop
layer and remain in the realm of
event-driven programming. Example 11-18 instead runs
mainloop
two different times,
stepping into and out of the event-driven model twice.
Finally, note that this scheme works only if you don’t have to
run any non-GUI code while the GUI is open, because your script is
inactive and blocked while mainloop
runs. You cannot, for example,
apply this technique to use utilities like those in the guiStreams
module we met earlier in this
chapter to route user interaction from non-GUI code to GUI windows.
The GuiInput
and GuiOutput
classes in that example assume
that there is a mainloop
call
running somewhere (they’re GUI based, after all). But once you call
mainloop
to pop up these windows,
you can’t return to your non-GUI code to interact with the user
until the GUI is closed and the mainloop
call returns. The net effect is
that these classes can be used only in the context of a fully GUI
program.
But really, this is an artificial way to use Tkinter. Example 11-18 works only
because the GUI can interact with the user independently, while the
mainloop
call runs; the script is
able to surrender control to the Tkinter mainloop
call and wait for results. That
scheme won’t work if you must run any non-GUI code while the GUI is
open. Because of such constraints, you will generally need a
main-window-plus-callbacks model in most GUI programs—callback code
runs in response to user interaction while the GUI remains open.
That way, your code can run while GUI windows are active. For an
example, see earlier in this chapter for the way the non-GUI pack
and unpack scripts were run from a GUI so that their results appear
in a GUI; technically, these scripts are run in a GUI callback
handler so that their output can be routed to a widget.
As mentioned earlier, it’s also possible to spawn the
GUI part of your application as a completely separate program. This
is a more advanced technique, but it can make integration simple for
some applications because of the loose coupling it implies. It can,
for instance, help with the guiStreams
issues of the prior section, as
long as inputs and outputs are communicated to the GUI over
Inter-Process Communication (IPC) mechanisms, and the widget
after
method (or similar) is used
by the GUI program to detect incoming output to be displayed. The
non-GUI script would not be blocked by a mainloop
call.
More generally, the GUI could be spawned by the non-GUI script
as a separate program, where user interaction results can be
communicated from the spawned GUI to the script using pipes,
sockets, files, or other IPC mechanisms. The advantage to this
approach is that it provides a separation of GUI and non-GUI
code—the non-GUI script would have to be modified only to spawn and
wait for user results to appear from the separate GUI program, but
could otherwise be used as is. Moreover, the non-GUI script would
not be blocked while an in-process mainloop
call runs (only the GUI process
would run a mainloop
), and the
GUI program could persist after the point at which user inputs are
required by the script, leading to fewer pop-up windows.
Examples 11-19 and 11-20 provide a simplistic example of this technique in action. They represent non-GUI and GUI programs that communicate over sockets—an IPC and networking device we will explore in the next part of the book. The important point to notice here is the way the programs are linked. When Example 11-19 starts, it spawns the GUI as a separate program; when it prints to standard output, the printed text is sent over a socket connection to the GUI program. Other than the startup and socket creation calls, the non-GUI program knows nothing at all about GUIs.
Example 11-19. PP3EGuiToolssocket-nongui.py
import time, sys from socket import * # GUI interface port = 50008 # I am client: use GUI server port host = 'localhost' # or start me after GUI started sock = socket(AF_INET, SOCK_STREAM) sock.connect((host, port)) file = sock.makefile('w', 0) # file interface wrapper, unbuffered sys.stdout = file # make prints go to sock.send # non-GUI code while 1: # print data to stdout print time.asctime( ) # sent to GUI process time.sleep(2.0)
The GUI part of this exchange is the program in Example 11-20. This script
implements a GUI to display the text printed by the non-GUI program,
but it knows nothing of that other program’s logic. For the display,
the GUI program prints to the stream redirection object we met
earlier in this chapter; because this program runs a GUI mainloop
call, this just works. We’re also
running a timer loop here to detect incoming data on the socket as
it arrives, instead of waiting for the non-GUI program to run to
completion. Because the socket is set to be nonblocking, input calls
don’t wait for data to appear, and hence, do not block the
GUI.
Example 11-20. PP3EGuiToolssocket-gui.py
import sys, os from socket import * from Tkinter import Tk from PP3E.Gui.Tools.guiStreams import GuiOutput myport = 50008 sockobj = socket(AF_INET, SOCK_STREAM) # GUI is server, script is client sockobj.bind(('', myport)) # config server before client sockobj.listen(5) os.startfile('socket-nongui.py') # spawn non-GUI on Windows (os.popen) conn, addr = sockobj.accept( ) # wait for client to connect sockobj.setblocking(0) # use nonblocking socket def checkdata( ): try: print conn.recv(1024), # if ready, show text in GUI window except error: # raises socket.error if not ready pass # or message to sys._ _stdout_ _ root.after(1000, checkdata) # check once per second root = Tk( ) sys.stdout = GuiOutput(root) # socket text is displayed on this checkdata( ) root.mainloop( )
When both the GUI and the non-GUI processes are running, the
GUI picks up a new message over the socket roughly once every two
seconds and displays it in the window shown in Figure 11-14. The GUI’s timer
loop checks for data once per second, but the non-GUI script sends a
message every two seconds only due to its time.sleep
calls.
To run this by yourself, start the GUI script—it spawns the
non-GUI script and displays a pop-up window that shows the text
printed in the socket-nongui
script (the date and time). The non-GUI script can keep running
linear, procedural code to produce data, because only the socket-GUI
program runs an event-driven
mainloop
call.
Although we aren’t going to get into enough socket details in this chapter to show how, this example should probably be augmented to detect and handle an end-of-file signal from the spawned program, and then terminate its timer loop. The non-GUI script could also start the GUI instead, but in the socket world, the server’s end (the GUI) must be configured to accept connections before the client (the non-GUI) can connect. One way or another, the GUI has to start before the non-GUI connects to it or the non-GUI script will be denied a connection and will fail.
The socket client/server model works well and is a general approach to connecting GUI and non-GUI code, but there are a few coding alternatives worth exploring in the next section before we move on.
The net effect of the two programs of the preceding
section is similar to a GUI program reading the output of a shell
command over a pipe file with os.popen
; but as we’ll see later, sockets
can also link programs running on remote machines across a network.
Perhaps subtler and more significant is the fact that without an
after
timer loop and nonblocking
input sources, the GUI may become stuck and unresponsive while
waiting for data from the non-GUI program and may not be able to
handle more than one data stream.
For instance, consider the guiStreams
call we wrote in Example 11-9 to redirect the
output of a shell command spawned with os.popen
to a GUI window. We could use
this with simplistic code like that in Example 11-21 to capture the
output of a spawned Python program and display it in a separately
running GUI program’s window.
Notice the -u
Python
command-line flag used here: it forces the spawned program’s
standard streams to be unbuffered, so we get printed text immediately as it is produced,
instead of waiting for the spawned program to completely finish. We
talked about this option in Chapter
5, when discussing deadlocks and pipes. Recall that print
writes to sys.stdout
, which is normally buffered. If
we don’t use the -u
flag here and
the spawned program doesn’t manually call sys.stdout.flush( )
, we won’t see any
output in the GUI until the spawned program exits or until its
buffers fill up. If the spawned program is a perpetual loop that
does not exit, we may be waiting a long time for output to appear on
the pipe, and hence, in the GUI.
This approach makes the non-GUI code in Example 11-22 much simpler: it just writes to standard output as usual, and it need not be concerned with creating a socket interface.
Example 11-22. PP3EGuiToolspipes-nongui.py
import time while 1: # non-GUI code print time.asctime( ) # sends to GUI process time.sleep(2.0)
Start the GUI script in Example 11-21: it launches the
non-GUI program automatically. This works, but the GUI is odd—we
never call mainloop
ourselves,
and we get a default empty top-level window. (In fact, it apparently
works at all only because the Tkinter update
call issued within the redirect
function enters the Tk event loop momentarily to process pending
events.) To do better, Example
11-23 creates an enclosing GUI and kicks off an event loop
manually by the time the shell command is spawned.
Example 11-23. PP3EGuiToolspipes-gui2.py
from Tkinter import * from PP3E.Gui.Tools.guiStreams import redirectedGuiShellCmd def launch( ): redirectedGuiShellCmd('python -u pipes-nongui.py') window = Tk( ) Button(window, text='GO!', command=launch).pack( ) window.mainloop( )
The -u
unbuffered flag is
crucial here—without it, you won’t see the text output window. The
GUI will be blocked in the initial pipe input call indefinitely
because the spawned program’s standard output will be queued up in
an in-memory buffer.
Either way we code this, however, when the GUIs are run they
become unresponsive for two seconds at a time while they read data
from the os.popen
pipe—window
moves, resizes, redraws, raises, and so on, are delayed for up to
two seconds, until the non-GUI program sends data to the GUI to make
the pipe read call return. Worse, if you press the “GO!” button
twice in the second version of the GUI, only one window updates
itself every two seconds, because the GUI is stuck in the second
button press callback—it never exits the loop that reads from the
pipe until the spawned non-GUI program exits.
Because of such constraints, a separately spawned GUI must
generally read a portion of the data at a time to avoid blocking.
For instance, in the socket-based scripts of the prior section
(Example 11-20), the
after
timer loop allows the GUI
to poll for data instead of waiting and display it as it
arrives.
Of course, the real issue here is that the guiStreams
utility is too simplistic;
issuing a read call within a GUI is generally prone to blocking. We
could try to run the redirect call in a thread—for example, by
changing the launch
function in
Example 11-23 as
follows:
def launch( ): import thread thread.start_new(redirectedGuiShellCmd, ('python -u pipes-nongui.py',))
But then we would be updating the GUI from a spawned thread, which, as we’ve learned, is a generally bad idea. With this change, the GUI hangs on Windows on the first “GO!” button press occasionally, and always hangs eventually if you press the button enough times (in fact, the process must be forcibly killed after it hangs). When it does run, it doesn’t help—the text window created in the child thread is still stuck in a read call.
Alternatively, we could try to use the Python select.select
call (described in Chapter 13) to implement polling for
data on the input pipe; unfortunately, select
works only on sockets in Windows
today (it also works on pipes and other file descriptors in
Unix).
In other contexts, a separately spawned GUI might also use
signals to inform the non-GUI program when points of interaction
arise, and vice versa (the Python signal
module and os.kill
call were introduced in Chapter 5). The downside with this
approach is that it still requires changes to the non-GUI program to
handle the signals.
Named pipes (the fifo files introduced in Chapter 5) are sometimes an
alternative to the socket calls of the original Examples 11-19 and 11-20, but sockets work on
Windows, and fifos do not (os.mkfifo
is not available in Windows XP
in 2.4, though it is in Cygwin Python). Even where they do work, we
would still need an after
timer
loop in the GUI to avoid blocking.
We might also use Tkinter’s createfilehandler
to register a callback
to be run when input shows up on the input pipe:
def callback(file, mask): ...read from file here... import _tkinter, Tkinter _tkinter.createfilehandler(file, Tkinter.READABLE, callback)
The file handler creation call is also available within
Tkinter.tkinter
and as a method
of a Tk
instance object.
Unfortunately again, as noted at the end of Chapter 10, this call is not
available on Windows and is a Unix-only alternative.
More generally, the GUI process might spawn a thread that reads the socket or pipe and places the data on a queue so that more than one data stream or long-running activity can overlap in time. In fact, the thread techniques we met earlier in this chapter could be used directly in such a role.
Example 11-24
shows how. The main trick this script employs is to split up the
input and output parts of the original redirectedGuiShellCmd
of the guiStreams
module we met earlier in Example 11-9. By so doing, the
input portion can be spawned off in a parallel thread and not block
the GUI. The main GUI thread uses an after
timer loop as usual, to watch for
data to be added by the reader thread to a shared queue. Because the
main thread doesn’t read program output itself, it does not get
stuck in wait states.
Example 11-24. PP3EGuiToolspipes_gui3.py
import thread, Queue, os from Tkinter import Tk from PP3E.Gui.Tools.guiStreams import GuiOutput stdoutQueue = Queue.Queue( ) # infinite size def producer(input): while True: line = input.readline( ) # OK to block: child thread stdoutQueue.put(line) # empty at end-of-file if not line: break def consumer(output, root, term='<end>'): try: line = stdoutQueue.get(block=False) # main thread: check queue except Queue.Empty: # 4 times/sec, OK if empty pass else: if not line: # stop loop at end-of-file output.write(term) # else display next line return output.write(line) root.after(250, lambda: consumer(output, root, term)) def redirectedGuiShellCmd(command, root): input = os.popen(command, 'r') # start non-GUI program output = GuiOutput(root) thread.start_new_thread(producer, (input,)) # start reader thread consumer(output, root) if _ _name_ _ == '_ _main_ _': win = Tk( ) redirectedGuiShellCmd('python -u pipes-nongui.py ', win) win.mainloop( )
As usual, we use a queue here to avoid updating the GUI except
in the main thread. Note that we didn’t need a thread or queue in
the prior section’s socket example, just because we’re able to poll
a socket to see whether it has data without blocking; an after
timer loop was enough. For a
shell-command pipe, though, a thread is an easy way to avoid
blocking.
When run, this program’s self-test code creates a ScrolledText
window that displays the
current date and time sent from the
pipes-nongui.py script in Example 11-22 (its window is
identical to Figure
11-14). The window is updated with a new line every two
seconds because that’s how often the spawned pipes-nongui
script prints a message to
stdout
.
Note how the producer thread calls readline( )
to load just one line at a
time. We can’t use input calls that consume the entire stream all at
once (e.g., read( )
, readlines( )
), because such calls would
not return until the program exits and sends end-of-file. The
read(N)
call would work to grab
one piece of the output as well, but we assume that the output
stream is text here. Also notice that the -u
unbuffered stream flag is used here
again, to get output as it is produced; without it, output won’t
show up in the GUI at all because it is buffered in the spawned
program (try it yourself).
This is similar in spirit to what we did in Example 11-23. Due to the way its code is structured, though, Example 11-24 has two major advantages:
Because input calls are spawned off in a thread this time,
the GUI is completely responsive. Window moves, resizes, and so
forth, happen immediately because the GUI is not blocked while
waiting for output from the non-GUI program. Although it is more
complex and requires thread support, its lack of blocking makes
this redirectedGuiShellCmd
much more generally useful than the original version we coded
earlier.
Moreover, because this GUI reads the spawned program’s standard output, no changes are required in the non-GUI program. Unlike the socket-based example in the prior section, the non-GUI program here needs no knowledge of the GUI that will display its results.
The only downside to this approach compared to the sockets of the prior section is that it does not directly support running the GUI and non-GUI programs on remote machines. As we’ll see later, sockets allow data to be passed between programs running on the same machine or across networks. Furthermore, if the GUI must do more than display another program’s output, sockets become a more general solution—as we’ll learn later, because sockets are bidirectional data streams, they allow data to be passed back and forth between two programs in more arbitrary ways.
Here’s another use case: the following code imports the new
GUI redirection function as a library component and uses it to
create a window that displays four lines of successively longer
strings, followed by a final line containing <end>
, reflecting the spawned
program’s exit:
>>> print open('spams.py').read( ) import time for i in range(1, 5): time.sleep(2) print 'spam' * i >>> from Tkinter import * >>> from pipes_gui3 import redirectedGuiShellCmd >>> root = Tk( ) >>> redirectedGuiShellCmd('python -u spams.py', root)
If the spawned program exits, Example 11-24 detects
end-of-file on the pipe and puts the final empty line in the queue;
the consumer thread displays an <end>
line in the GUI by default
when it detects this condition. Here again, the sleep
call in the spawned program
simulates a long-running task, and we really need the -u
unbuffered streams flag—without it, no
output appears in the GUI for eight seconds, until the spawned
program is completely finished. With it, the GUI receives and
displays each line as it is printed, one every two seconds.
This is also, finally, the sort of code you could use to display the output of a non-GUI program in a GUI, without sockets, changes in the original program, or blocking the GUI. Of course, in many cases, if you have to work this hard to add a GUI anyhow, you might as well just make your script a traditional GUI program with a main window and event loop. Furthermore, the GUIs we’ve coded in this section are limited to displaying another program’s output; sometimes the GUI may have to do more. For many programs, though, the general separation of display and program logic provided by the spawned GUI model can be an advantage—it’s easier to understand both parts if they are not mixed together.
We’ll learn all about sockets in the next part of the book, so you should consider parts of this discussion something of a preview. As we’ll see, things start to become more and more interesting when we start combining GUIs, threads, and network sockets. Before we do, though, the next chapter closes out the purely GUI part of this book by applying the widgets and techniques we’ve learned in more realistically scaled programs.
18.226.251.206