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

Beginning Python (2005)

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

Writing a GUI with Python

Figure 13-26

Currently, there will only be one pyRAP server, so you can leave that tree view at that. If you want, you can add signal handlers that respond to button presses to generate drop-down menus or perform other actions based on which line in the host tree the user has selected. The following sample code is included to demonstrate how this can be done.

In this sample code, you add a button_press signal to the GUI in Glade and in your code’s signal connection dictionary:

#add this code to your signal dictionary in PyRAP:__init__ to #capture treeview1’s button presses “on_treeview1_button_press_event”: self.moduletree_press,

#The handler for the button press looks like this.

#You can place this directly in your PyRAP class as a method def treeview1_press(self,obj,event):

“””

Handle people double clicking on our tree view “””

#print “Clicked”

if event.type==gtk.gdk._2BUTTON_PRESS: #print “Double Click”

model,iter=self.treeview1.get_selection().get_selected() if iter==None:

print “weird - nothing was selected, yet we got a double-click” return

nodetext=model.get_value(iter,0) #now do something based on nodetext.

#...

Fur ther Enhancing PyRAP

Tree Views can quickly become quite tricky. Not every column needs to be visible to the user. In some columns, you may want to store references to the actual object being displayed. For example, if you had a Server class, you could specify a Server object as an invisible object on the line, and if the user doubleclicks on that line, we can pass that information on to the Server object itself.

The trick to doing this is to set_value with a number and then never use that number in insert_row as text=number. This enables you to have columns in your model that are never referenced in the TreeView. In fact, you can have different TreeViews, each of which displays different columns in your

241

TEAM LinG

Chapter 13

model. Hence, the “model, view, controller” name of this way of doing things. (We haven’t gone over the “controller” part here.)

The following code demonstrates several techniques you will find useful as you code more intensive applications in pyGTK. First you’ll notice it has little icons embedded in it as XPM files. It also can react dynamically to requests to add or remove lines from the TreeView. It stores objects in an invisible column for later use, and has a few other code snippets you might feel like copying at some point as you become more advanced with your pyGTK work. For what it’s worth, in modern pyGTK, Lists are essentially the same as Trees, so once you become comfortable with Tree’s, you’ll find Lists quite simple.

You’ll walk through the code, and it will be explained it as you go.

First, you have some global variables to declare. The first one is a “XPM” picture of a big capital L. XPM is a very basic image format. The colors come first with a space being color #000000, a . being color #ffff04, and the X being the color #b2c0dc, and then these defined characters represent the individual pixels in the image. It’s a quick and easy way to add icons to your program:

#### START CODE

 

localNodeXPM = [

 

“12 12 3 1”,

 

c #000000”,

“.

c #ffff04”,

“X

c #b2c0dc”,

“X

 

X”,

“X

..

X”,

“X

..

X”,

“X

..

X”,

“X

..

X”,

“X

..

X”,

“X

..

X”,

“X

..

X”,

“X

..

X”,

“X

.........X”,

“X

.........X”,

“X

 

X”

]

 

 

 

 

 

You then need to convert that XPM picture into a PixBuf:

localNodePB = gtk.gdk.pixbuf_new_from_xpm_data(localNodeXPM)

We store a reference to this new PixBuf in a dictionary:

text_to_PB={} text_to_PB[“”]=None

text_to_PB[“LocalNode”]=localNodePB

The next code fragment shows how to expand a TreeView as if someone had clicked on it to expand it. It takes in a path, which is a numeric description of a row and column in the treeview. It does some basic error-checking to ensure that path is not None:

242

TEAM LinG

Writing a GUI with Python

def treeview_expand_to_path(treeview, path):

“””Expand row at path, expanding any ancestors as needed.

This function is provided by gtk+ >=2.2, but it is not yet wrapped by pygtk 2.0.0.”””

if path==None: return

for i in range(len(path)): treeview.expand_row(path[:i+1], open_all=False)

You also have a function to find objects in the TreeModel, starting from an iterator within that model. This function is recursive (which means that it calls itself), as most tree iteration algorithms are. Of course, this is not ideal for extremely large data sets, as Python has a somewhat limited stack space compared to languages that are built to use recursive functions more extensively. Still, if your data set is not over a couple of hundred rows deep, you’ll find this works for your needs. It returns an iterator:

def findobj(model,searchobj,current): myiter=current row=model.get_value(myiter,0)

#print “row[0]=%s searchobj=%s”%(row,searchobj)

if row==searchobj:

#print “Found! - returning %s”%(myiter) return myiter

else:

if model.iter_has_child(myiter): childiter=model.iter_children(myiter) while childiter!=None:

myiter=findobj(model,searchobj,childiter) if myiter!=None:

return myiter childiter=model.iter_next(childiter)

#print “Not found!” return None

Now start your nodegui class. You can ignore the engine referenced throughout thanks to proper object isolation. You’ll find that a lot of your code uses “engines” of various sorts as middleware within your own application.

The constructor here defers initialization to the init_app function:

class nodegui:

def __init__(self,nodetree,local,engine):

self.engine=engine self.init_app(nodetree,local)

If you refer back to Figure 13-8 (the image of CANVAS), you’ll see what this code has to manage. It was originally responsible for adding new nodes to the treeview on the right-hand side (the “Node Tree”).

243

TEAM LinG

Chapter 13

The addNode function takes a high-level object, the Node, and adds it; then it adds all its displayable sub-objects to treeview. In this case, we first add the interfaces, followed by the hosts that the node knows about, and then we finally add any other Nodes that are under this Node, making it a recursive function.

We then expand the treeview to show this new Node using the expand_to_path function detailed earlier:

def addNode(self,node):

#print “nodegui::addNode called”

#recursively go through and set up the node tree from the start node p=self.addLine(node)

self.addLine(node.interfaces)

for interface in node.interfaces.get_children(): self.addLine(interface)

for listeners in interface.get_children(): self.addLine(listeners)

self.addLine(node.hostsknowledge)

for host in node.hostsknowledge.get_children(): self.addLine(host)

for c in host.get_children(): self.addLine(c)

self.addLine(node.connected_nodes)

for n in node.connected_nodes.get_children(): self.addNode(n)

#print “nodegui::addNode leaving” #self.nodetree.set_cursor(p) treeview_expand_to_path(self.nodetree, p) return

The addLine function takes an object and adds that object to the tree model (and hence to the tree view). Each line has two columns: a potentially empty pixbuf that represents the class of the line and a text field. The model itself has another column, never displayed by the treeview, which is the line object itself. This way, the tree model is connected to the objects it represents.

Of course, addLine also checks to ensure that no duplicate objects are in the tree. Each line object has a parent attribute that is set to None if it is the root object. Otherwise, the line object is added under its parent object:

def addLine(self,lineobj): #no duplicates

start=self.model.get_iter_first()

if start!=None and findobj(self.model,lineobj,start): return

lineobj.set_engine(self.engine) #print “\naddLine(%s)”%lineobj if lineobj.parent==None:

myiter=self.model.insert_after(None,None)

244

TEAM LinG

Writing a GUI with Python

else:

#somehow find the parent node in the tree parentobj=lineobj.parent

#for line in tree, if line[0]==parentobj, return line #http://www.pygtk.org/pygtk2`tutorial/sec-TreeModelInterface.html start=self.model.get_iter_first() myiter=findobj(self.model,parentobj,start) myiter=self.model.insert_after(myiter,None)

lineobj.gui=self pix=lineobj.get_pix() #print “Pix=%s”%pix if pix!=None:

pix=text_to_PB[pix] if pix==””:

pix=None

#NOT A VISIBLE COLUMN (since text=0 has never been set) self.model.set_value(myiter,0,lineobj) self.model.set_value(myiter,1,pix) #Set the icon in the first column self.model.set_value(myiter,2,lineobj.get_text()) #set the text in the

first column

return self.model.get_path(myiter) #return

This function deletes a row and all of its children from the TreeView by deleting them from the model. Iterators are again used to traverse the tree downwards:

def delete(self, line): treestore=self.model start=treestore.get_iter_first()

from_parent=findobj(treestore,line,start) iter = treestore.iter_children(from_parent)

while iter: treestore.remove(iter)

iter = treestore.iter_children(from_parent) treestore.remove(from_parent)

return

If an object changes, it may have changed how it wants to be represented by the TreeView. The update_object method enables it to tell the TreeView that it’s time to refresh its pixbuf or its textual description:

def update_object(self,object):

start=self.model.get_iter_first() myiter=findobj(self.model,object,start) if myiter==None:

#error! return

pix=object.get_pix() #print “Pix=%s”%pix if pix!=None:

pix=text_to_PB[pix]

245

TEAM LinG

Chapter 13

if pix==””: pix=None

self.model.set_value(myiter,0,object) #NOT A VISIBLE COLUMN (since text=0 has never been set)

self.model.set_value(myiter,1,pix) #Set the icon in the first column self.model.set_value(myiter,2,object.get_text()) #set the text in the first

column

self.model.row_changed(self.model.get_path(myiter),myiter)

#TODO: we need to force an expose event to the treeview now, somehow! return

Next is the deferred initialization procedure. This configures a TreeView and a model for use. You can see that instead of a CellRendererText, we use a CellRendererPixbuf to create the pretty pictures:

def init_app (self,nodetree,local): “Initialize the application.”

self.nodetree=nodetree

#set up columns

#this “text=X” is the column number cellpb = gtk.CellRendererPixbuf()

column=gtk.TreeViewColumn(“Node Tree”, cellpb, pixbuf=1) cell = gtk.CellRendererText()

column.pack_start(cell, False) #here we pack a text “column” into the same

column

column.add_attribute(cell, ‘text’, 2) #column 2 is in “Name” but is a text

column

#to right align it - we don’t like that very much #cell.set_property(‘xalign’, 1.0) self.nodetree.append_column(column)

model=gtk.TreeStore(gobject.TYPE_PYOBJECT,gtk.gdk.Pixbuf,gobject.TYPE_STRING) self.nodetree.set_model(model)

self.model=model

self.addNode(local) return

The final method handles interaction with the user. This shows one of the rare times you’ll find yourself constructing widgets by hand — when doing pop-up menus. Of course, here the pop-up menu is constructed out of a list of strings automatically pulled from the line object. This is one of the reasons why we have a reference to the line object in the model.

All line objects have a menu_response method (not shown here) that will react to being clicked by the user:

246

TEAM LinG

Writing a GUI with Python

def line_press(self, obj, event): #print “Line Press called” if event.button == 3:

model,iter=self.nodetree.get_selection().get_selected() if iter==None:

#print “weird - nothing was selected, yet we got a right-click” return

x=int(event.x)

y=int(event.y)

try:

path, col, colx, celly= obj.get_path_at_pos(x,y) except TypeError:

return obj.grab_focus()

obj.set_cursor(path, col, 0) nodetext=model.get_value(iter,2) lineobj=model.get_value(iter,0) menulines=lineobj.get_menu()

if menulines==[]:

#print “Nothing in menu...returning” return

else:

#print “Something in menu of %s: %s”%(nodetext,menulines) pass

mymenu=gtk.Menu() for l in menulines:

mline=gtk.MenuItem(l)

mline.connect(“activate”, lineobj.menu_response, l) mline.show()

mymenu.append(mline) #print nodetext, str(event) mymenu.show()

mymenu.popup(None,None, None,event.button, event.time)

You also have a global quit function, of course:

def quit(args): gtk.mainquit() return

Our main function initializes everything and starts the main loop. The trick here is that we use this module from the main GUI module (and it runs as the main GUI thread), but it can also be tested independently:

if __name__ == ‘__main__’:

local=localNode()

#do splashscreen here maybe gladefile=”newgui.glade” window=”window1”

wTree = gtk.glade.XML (gladefile, window)

247

TEAM LinG

Chapter 13

nodetree=wTree.get_widget(“nodeview”) mygui=nodegui(nodetree,local,None) #window1 must be the main app window!!!

dic = {“on_quit_button_clicked” : quit, “on_window1_destroy” : (quit), “on_nodeview_button_press_event”:mygui.line_press,

}

window=wTree.get_widget(“window1”) # sure there must be another way wTree.signal_autoconnect (dic)

#hmmm

try:

gtk.threads_init() except:

print “No threading was enabled when you compiled pyGTK!” sys.exit(1)

gtk.threads_enter() gtk.mainloop () gtk.threads_leave()

Summar y

There’s no limit to the things you can do with your GUI using pyGTK. You can take screenshots, display graphics, handle complex information sets in large windows, draw on a blank canvas, or simply pop up quick GUIs for custom command-line utilities, exposing them to less technically oriented users.

There are, of course, personal styles to every programming project. Many people have developed tools that enable automated application development. Python’s bevy of introspection and OO features enables you to dynamically handle all sorts of changes in your GUI. As you become more familiar with pyGTK, you’ll find these sorts of techniques to be extremely natural.

Even if you don’t use pyGTK, understanding how pyGTK works will be a valuable asset in your programming toolbox. Furthermore, there’s always the possibility that you have a spare 15 minutes and want to write a custom GUI chat client for your friends.

Exercises

1.Write a Glade interface and a pyGTK class that runs a command and puts the results into a TextView.

2.Modify exercise 1 to put the results into the TextView as they come back from the command. (Hint: You’ll need to use threading to do this).

248

TEAM LinG

14

Accessing Databases

Just about every large enterprise system uses a database for storing data. For example, amazon.com, the online retailer, needs a database to store information on each product for sale. For Python to prove capable of handling these types of enterprise applications, the language must be able to access databases.

Luckily, Python provides a database API (Application Programming Interface — how you program for the database), which enables you to access most databases using an API that is very similar in all of the databases that the API works with, in spite of the databases’ different native APIs. The database, or DB, API doesn’t define all aspects of working with databases, so there are some minor differences. For the most part, though, you can access databases such as Oracle or MySQL from your Python scripts without worrying too much about the details of the specific databases.

Having a generic database API proves very useful, as you may need to switch databases or have your application work with multiple databases, and you won’t want to recode major parts of your program to allow this. Normally, you can do all of this in Python without a lot of programming changes being needed.

Even if you aren’t writing the next amazon.com online site, databases provide a convenient means to persist data for longer than the program is running (so that you don’t lose the data that a user has entered if you want to restart your program), query for items, and modify your data in a safe manner.

This chapter covers the two main database systems supported by Python, DBM persistent dictionaries, and relational databases with the DB API. In addition, this chapter describes how to set up a database, in case you don’t have a database handy.

Specific topics include the following:

Using the DBM libraries to create persistent dictionaries

Learning about relational databases

Setting up the Gadfly database

TEAM LinG

Chapter 14

Setting up the MySQL database

Working with the Python DB API

Creating connections

Accessing data with cursors

Connecting to databases

Querying and modifying data

Working with transactions

Handling errors

Using other database tools

In many cases, you don’t require a full-blown relational database. In such cases, creating a persistent dictionary using dbm files is enough.

Working with DBM Persistent Dictionaries

A persistent dictionary acts exactly like you’d expect. You can store name/value pairs in the dictionary, which are saved to a disk, and so their data will endure between various times that your program is run. So if you save data to a dictionary that’s backed by a dbm, the next time that you start your program, you can read the value stored under a given key again, once you’ve loaded the dbm file. These dictionaries work like normal Python dictionaries, which are covered in Chapter 3. The main difference is that the data is written to and read from disk.

An additional difference is that the keys and the values must both be strings.

DBM, short for database manager, acts as a generic name for a number of C language libraries originally created on Unix systems. These libraries sport names such as dbm, gdbm, ndbm, sdbm, and so on. These names correspond closely to the available modules in Python that provide the requisite functionality.

Choosing a DBM Module

Python supports a number of DBM modules. Each DBM module supports a similar interface and uses a particular C library to store the data to disk. The main difference lies in the underlying binary format of the data files on disk. Each DBM module, unfortunately, creates incompatible files. That is, if you create a DBM persistent dictionary with one DBM module, you must use the same module to read the data.

None of the other modules will work with that data file.

The following table lists the DBM modules.

anydbm

Chooses best DBM module

dbhash

Uses the Berkeley Unix DB library

dbm

Uses the Unix DBM library

 

 

250

TEAM LinG