Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Python (2005)

.pdf
Скачиваний:
158
Добавлен:
17.08.2013
Размер:
15.78 Mб
Скачать

Writing a GUI with Python

In short, this XML file contains the same information as the GUI we defined visually, and the same information as the several lines of Python we used to define the same GUI in TwoButtonsGUI.py. If there were a way to get Python to parse this file and create a GUI out of it, we could save a significant amount of code. This is where libglade comes in.

Try It Out

Building a GUI from a Glade File

libglade parses the XML file and makes GTK widgets corresponding to the widgets described in the XML file. Here’s GladeTwoButtonsGUI.py, a version of TwoButtonsGUI.py that loads its GUI from the XML file instead of using a series of Python statements:

#!/usr/bin/env python import findgtk import gtk.glade

class TwoButtonsGUI: def __init__(self):

self.window = gtk.glade.XML(‘GladeTwoButtonsGUI.glade’, ‘window1’)

if __name__ == ‘__main__’: TwoButtonsGUI() gtk.main()

How It Works

This program uses libglade to load a set of GUI widgets from the GladeTwoButtonsGUI.glade file we created with Glade. The GUI looks just the same as the Glade mock-up, and the same as we created with pyGTK calls in the TwoButtonsGUI.py program. The advantage over the original TwoButtonsGUI.py is that we had to write a lot less code to get the same GUI.

Glade greatly simplifies the layout of even small GUIs. As you’ll see, it also provides a framework for designating which events a GUI is expected to handle.

You may need to install the Python libglade bindings separately from Glade. If all else fails, you can download the bindings as part of the pygtk package, at http://ftp.gnome.org/pub/GNOME/ sources/pygtk/. See Appendix B for more information.

Creating a Real Glade Application

Of course, the Glade version of our two-button application doesn’t do anything, any more than the version that just used Python code did. In this section, we’ll create a complex GUI, with some signal handlers, for an application called pyRAP. This is a chat-themed GUI that could be used as a client for the Python Chat Server described in Chapter 16.

Create a new Glade project called PyRAP, and create a new window as shown previously. To create a basic GUI, start with a Vertical Box widget, also shown previously. Click the Vertical Box in the widget palette, and then click the crosshatch marks in the new window to place it there. When Glade asks you

231

TEAM LinG

Chapter 13

how many rows you want in your Vertical Box, enter 3 (as opposed to the two-row box created in the previous example).

Put a Menu Bar widget in the top row, and a Status Bar widget in the bottom row. You should now have a GUI that looks a lot like the application interface most people have come to expect, with an empty space in the middle (see Figure 13-14).

Figure 13-14

That empty middle container (note the cross-hatching) is where we’ll put the guts of our application. For starters, we’ll just have pyRAP take the contents of an Input widget and write it to another widget. To do this, we’ll split our central container into two portions with a two-column Horizontal Box, as shown in Figure 13-15.

Figure 13-15

232

TEAM LinG

Writing a GUI with Python

Now we’ve got a window that is divided into three portions by a vertical box. The middle portion of the Vertical Box is itself divided in two by a Horizontal Box. Let’s go one step further and use another Vertical Box to divide the left portion of the Horizontal Box into three sections, as shown in Figure 13-16.

Figure 13-16

That’s enough layout widgets. Now it’s time to place some real widgets. In the Vertical Box you just created, put a Label in the top slot, a Text Entry in the middle slot, and a Button in the bottom slot. Your GUI should now look like the window shown in Figure 13-17.

Figure 13-17

Note that the label and button widgets appear with some rather bland default text. In a little bit, you’ll change that text using the properties sheets for those widgets. Right now, though, let’s fill up the only remaining slot in your GUI with a Text View widget, as shown in Figure 13-18.

233

TEAM LinG

Chapter 13

Figure 13-18

Let’s change that default text. Select the label and the button in turn, and use the property sheet to set their Label properties (see Figure 13-19). As you change the default text, you’ll see the text in the mockup change as well.

Figure 13-19

Now your mock-up should look like the GUI for a real application (see Figure 13-20).

What you’re seeing is a Label, a Text Entry, and a Button on the left side, and a Text View on the right side. GTK supports most of the widgets you can expect from any windowing interface — Combo-Boxes, Spin Buttons for numeric input, and so on. The only difficult part of GTK is understanding and using Trees and Lists and properly designing your application to handle threads. Now you’ve reached the fun part: deciding what to do with this application.

234

TEAM LinG

Writing a GUI with Python

Figure 13-20

Now it’s time to learn how to connect the application to some Python code. Save your Glade project and let’s start writing PyRAP.py, the code for the application that uses it:

#!/usr/bin/env python import time

import findgtk import gtk.glade

class PyRAPGUI:

def __init__(self):

self.wTree = gtk.glade.XML(“PyRAP.glade”, “window1”)

if __name__ == ‘__main__’:

PyRAPGUI()

try:

gtk.threads_init() except:

print “No threading was enabled when you compiled pyGTK!” import sys

sys.exit(1) gtk.threads_enter() gtk.main() gtk.threads_leave()

This code is just a skeleton, and it has the same problem as the earlier Glade example. You can enter text into the Text Enter widget, but clicking the button doesn’t do anything. You need to set up a signal so the program does something when the button is clicked.

Go back into Glade, and select the Send button in your mock-up. Select the Properties View. Add a signal that’s activated when the Send button is clicked by clicking first on the Signals tab, and then on the ellipses (...) button next to the Signal: label (see Figure 13-21). Select the clicked signal and then click Add. When the GUI gets a click on the button, it’ll generate a signal on_button1_clicked for pyGTK to process.

235

TEAM LinG

Chapter 13

Figure 13-21

Click the window1 object in the main screen of Glade to bring focus on the main widget. Next, go to the window’s properties sheet. Carry out the same process as before to add an on_window1_destroy signal for the window widget.

Now let’s redo PyRAP.py to respond to those signals. When you kill the window, the program will exit, as in the previous examples. When you click the Send button, PyRAP will copy to the Text View widget on the right anything you typed into the Text Entry widget on the left:

#!/usr/bin/env python import findgtk import gtk

import time

class PyRAPGUI:

def __init__(self):

self.wTree = gtk.glade.XML (“PyRAP.glade”, “window1”) dic={ “on_window1_destroy” : self.quit,

“on_button1_clicked” : self.send,

}

self.wTree.signal_autoconnect (dic)

self.username=”Bob”

#setup the text view to act as a log window self.logwindowview=self.wTree.get_widget(“textview1”) self.logwindow=gtk.TextBuffer(None) self.logwindowview.set_buffer(self.logwindow)

return

#Handlers for the GUI signals def quit(self,obj):

“Handles the ‘destroy’ signal of the window.” gtk.main_quit()

sys.exit(1)

236

TEAM LinG

Writing a GUI with Python

def send(self,obj):

“Handles the ‘clicked’ signal of the button.” message=self.wTree.get_widget(“entry1”).get_text() print “Message=%s” % message self.log(self.username + “: “ + message, “black”)

def log(self,message,color,enter=”\n”): “””

A helper method for the “send” GUI signal handler:

logs a message to the log window and scrolls the window to the bottom “””

message=message+enter

buffer = self.logwindow iter = buffer.get_end_iter() #gtk versioning avoidance if color != “black”:

tag = buffer.create_tag() tag.set_property(“foreground”, color)

self.logwindow.insert_with_tags(buffer.get_end_iter(), message, tag) else:

self.logwindow.insert(iter, message) #gtk.FALSE and gtk.TRUE on older pyGTK

mark = buffer.create_mark(“end”, buffer.get_end_iter(), False) self.logwindowview.scroll_to_mark(mark,0.05,True,0.0,1.0) #print “Exited log function”

if __name__ == ‘__main__’: PyRAPGUI()

try:

gtk.threads_init() except:

print “No threading was enabled when you compiled pyGTK!” import sys

sys.exit(1) gtk.threads_enter() gtk.main() gtk.threads_leave()

First, we initialize the Text View widget to contain a text buffer. Then we must handle writing into the text buffer and scrolling it down so it always displays the latest message. As a bonus, we also put in some code to display the text in different colors, if desired. We’ll probably use that later. As you can see, with a few widgets and two signals, we’ve created the bare bones of a working GUI for a text messenger system (see Figure 13-22).

Figure 13-22

237

TEAM LinG

Chapter 13

The exciting thing about Glade is that you can go from concept to working demo in a couple of hours. As you revise your program, the GUI can morph completely, without ever affecting your code. Of course, pyRAP is lacking networking code in this example, but that could be fleshed out with either socket calls (to connect to IRC, an instant messaging system, or Chapter 16’s Python Chat server) or a nice XML-RPC client (see Chapter 21).

Advanced Widgets

Not all widgets are as easy to use as simple Entry Boxes or Spin Buttons. As you’ve seen with the text view in the previous demo, some widgets require initialization to use. A large part of this initialization requirement is that the widgets themselves are portals into the data set, not the data set itself. For example, you might have multiple text views, each representing different parts of the same text buffer. Classical GUI design text refers to this as the model, view, controller design. It is hoped that you’ll need to know as little about that as possible.

Now you’re going to add a tree view to the left side of your application, which will contain a server list. The easiest way to do this is to use the Widget Tree (another window you can activate from Glade’s View menu). Select the first object under the hbox1, as shown in Figure 13-23, and insert a new container before it. This will add another column to the horizontal box, to the left of the label and button.

Figure 13-23

The Widget Tree is extremely useful for advanced Glade users. As you modify your application, you can cut and paste various widgets from one container to another, or simply add new containers where you want to, without having to dramatically redo the GUI mock-up. Unfortunately, there is currently no Undo feature in Glade so save when you’re happy with the current state, and then experiment.

Add a Tree View widget to the newly created slot to the left of the other widgets. Your Glade mock-up will now look like the window shown in Figure 13-24.

238

TEAM LinG

Writing a GUI with Python

Figure 13-24

The new GUI you can see in figure 13-25 will work just as well with your existing PyRAP.py code, but the Tree View widget won’t do anything, because there’s no initialization code for it and we haven’t set up any signals for it.

Figure 13-25

Tree Views display data in a columnar format, but as you can see, no columns will show up in your application yet; you need to manually set the column headers. In addition, the figure shows that the author has changed the main window’s title to be “pyRAP,” in anticipation of adding network support and some application logic to enable two applications to communicate with a central server and have a “rap battle” with each other.

To fill in your Tree View, you’ll have to initialize it, much as you did with your text view earlier. Normally, it is wise to split this process off into another file, but in this case, you’ll keep it all together. The following code first creates a model variable that contains a TreeStore model. The model variable knows it is going to take two strings as columns. The insert_row function (further below) is a wrapper to the model.insert_after and model.set_value functions. This code is largely cut-and-paste when designing your own projects.

An important concept in this code, and in the gtk API in general (and many other APIs), is the concept of an iterator. An iterator is simply an object that holds a position within a list or other data structure. In this case, the insert_row function is returning an iterator that holds the position within the tree model into which a row was inserted. Later, we can pass that iterator back into insert_row to insert a row

239

TEAM LinG

Chapter 13

under the “host” line. The following code fragment also sets the TreeView widget to use the new model we created with the API call set_model. Also notice that we’re grabbing the treeview1 widget from wherever it happens to be in the Glade-created GUI. If we move treeview1, this code does not have to change.

Put this code at the end of the __init__ method of your PyRAP class:

#initialize our host tree self.hosttree=self.wTree.get_widget(“treeview1”) model=gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING) self.hostsmodel=model host_inter=insert_row(model,None,’www.immunitysec.com’, ‘Default Main

Server’) self.hosttree.set_model(model)

In the following code you define a renderer, which you will use to display a line of text inside one of the columns of the TreeView. You then append that column to the TreeView widget. The text=0 is not, as it appears, a Boolean value but rather the index into the model from which the text of the column should come. In this case, insert_row is going to put the hostname (in this case ‘www.immunitysec. com’) as the first value in a row in the model:

renderer=gtk.CellRendererText() column=gtk.TreeViewColumn(“Host/Channel”,renderer, text=0)

column.set_resizable(True) self.hosttree.append_column(column)

You do this again for a second column, giving it an index into the model of 1 (the second value in the model’s row):

renderer=gtk.CellRendererText() column=gtk.TreeViewColumn(“Users”,renderer, text=1)

column.set_resizable(True) self.hosttree.append_column(column)

And, of course, you’ll need to add the insert_row method:

def insert_row(self,model,parent,firstcolumn,secondcolumn, thirdcolumn=None): myiter=model.insert_after(parent,None) model.set_value(myiter,0,firstcolumn) model.set_value(myiter,1,secondcolumn)

if thirdcolumn != None: model.set_value(myiter,2,thirdcolumn)

return myiter

When all of this is inserted into the __init__ function of your PyRAP class, and you run your application, you should see your column headers and some initial information in your tree view, as shown in Figure 13-26.

240

TEAM LinG