Building Buttons

Buttons, of course, are standard elements of virtually every GUI these days. Modern buttons are very sophisticated, usually having a 3-dimensional look and feel. Our simple graphics package does not have the machinery to produce buttons that appear to depress as they are clicked. The best we can do is find out where

Figure 10.3: Snapshot of dice roller in action.

the mouse was clicked after the click has already completed. Nevertheless, we can make a useful, if less pretty, button class.

Our buttons will be rectangular regions in a graphics window where user clicks can influence the behavior of the running application. We will need to create buttons and determine when they have been clicked. In addition, it is also nice to be able to activate and deactivate individual buttons. That way, our applications can signal which options are available to the user at any given moment. Typically, an inactive button is grayed-out to show that it is not available.

Summarizing this description, our buttons will support the following methods:

constructor Create a button in a window. We will have to specify the window in which the button will be displayed, the location/size of the button, and the label that will be on the button.

activate Set the state of the button to active.

deactivate Set the state of the button to inactive.

clicked Indicate if the button was clicked. If the button is active, this method will determine if the point clicked is inside the button region. The point will have to be sent as a parameter to the method.

getLabel Returns the label string of the button. This is provided so that we can identify a particular button.

In order to support these operations, our buttons will need a number of instance variables. For example, the button itself will be drawn as a rectangle with some text centered in it. Invoking the activate and deactivate methods will change the appearance of the button. Saving the Rectangle and Text objects as instance variables will allow us to change the width of the outline and the color of the label. We might start by implementing the various methods to see what other instance variables might be needed. Once we have identified the relevant variables, we can write a constructor that initializes these values.

Let's start with the activate method. We can signal that the button is active by making the outline thicker and making the label text black. Here is the code (remember the self parameter refers to the button object):

def activate(self):

"Sets this button to 'active'." self.label.setFill('black') self.rect.setWidth(2) self.active = 1

As I mentioned above, in order for this code to work, our constructor will have to initialize self.label as an approprate Text object and self.rect as a Rectangle object. In addition, the self.active instance variable stores a Boolean value (1 for true, 0 for false) to remember whether or not the button is currently active.

Our deactivate method will do the inverse of activate. It looks like this:

def deactivate(self):

"Sets this button to 'inactive'." self.label.setFill('darkgrey') self.rect.setWidth(1) self.active = 0

Of course, the main point of a button is being able to determine if it has been clicked. Let's try to write the clicked method. As you know, the graphics package provides a getMouse method that returns the point where the mouse was clicked. If an application needs to get a button click, it will first have to call getMouse and then see which active button (if any) the point is inside of. We could imagine the button processing code looking something like the following:

pt = win.getMouse() if button1.clicked(pt):

# Do button1 stuff elif button2.clicked(pt):

# Do button2 stuff elif button2.clicked(pt)

# Do button3 stuff

The main job of the clicked method is to determine whether a given point is inside the rectangular button. The point is inside the rectangle if its x and y coordinates lie between the extreme x and y values of the rectangle. This would be easiest to figure out if we just assume that the button object has instance variables that record the min and max values of x and y.

Assuming the existence of instance variables xmin, xmax, ymin, and ymax, we can implement the clicked method with a single Boolean expression.

def clicked(self, p):

"RETURNS true if button is active and p is inside" return self.active and \

self.xmin <= p.getX() <= self.xmax and \ self.ymin <= p.getY() <= self.ymax

Here we have a single large Boolean expression composed by anding together three simpler expressions; all three must be true for the function to return a true value. Recall that the backslash at the end of a line is used to extend a statement over multiple lines.

The first of the three subexpressions simply retrieves the value of the instance variable self.active. This ensures that only active buttons will report that they have been clicked. If self.active is false, then clicked will return false. The second two subexpressions are compound conditions to check that the x and y values of the point fall between the edges of the button rectangle. (Remember, x <= y <= z means the same as the mathematical expression x< y < z (section 7.5.1)).

Now that we have the basic operations of the button ironed out, we just need a constructor to get all the instance variables properly initialized. It's not hard, but it is a bit tedious. Here is the complete class with a suitable constructor.

# button.py from graphics import *

class Button:

"""A button is a labeled rectangle in a window.

It is activated or deactivated with the activate()

and deactivate() methods. The clicked(p) method returns true if the button is active and p is inside it."""

def _init_(self, win, center, width, height, label):

""" Creates a rectangular button, eg:

qb = Button(myWin, Point(30,25), 20, l0, 'Quit') """

self.xmax, self.xmin = x+w, x-w self.ymax, self.ymin = y+h, y-h pl = Point(self.xmin, self.ymin)

p2 = Point(self.xmax, self.ymax)

self.rect = Rectangle(pl,p2)

self.rect.setFill('lightgray')

self.rect.draw(win)

self.label = Text(center, label)

self.label.draw(win)

self.deactivate()

def clicked(self, p):

"RETURNS true if button active and p is inside" return self.active and \

self.xmin <= p.getX() <= self.xmax and \ self.ymin <= p.getY() <= self.ymax def getLabel(self):

"RETURNS the label string of this button." return self.label.getText()

def activate(self):

"Sets this button to 'active'." self.label.setFill('black') self.rect.setWidth(2) self.active = l def deactivate(self):

"Sets this button to 'inactive'." self.label.setFill('darkgrey') self.rect.setWidth(l) self.active = 0

You should study the constructor in this class to make sure you understand all of the instance variables and how they are initialized. A button is positioned by providing a center point, width and height. Other instance variables are calculated from these parameters.

Was this article helpful?

0 0

Post a comment