25.3. How to Separate Callback Methods From Signal Handlers

25.3.1. Overview

You do not have to store all of your callback methods in one main program file. You can separate them into classes of their own, in separate files. This way your main program can derive the methods from those classes using inheritance. You end up having all the original functionality with the added benifits of easier maintenance, code reusability, and smaller file sizes, which means less of a burden for text editors.

25.3.2. Inheritance

Inheritance is a way to reuse code. A class can inherit all the functionality of other classes, and the nice thing about inheritance is that we can use it to divide a huge program into logical groups of smaller, more maintainable pieces.

Now lets spend a second on terminology. A derived class, some call this a subclass or a child class, is a class that derives some of its functionality from other classes. A base class, some call it a superclass or a parent class, is what the derived class inherits from.

Below is a short example to help you become familiar with inheritance. You can try this out in the python interpreter to gain some first hand experience.

Create two base classes:

class base1:
   base1_attribute = 1
   def base1_method(self):
     return "hello from base class 1"

class base2:
   base2_attribute = 2
   def base2_method(self):
     return "hello from base class 2"
    

Then create a derived class that inherits from these two base classes:

class derived(base1, base2):  #a class derived from two base classes
   var3 = 3                     
    

Now the derived class has all the functionality of the base classes.

x = derived()        # creates an instance of the derived class
x.base1_attribute    # 1
x.base2_attribute    # 2
x.var3               # 3
x.base1_method()     # hello from base class 1
x.base2_method()     # hello from base class 2
    

The object called x has the ability to access the variables and methods of the base classes because it has inherited their functionality. Now lets apply this concept of inheritance to a PyGTK application.

25.3.3. Inheritance Applied To PyGTK

Create a file called gui.py, then copy this code into it.

#A file called: gui.py

import pygtk                                                  
import gtk                                                   

# Create a class definition called gui                                    
class gui:
  #                                                     
  #          CALLBACK METHODS
  #------------------------------------                                                                                                
  def open(self, widget):                              
    print "opens stuff"                                       
  def save(self, widget):                              
    print "save stuff"                                        
  def undo(self, widget):                              
    print "undo stuff"                                       
  def destroy(self, widget):
    gtk.main_quit()
         
                                                        
  def __init__(self): 
    #
    #        GUI CONSTRUCTION CODE
    #-----------------------------------------------
    self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    self.window.show()                                                              
    self.vbox1 = gtk.VBox(False, 25)
    self.window.add(self.vbox1)
    open_button = gtk.Button(label="Open Stuff")
    open_button.set_use_stock(True)
    self.vbox1.pack_start(open_button, False, False, 0)
    open_button.show() 
    save_button = gtk.Button(label="Save Stuff")
    self.vbox1.pack_start(save_button, False, False, 0)
    save_button.show()
    undo_button = gtk.Button(label="Undo")
    self.vbox1.pack_start(undo_button, False, False, 0)
    undo_button.show()
    self.vbox1.show()
    #
    #        SIGNAL HANDLERS
    #------------------------------------------------                                                        
    open_button.connect("clicked", self.open)          
    save_button.connect("clicked", self.save)           
    undo_button.connect("clicked", self.undo) 
    self.window.connect("destroy", self.destroy)          
                                                              
  def main(self):                                             
   gtk.main()                                                 
                                                              
if __name__ == "__main__":                                    
 gui_instance = gui()       # create a gui object             
 gui_instance.main()        # call the main method         
    

If your run this program you will find it is just a simple window with some buttons. As the program is organized right now, all of the code is in one single file. But in a moment, you will find out how to break that program up into multiple files. The idea is to take those four callback methods out of the gui class and put them into classes of their own, in separate files. Now if you had hundreds of callback methods you would try and group them in some logical way, for example, you might put all of your methods that deal with input/output into the same class, and you would make other classes for other groups of methods as well.

The first thing we have to do is make some classes for the methods in the gui.py file. Create three new text files, and name them io.py, undo.py, and destroy.py, and put these files in the same directory as the gui.py file. Copy the code below into the io.py file.

class io:                                                       
  def open(self, widget):                                  
    print "opens stuff"                                          
                                      
  def save(self, widget):                                  
    print "save stuff"                                           
    

These are the two callback methods, open and save, from the gui.py program. Copy the next block of code into the undo.py file.

class undo:                                                      
  def undo(self, widget):                                  
    print "undo stuff" 
    

This is the undo_method from gui.py. And finally, copy the code below into destroy.py.

import gtk

class destroy:
  def destroy(self, widget):
    gtk.main_quit()                                                            
    

Now all the methods are separated into classes of their own.

Important

In your future programs you will want to import things like gtk, pango, os ect... into your derived class(the one with all of your gui initialization code), but also, remember to import any modules or classes you need into your base classes too. Sometimes you might create an instance of a gtk widget in a base class method, in that case import gtk.

This is just an example of a base class where you would be required to import gtk.

  import gtk                                                        
                                                                           
  class Font_io                                                     
    def Font_Chooser(self,widget):                                  
      self.fontchooser = gtk.FontSelectionDialog("Choose Font")                                  
      self.fontchooser.show()    
      

Notice it defines a gtk widget, a font selection dialog. You would normally import gtk in your main class(the derived class) and everything would be ok. But the second you take this Font_Chooser method out of your main class and put it into a class of its own, and then try to inherit from it, you would find you get an error. In this case, you would not even see any error until you were running the program. But when you try to use the Font_Chooser, you would find that gtk is not defined, even though you have imported it in your derived class. So just remember that when you create base classes, you need to add their proper imports too.

With your three classes in three separate py files, you now need to change the code in the gui.py file in three ways.

  1. Import the classes you have created.

  2. Change your class definition.

  3. Delete your callback methods.

The updated code below shows how to do this.

#A file called:  gui.py
#(updated version)
#(with multiple inheritance)

import pygtk                                                  
import gtk  
                       
from io import file_io                       #                                
from undo import undo                        # 1. Import Your Classes
from destroy import destroy                  #

# Create a class definition called gui                                    
class gui(io, undo, destroy):                # 2. Changed Class Definition
                                             # 3. Deleted Callbacks
  def __init__(self): 
    #
    #        GUI CONSTRUCTION CODE
    #-----------------------------------------------
    self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    self.window.show()
    self.vbox1 = gtk.VBox(False, 25)
    self.window.add(self.vbox1)
    open_button = gtk.Button(label="Open Stuff")
    open_button.set_use_stock(True)
    self.vbox1.pack_start(open_button, False, False, 0)
    open_button.show() 
    save_button = gtk.Button(label="Save Stuff")
    self.vbox1.pack_start(save_button, False, False, 0)
    save_button.show()
    undo_button = gtk.Button(label="Undo")
    self.vbox1.pack_start(undo_button, False, False, 0)
    undo_button.show()
    self.vbox1.show()
    #
    #        SIGNAL HANDLERS
    #------------------------------------------------
    open_button.connect("clicked", self.open_method)          
    save_button.connect("clicked", self.save_method)           
    undo_button.connect("clicked", self.undo_method) 
    self.window.connect("destroy", self.destroy)          
                                                              
  def main(self):                                             
   gtk.main()                                                 
                                                              
if __name__ == "__main__":                                    
 gui_instance = gui()       # create a gui object             
 gui_instance.main()        # call the main method
    

These three lines are new:

       from io import io                                   
       from undo import undo                                          
       from destroy import destroy     
    

The import statements are of the form:

       from [filename of your class file] import [class name]
    

Here is the class definition change:

       class gui(io, undo, destroy):
    

The names of the base classes go between the parenthesis in the class definition. Now the gui class has become a derived class, and is able to use all the attributes and methods defined in its base classes. Also, the gui class is inheriting from multiple classes(two or more), this is known as multiple inheritance.

Now change the gui.py file to the updated version and run the program again. You will notice it works exactly the same, except now all the callback methods are in separate classes, being inherited by the gui class.

There is just one other matter of interest to take note of. As long as your gui.py program and your base class files are all in the same directory, everything will work just fine. But if you want to create another directory inside there called classes, in which to organize your files of base classes, then you will need to add two more lines of code with the rest of your import statements in gui.py, like this:

        import sys
        sys.path.append("classes")
    

where classes is the name of the directory you store your classes in. This lets Python know where to look for your classes. Try it out. Just make a directory called classes in the directory where you have the gui.py program. Then put your three base class files into this classes directory. Now add the two lines of code show above to the top of the gui.py file. And thats it!

One final note for those that use py2exe for compiling python programs. Put your base classes in your Python directory, then py2exe will included them in the compiled version of your program just like any other Python module.