Per Erik Strandberg /cv /kurser /blog

I recently understood that Gnome [1] , and also Xfce Desktop Environment [2] create GUI's (see Graphical User Interface) using a library called GTK: GIMP Tool Kit. This provides a stable and well balanced kit for creating almost any GUI in many desktop environments (mostly for *nix, but also Windows if you do some tricks).

I have also known for quite some time that there are a few ways of creating a GUI using our favourite programming language Python. But I have unfortunately had little or no success and/or patience using TKInter (the first one I tried). But Yesterday I took the courage to try to investigate pygtk - an implementation of gtk for python.

More on pygtk is found here [3] and a tutorial in different formats is found here: [4]

There are a number of ways for building GUI's with gtk. There are also a number of standard dialogs already implemented, like the open/save file dialog.

Example One: Included Dialogs

This little example will, if it run from a terminal use a standard Open As-GUI and print the contents of the file selected on the terminal. (The example is based on the one included in the pygtk manual)

Here is a screen shot of if: http://www.pererikstrandberg.se/blog/pygtk_dumpfile1.png

# needed imports
import pygtk
import gtk
pygtk.require('2.0')

# create a new dialog 
dialog = gtk.FileChooserDialog("Open..", None,
    gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
    gtk.STOCK_OPEN, gtk.RESPONSE_OK))

if dialog.run() == gtk.RESPONSE_OK:
    filename = dialog.get_filename()
    print "%s\n%s" % (filename, '-' * len(filename))
    f = open(filename, 'r')
    print f.read()
    f.close()
    
dialog.destroy()
dump_file1.py 

And by adding a filter (see below) between the dialog creation and the dialog.run this is what we get instead: http://www.pererikstrandberg.se/blog/pygtk_dumpfile2.png

# if we want to apply filters
filter1 = gtk.FileFilter()
filter1.set_name("any file")
filter1.add_pattern("*")
dialog.add_filter(filter1)

filter2 = gtk.FileFilter()
filter2.set_name("python files")
filter2.add_pattern("*.py")
dialog.add_filter(filter2)

Example Two: Building GUI's from scratch

As you might have already figured out it is possible to create GUI's with at least some tools (see next section). But it is always nice to do it from scratch at least a few times to get the hang of it.

Gtk uses callbacks (function-pointers that for example write a file to disk) and events (an event is triggered if you for example press a button) to make things happen (for example when clicking a button the file you work on is saved - this is real magic). The user must also connect the event with suitable callbacks. This might sound complicated but it is quite nice if for example more than one event triggers the same behavior (f.x. pressing Ctrl+S or clicking the save button BOTH results in a file being saved).

The following example results in a simple window with a button: http://www.pererikstrandberg.se/blog/pygtk_mini.png

#!/usr/bin/env python

import pygtk
pygtk.require('2.0')
import gtk

class HelloWorld:

   # this is a function later used as a callback
   def hello(self, widget, data=None):
      print "Hello World"

   def destroy(self, widget, data=None):
      gtk.main_quit()

   def __init__(self):
      self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)

      self.window.connect("destroy", self.destroy)      
      self.window.set_border_width(10)
      
      self.button = gtk.Button("Hello World")

      # this next line connects the button, the click event and the function
      self.button.connect("clicked", self.hello, None)
      
      # here we add the button to the window 
      self.window.add(self.button)

      # we must also show both the button and the window
      self.button.show()
      self.window.show()

   def main(self):
      gtk.main()

if __name__ == "__main__":
   hello = HelloWorld()
   hello.main()

Building more complicated GUI's require a little more planning and thinking - but it is far from impossible. One important thing to know is that one needs to add buttons and other things in horizontal and vertical boxes - than in turn can contain other horizontal and vertical boxes. A very complex GUI should most likely be built with an IDE - such as the one we will see in the next section. This is particularly important if a developer later wants to be able to add, move or remove some bells and whistles.

Example Three: Building interfaces with Glade Interface Designer

Glade

An (or perhaps the) excellent tool for building Interfaces is Glade. It is of course free (as in freedom of speech). Glade comes with a few versions, and on my Xubuntu Linux installation I got Glade 2 as the default version (when just selected "Glade" in Synaptic package manager). Glade 2 is perhaps an excellent tool, but it is not suitable for python - it is more adapted towards creating Interfaces in good old fashioned C. A little searching in Synaptic revealed Glade 3. This versions is more modular and created interfaces for most programming languages that can work with GTK by using an intermediate layer of XML (the files a saved as "*.glade" - I like that having a file suffix that should surely be unique :D ).

You find Glade here: [5]

Using Glade

http://www.pererikstrandberg.se/blog/pygtk_glade_editor.png
This little screen shot shows a typical Glade session.

Also note that the screen shot shows the layout of my interface (in the Inspector). First in my Main Window I have a vertical box. The vertical box has two slots. The second of these slots contain the text view (called textor). This first slot in the vertical box is occupied with a horizontal box. Do not let this confuse you - this is just the gtk-way to let you add a number of buttons. The horizontal box in this little example contains two buttons: an open-button and a save-button.

Handlers

As you can see in the inspector the save button has the signal "clicked" associated to the handler "init_save". When the interface is under planning I guess it is nice to keep a list and plan all these handlers - otherwise they will surely be forgotten and using your application will be strange.

The handlers (f.x. init_save) must python later must be connected with a callback. The syntax is something like this: self.window.connect("do_stuff", self.function_that_does_the_stuff). Another tutorial I read used a dictionary to autoconnect many things at once:

dic = { "init_save" : self.saveit, "init_load" : self.loadit}
self.wTree.signal_autoconnect(dic)

Connecting an interface and a python script

To "connect" a glade project and a python script you can use a syntax similar to this one:

#!/usr/bin/python

import sys
import pygtk
pygtk.require("2.0")

import gtk
import gtk.glade

class RandomTestGTK:
    def __init__(self):
        self.gladefile = "silly_edit.glade"
        self.wTree = gtk.glade.XML(self.gladefile, "MainWindow")

As you can see in the above code snippet there a lot of important packages here that give you an idea of the underlying structure: pygtk, gtk, and gtk.glade.

Handle the widgets

In your program there may be items the user should be able to modify - such as the text in your editor. Also you might want to be able to change the title of your application. To obtain this it seems the following way is nice:

self.mother = self.wTree.get_widget("MainWindow")
self.textor = self.wTree.get_widget("textor").get_buffer()

Here I use the get_widget function of self.wTree and use the name of the widget I want to handle. I just store them in a random handle in self. Please note that I use two items called textor - one is a widget that contains a buffer. The otherone is a handle to the buffer - don't let this confuse you I am just lazy and used the same name twice.

Implementing the callbacks

As you have seen above I have connected the click signals of the save button in my interface to the init_save-handler. I also connected the init_save-handler using a callback to the saveit-function. So now I need to implement these functions.

Here is a stripped version of my loadit-function (It is silly but at least it does something). As you can se it opens a file and places its contents in self.textor.

def loadit(self, widget):
    f = file('/home/per/.bashrc')
    self.textor.set_text(f.read())
    f.close()

Result

A screen shot of the resulting application:
http://www.pererikstrandberg.se/blog/pygtk_glade_silly_edit.png

Source

Here is the glade-project [6] and here is the python project [7] .

Beware: if you do not alter the source code it will not work under windows, also it will most likely not find the any file when you press load, but it will save a file in /tmp/my_old_bachrc.txt when you press save.

Conclusions

I really like pygtk - hopefully I'll learn a little more and give a tutorial on how to use multiple windows in the same project sometime soon.


This page belongs in Kategori Programmering