Tuesday 26 June 2012

The classy way

One of the issues with GUI programs is that it is event-driven. The code in the event handlers is not called directly by your program it is called in response to something happening. This really is a great way to manage GUI programs, but it takes a bit of thinking about. When your event handler is triggered it will want to interact with other parts of your program; read or change variables, call other functions and so on. The event handler is not called directly by your code, that is the point. It responds to an event whenever the event happens. This means that you cannot pass variables to the event handler as parameters. We used global variables to overcome this but this is not a great way to do it. A better way is to use a class.

I'm not going to describe the ins and outs of classes and object-oriented programming - Google it. I will say that a class is the definition of how an object works and an object is what a program creates from the class. Objects run in code, classes are the definition of objects. The class can define data which can be used throughout the object. The class can also define functions to work within the object, using the object's data.

Data variables defined in a class are known as fields and when they are used outside of the object they can be known as properties. Functions defined in a class that get used from  outside the object the class defines are known as methods.

If we define a class in which we include all of the data fields we need to make our game of Rye work and define the event handlers there too, the event handlers can access any of the data they need. The global variable disappear with a touch of class.

Python has good support for classes. If you want to know more, take a look at the documentation.

I have converted the previous program to a version that uses a class, without adding any extra functionality.

#!/usr/bin/env python

from Tkinter import *

import ryepics

class ryeapp(Tk): 1
   
    def __init__(self,parent): 2
        Tk.__init__(self,parent) 3
        self.parent=parent
      
        # create the shared variables 4
        self.imageList=ryepics.createImages()
        self.ryeId=0
        self.ryeX=0
        self.ryeY=0
      
        # GUI initialisation
        self.title('Raspberry Rye')
        self.grid()
      
        # create the canvas for the game
        self.cnvs = Canvas(self, height=300, width=300)
        self.cnvs.grid(column=0, row=0, columnspan=4)
      
        # draw Rye at position 2,2
        self.ryeX=2
        self.ryeY=2
        sx=self.ryeX*20+10
        sy=self.ryeY*20+10
        self.ryeId=self.cnvs.create_image(sx,sy,image=self.imageList['G'])
      
        self.cnvs.bind("<Key-Down>", self.ryeDown)
        self.cnvs.bind("<Key-Up>", self.ryeUp)
        self.cnvs.bind("<Key-Left>", self.ryeLeft)
        self.cnvs.bind("<Key-Right>", self.ryeRight)
   
        # set the canvas to receive the focus, so keypresses are captured
        self.cnvs.focus_set()
   
    def ryeDown(self,event): 5
        self.cnvs.move(self.ryeId, 0, 20)
   
    def ryeUp(self,event):
        self.cnvs.move(self.ryeId, 0, -20)

    def ryeLeft(self,event):
        self.cnvs.move(self.ryeId, -20, 0)

    def ryeRight(self,event):
        self.cnvs.move(self.ryeId, 20, 0)

if __name__ == "__main__":
    app = ryeapp(None) 6
    app.mainloop()


This version is very similar to the last one, except that almost all of the program is now wrapped up in a class call ryeapp 1. The class is based on the Tk class, so our class extends the Tk class. Extending a class is known as inheritance and a fundamental part of object-oriented programming. All of the functions of Tk are now part of our class and we can add as much extra stuff as we want. This is not the only way to use a class, when you know more you might do it another way.

Classes can define a special function that is always called __init__ (that's two underscores before and after the init). Whenever the class is used to create an object this function will be called, so this is a great way to initialise our GUI. We define it on line 2 which is slightly different from the def statements we used before. Firstly it is indented within the class level. This means it is part of the class. Secondly the first parameter is self. This is a special reference to the rest of the class. This is how all the parts of the class can see each other. We'll use self whenever we want to refer to other parts of the class.

We have based our class on Tk. Tk has its own __init__ function, so the first thing we must do is call it, as in line 3. All of the rest of the function is similar to what we did in the previous program and it is worth comparing them. All of the variables we created as global variables before we can now create as fields in the class, starting at line 4.Everywhere we want to refer to a field we must use self. as the prefix to use the one in the class and not some other variable.

The event handlers are now defined as part of the class, starting on line 5. They can refer to the self.cnvs and self.ryeId safely.

Lastly when we have defined the class we need to use it to create the object that actually does something. Line 6 does this, creating the object app from the class ryeapp, passing None as the parent name, since this is the top level window it has no parent. The creation of the object automatically called our __init__ function which here does most of the work. When that is complete the mainloop() can start which waits for the events which get processed as we have seen before.

You can download a copy of classRye.py here.

2 comments:

  1. Sorry for the Google-English, I´m german, living in Sweden.

    In all Raspi code examples, the "import ryepics" included, there is the error message:

    File ". / ClassRye.py", line 5, in
    import ryepics
    Import Error: No module named ryepics

    What to do?

    Greetings from Sweden,
    Bernd

    ReplyDelete
    Replies
    1. Hi Bernd,
      If you click on the link to the ryeClass.py at the bottom of the post you will find that on that page there is also a link for ryepics.py there.

      Chris

      Delete

Thank you for taking the time to add a comment.

I have decided, regrettably, to review comments before they are published to ensure that offensive or inappropriate comments do not get shown.

Genuine comments are always welcome and will be published as quickly as possible.