Section 2.7. Step 5: Adding a GUI


2.7. Step 5: Adding a GUI

The console-based interface approach of the preceding section works, and it may be sufficient for some users assuming that they are comfortable with typing commands in a console window. With just a little extra work, though, we can add a GUI that is more modern, easier to use and less error prone, and arguably sexier.

2.7.1. GUI Basics

As we'll see later in this book, a variety of GUI toolkits and builders are available for Python programmers: Tkinter, wxPython, PyQt, PythonCard, Dabo, and more. Of these, Tkinter ships with Python, and it is something of a de facto standard.

Tkinter is a lightweight toolkit and so meshes well with a scripting language such as Python; it's easy to do basic things with Tkinter, and it's straightforward to do more advanced things with extensions and OOP-based code. As an added bonus, Tkinter GUIs are portable across Windows, Linux/Unix, and Macintosh; simply copy the source code to the machine on which you wish to use your GUI.

Because Tkinter is designed for scripting, coding GUIs with it is straightforward. We'll study all of its concepts and tools later in this book. But as a first example, the first program in Tkinter is just a few lines of code, as shown in Example 2-23.

Example 2-23. PP3E\Preview\tkinter001.py

 from Tkinter import * Label(text='Spam').pack( ) mainloop( ) 

This isn't the most useful GUI ever coded, but it demonstrates Tkinter basics and it builds the fully functional window shown in Figure 2-1 in just three simple lines of code. From the Tkinter module, we get widget (screen device) construction calls such as Label, geometry manager methods such as pack, widget configuration constants such as TOP and RIGHT side hints for pack, and the mainloop call, which starts event processing.

Figure 2-1. tkinter001.py window


You can launch this example in IDLE from a console command line by clicking its icon the same way you can run other Python scripts. Tkinter itself is a standard part of Python and works out-of-the-box on Windows, though you may need to install extras on some computers (more details later in this book).

It's not much more work to code a GUI that actually responds to a user: Example 2-24 implements a GUI with a button that runs the reply function each time it is pressed.

Example 2-24. PP3E\Preview\ tkinter101.py

 from Tkinter import * from tkMessageBox import showinfo def reply( ):     showinfo(title='popup', message='Button pressed!') window = Tk( ) button = Button(window, text='press', command=reply) button.pack( ) window.mainloop( ) 

This example still isn't very sophisticatedit creates an explicit Tk main window for the application to serve as the parent container of the button, and it builds the simple window shown in Figure 2-2 (in Tkinter, containers are passed in as the first argument when making a new widget; they default to the main window). But this time, each time you click the "press" button, the program responds by running Python code that pops up the dialog window in Figure 2-3.

Figure 2-2. tkinter101.py main window


Figure 2-3. tkinter101.py common dialog pop up


Notice how the pop-up dialog looks like it should for Windows, the platform on which this screenshot was taken; Tkinter gives us a native look and feel that is appropriate for the machine on which it is running. We can customize this GUI in many ways (e.g., by changing colors and fonts, setting window titles and icons, using photos on buttons instead of text), but part of the power of Tkinter is that we need to set only the options we are interested in tailoring.

2.7.2. Using OOP for GUIs

All of our GUI examples so far have been top-level script code with a function for handling events. In larger programs, it is often more useful to code a GUI as a subclass of the Tkinter Frame widgeta container for other widgets. Example 2-25 shows our single-button GUI recoded in this way as a class.

Example 2-25. PP3E\Preview\tkinter102.py

 from Tkinter import * from tkMessageBox import showinfo class MyGui(Frame):     def _ _init_ _(self, parent=None):         Frame._ _init_ _(self, parent)         button = Button(self, text='press', command=self.reply)         button.pack( )     def reply(self):         showinfo(title='popup', message='Button pressed!') if _ _name_ _ == '_ _main_ _':     window = MyGui( )     window.pack( )     window.mainloop( ) 

The button's event handler is a bound methodself.reply, an object that remembers both self and reply when later called. This example generates the same window and pop up as Example 2-24 (Figures 2-2 and 2-3); but because it is now a subclass of Frame, it automatically becomes an attachable componenti.e., we can add all of the widgets this class creates, as a package, to any other GUI, just by attaching this Frame to the GUI. Example 2-26 shows how.

Example 2-26. PP3E\Preview\attachgui.py

 from Tkinter import * from tkinter102 import MyGui # main app window mainwin = Tk( ) Label(mainwin, text=_ _name_ _).pack( ) # popup window popup = Toplevel( ) Label(popup, text='Attach').pack(side=LEFT) MyGui(popup).pack(side=RIGHT)                   # attach my frame mainwin.mainloop( ) 

This example attaches our one-button GUI to a larger window, here a Toplevel pop-up window created by the importing application and passed into the construction call as the explicit parent (you will also get a Tk main window; as we'll learn later, you always do, whether it is made explicit in your code or not). Our one-button widget package is attached to the right side of its container this time. If you run this live, you'll get the scene captured in Figure 2-4; the "press" button is our attached custom Frame.

Figure 2-4. Attaching GUIs


Moreover, because MyGui is coded as a class, the GUI can be customized by the usual inheritance mechanism; simply define a subclass that replaces the parts that differ. The reply method, for example, can be customized this way to do something unique, as demonstrated in Example 2-27.

Example 2-27. PP3E\Preview\customizegui.py

 from tkMessageBox import showinfo from tkinter102 import MyGui class CustomGui(MyGui):                            # inherit init     def reply(self):                               # replace reply         showinfo(title='popup', message='Ouch!') if _ _name_ _ == '_ _main_ _':     CustomGui().pack( )     mainloop( ) 

When run, this script creates the same main window and button as the original MyGui class. But pressing its button generates a different reply, as shown in Figure 2-5, because the custom version of the reply method runs.

Figure 2-5. Customizing GUIs


Although these are still small GUIs, they illustrate some fairly large ideas. As we'll see later in the book, using OOP like this for inheritance and attachment allows us to reuse packages of widgets in other programscalculators, text editors, and the like can be customized and added as components to other GUIs easily if they are classes.

2.7.3. Getting Input from a User

As a final introductory script, Example 2-28 shows how to input data from the user in an EnTRy widget and display it in a pop-up dialog. The lambda it uses defers the call to the reply function so that inputs can be passed ina common Tkinter coding pattern (we could also use ent as a global variable within reply, but that makes it less general). This example also demonstrates how to change the icon and title of a top-level window; here, the window icon file is located in the same directory as the script.

Example 2-28. PP3E\Preview\tkinter103.py

 from Tkinter import * from tkMessageBox import showinfo def reply(name):     showinfo(title='Reply', message='Hello %s!' % name) top = Tk( ) top.title('Echo') top.iconbitmap('py-blue-trans-out.ico') Label(top, text="Enter your name:").pack(side=TOP) ent = Entry(top) ent.pack(side=TOP) btn = Button(top, text="Submit", command=(lambda: reply(ent.get( )))) btn.pack(side=LEFT) top.mainloop( ) 

As is, this example is just three widgets attached to the Tk main top-level window; later we'll learn how to use nested Frame container widgets in a window like this to achieve a variety of layouts for its three widgets. Figure 2-6 gives the resulting main and pop-up windows after the Submit button is pressed (shown here running on a different Windows machine). We'll see something very similar later in this chapter, but rendered in a web browser with HTML.

Figure 2-6. Fetching input from a user


The code we've seen so far demonstrates many of the core concepts in GUI programming, but Tkinter is much more powerful than these examples imply. There are more than 20 widgets in Tkinter and many more ways to input data from a user, including multiple-line text, drawing canvases, pull-down menus, radio and check-buttons, scroll bars, as well as other layout and event handling mechanisms. Beyond Tkinter itself, extensions such as the open source PMW and Tix libraries add additional widgets we can use in our Python Tkinter GUIs and provide an even more professional look and feel. To hint at what is to come, let's put Tkinter to work on our database of people.

2.7.4. A GUI Shelve Interface

For our database application, the first thing we probably want is a GUI for viewing the stored dataa form with field names and valuesand a way to fetch records by key. It would also be useful to be able to update a record with new field values given its key and to add new records from scratch by filling out the form. To keep this simple, we'll use a single GUI for all of these tasks. Figure 2-7 shows the window we are going to code as it looks in Windows; the record for the key sue has been fetched and displayed. This record is really an instance of our class in our shelve file, but the user doesn't need to care.

Figure 2-7. peoplegui.py main display/input window


2.7.4.1. Coding the GUI

Also, to keep this simple, we'll assume that all records in the database have the same sets of fields. It would be a minor extension to generalize this for any set of fields (and come up with a general form GUI constructor tool in the process, such as this book's PyForm example), but we'll defer such evolutions to later in this book. Example 2-29 implements the GUI shown in Figure 2-7.

Example 2-29. PP3E\Preview\peoplegui.py

 ############################################################################ # implement a GUI for viewing/updating class instances stored in a shelve; # the shelve lives on machine this script runs on, as 1 or more local files ############################################################################ from Tkinter import * from tkMessageBox import showerror import shelve shelvename = 'class-shelve' fieldnames = ('name', 'age', 'job', 'pay') def makeWidgets( ):     global entries     window = Tk( )     window.title('People Shelve')     form   = Frame(window)     labels = Frame(form)     values = Frame(form)     labels.pack(side=LEFT)     values.pack(side=RIGHT)     form.pack( )     entries = {}     for label in ('key',) + fieldnames:         Label(labels, text=label).pack( )         ent = Entry(values)         ent.pack( )         entries[label] = ent     Button(window, text="Fetch",  command=fetchRecord).pack(side=LEFT)     Button(window, text="Update", command=updateRecord).pack(side=LEFT)     Button(window, text="Quit",   command=window.quit).pack(side=RIGHT)     return window def fetchRecord( ):     key = entries['key'].get( )     try:         record = db[key]                      # fetch by key, show in GUI     except:         showerror(title='Error', message='No such key!')     else:         for field in fieldnames:             entries[field].delete(0, END)             entries[field].insert(0, repr(getattr(record, field))) def updateRecord( ):     key = entries['key'].get( )     if key in db.keys( ):         record = db[key]                      # update existing record     else:         from person import Person             # make/store new one for key         record = Person(name='?', age='?')    # eval: strings must be quoted     for field in fieldnames:         setattr(record, field, eval(entries[field].get( )))     db[key] = record db = shelve.open(shelvename) window = makeWidgets( ) window.mainloop( ) db.close( ) # back here after quit or window close 

Notice how the end of this script opens the shelve as a global variable and starts the GUI; the shelve remains open for the lifespan of the GUI (mainloop returns only after the main window is closed). As we'll see in the next section, this state retention is very different from the web model, where each interaction is normally a standalone program. Also notice that the use of global variables makes this code simple but unusable outside the context of our database; more on this later.

2.7.4.2. Using the GUI

The GUI we're building is fairly basic, but it provides a view on the shelve file and allows us to browse and update the file without typing any code. To fetch a record from the shelve and display it on the GUI, type its key into the GUI's "key" field and click Fetch. To change a record, type into its input fields after fetching it and click Update; the values in the GUI will be written to the record in the database. And to add a new record, fill out all of the GUI's fields with new values and click Updatethe new record will be added to the shelve file using the key and field inputs you provide.

In other words, the GUI's fields are used for both display and input. Figure 2-8 shows the scene after adding a new record (via Update), and Figure 2-9 shows an error dialog pop up issued when users try to fetch a key that isn't present in the shelve.

Figure 2-8. peoplegui.py after adding a new persistent object


Figure 2-9. peoplegui.py common error dialog pop up


Notice how we're using repr( ) again to display field values fetched from the shelve and eval( ) to convert field values to Python objects before they are stored in the shelve. As mentioned previously, this is potentially dangerous if someone sneaks some malicious code into our shelve, but we'll finesse such concerns for now.

Keep in mind, though, that this scheme means that strings must be quoted in input fields other than the keythey are assumed to be Python code. In fact, you could type an arbitrary Python expression in an input field to specify a value for an update. (Typing "Tom"*3 in the name field, for instance, would set the name to TomTomTom after an update, though this was not by design! Fetch to see the result.)

Even though we now have a GUI for browsing and changing records, we can still check our work by interactively opening and inspecting the shelve file or by running scripts such as the dump utility in Example 2-19. Remember, despite the fact that we're now viewing records in a GUI's windows, the database is a Python shelve file containing native Python class instance objects, so any Python code can access it. Here is the dump script at work after adding and changing a few persistent objects in the GUI:

 ...\PP3E\Preview> python dump_db_class.py tom =>    Tom Doe 90000 peg =>    1 4 tomtom =>    Tom Tom 40000 bob =>    Bob Smith 30000 sue =>    Sue Jones 40000 bill =>    bill 9999 nobody =>    John Doh None Smith Doe 

2.7.4.3. Future directions

Although this GUI does the job, there is plenty of room for improvement:

  • As coded, this GUI is a simple set of functions that share the global list of input fields (entries) and a global shelve (db). We might instead pass these two objects in as function arguments using the lambda TRick of the prior section; though not crucial in a script this small, as a rule of thumb, making your external dependencies explicit makes your code both easier to understand and reusable in other contexts.

  • We could also structure this GUI as a class to support attachment and customization, though it's unlikely that we'll need to reuse such a specific GUI (but see peoplegui_class.py in the book examples directory for a start).

  • More usefully, we could pass in the fieldnames tuple as an input parameter to the functions here to allow them to be used for other record types in the future. Code at the bottom of the file would similarly become a function with a passed-in shelve filename, and we would also need to pass in a new record construction call to the update function because Person could not be hardcoded. (Such generalization is beyond the scope of this preview, but see people_general.py in the book examples directory for a first implementation and the PyForm program later in this book for a more general approach.)

  • To make this GUI more user friendly, it might also be nice to add an index window that displays all the keys in the database in order to make browsing easier. Some sort of verification before updates might be useful as well, and Delete and Clear buttons would be simple to code. Furthermore, assuming that inputs are Python code may be more bother than it is worth; a simpler input scheme might be easier and safer.

  • We could also support window resizing (as we'll learn, widgets can grow and shrink with the window) and provide an interface for calling class methods (as is, the pay field can be updated, but there is no way to invoke the giveRaise method).

  • If we plan to distribute this GUI widely, we might package it up as a standalone executable programa frozen binary in Python terminologyusing third-party tools such as Py2Exe, Installer, and Freeze (search the Web for pointers). Such a program can be run directly without installing Python on the receiving end.

We'll leave all such extensions as suggested exercises and revisit some of them later in this book.

Before we move on, two notes. First, I should mention that even more graphical packages are available to Python programmers. For instance, if you need to do graphics beyond basic windows, the Tkinter Canvas widget supports freeform graphics. Third-party extensions such as Blender, OpenGL, VPython, PIL, VTK, and PyGame provide even more advanced graphics, visualization, and animation tools for use in Python scripts. Moreover, the PMW and Tix widget kits mentioned earlier extend Tkinter itself. Try the Vaults of Parnassus, PyPI, and Google for third-party graphics extensions.

And in deference to fans of other GUI toolkits such as wxPython and PyQt, I should also note that there are other GUI options to choose from and that choice is sometimes very subjective. Tkinter is shown here because it is mature, robust, fully open source, well documented, well supported, lightweight, and a standard part of Python. By most accounts, it remains the standard for building portable GUIs in Python.

Other GUI toolkits for Python have pros and cons of their own, discussed later in this book. For example, some exchange simplicity for richer widget sets. By and large, though, they are variations on a themeonce you've learned one GUI toolkit, others are easy to pick up. Because of that, we'll focus fully on learning one toolkit in its entirety in this book instead of sampling many partially. Some consider web pages to be a kind of GUI as well, but you'll have to read the next and final section of this chapter to judge that for yourself.




Programming Python
Programming Python
ISBN: 0596009259
EAN: 2147483647
Year: 2004
Pages: 270
Authors: Mark Lutz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net