You may have noticed that there is still a big problem with our Calculator — the keypad doesn’t work correctly in any base except for decimal. The reason for this failure lies with the following statement in the enterDigit: method:
X = (X*10.0) + [ [sender selectedCell] tag];
This statement multiplies whatever is in the X register by 10 and
adds the tag of a digit button each time one is clicked.
Unfortunately, we don’t want to multiply the X
register by 10 if a radix other than base 10 is in effect; instead,
we want to multiply by the current radix. So, for a first pass, the
10.0
in this statement should be replaced with
radix
.
But that’s not the only change we need to make; we also have to change the keypad of buttons so that particular buttons are deactivated when certain bases are selected. For example, a user shouldn’t be able to press the 8 button when the Octal base is chosen. Also, it would be nice to make buttons for the numbers A, B, C, D, E, and F appear when the user selects Hex. We’ll address all of these problems and add the new features in the remainder of this chapter.
Every Cocoa button is either enabled or disabled. If a button is disabled, the black labeling on it turns gray, and the button won’t respond to the mouse. In the following steps, we’ll modify thesetRadix: method so that each time the radix is changed, the method will scan all of the buttons in the digit-button matrix and disable the ones whose tags are equal to or greater than the newly selected radix.
To scan all of the digit buttons in the NSMatrix, we’ll need its id. We’ll also need the id of each individual button that the matrix contains. As we will see, the id of each cell inside an NSMatrix object is stored in yet another Foundation object, called an NSArray. As its name implies, an NSArray contains an array (or list) of other objects.
Our program will use the keyPad
outlet to learn
the id of the NSMatrix object when the application starts up. As
usual, we’ll arrange for this initialization in IB.
Now drag the Controller.h
icon from
PB’s Groups & Files pane and drop it in the
MainMenu.nib
window in IB.
This operation has the same effect as choosing IB’s
Classes → Read Controller.h menu command. You can
also drag the Controller.h
icon from the Finder
and drop it in the same place. Now MainMenu.nib
“knows about” the new
keyPad
outlet in the Controller class.
Connect the newly created keyPad
outlet in the
Controller instance to the NSMatrix object that contains the digit
buttons for your Calculator. Make sure you connect to the matrix and
not to a single digit button within the matrix (move the mouse near
the edge of the matrix until you see that it’s
surrounded by a connection wire box, as shown in Figure 7-11).
NSArray is an important class for Cocoa programmers; it’s a generic class (actually called a Foundation class) for maintaining a collection, or list, of other objects. The NSArray class has methods for:
Creating an array from a single object, or from a collection of objects
Sending a message to every object in the list
Counting the number of elements in the list
Accessing a specific element in the list by number
Creating a new NSArray from the existing array
After an NSArray is created and initialized, the collection of objects that make up the array never changes. If you need to create an array to which objects can be added or removed, use an NSMutableArray instead. This mutable (changeable) class has additional methods for:
Adding an object to the list
Adding an object to the list if it isn’t already there
Removing an object from the list
Refer to the Cocoa Foundation documentation for a detailed explanation of the NSArray and NSMutableArray classes.
Back in PB, insert the code shown here in bold into the setRadix:
action method in
Controller.m
:
- (IBAction)setRadix:(id)sender { NSArray *cells; int i; radix = [[sender selectedCell] tag]; // Disable the buttons that are higher than selected radix cells = [keyPad cells]; for (i=0; i<[cells count]; i++) { id cell = [cells objectAtIndex: i]; [cell setEnabled: ([cell tag] < radix) ]; } [self displayX]; }
We’ll explain the new code in setRadix: line by line. The following line
sends the cells
message to the
keyPad
(NSMatrix) object, which causes the object
to return the id of the NSArray object that holds all of the
NSMatrix’s (button) cells:
cells = [keyPad cells];
Once we have the id (stored in the cells
instance
variable) of this NSArray object, we can easily access the objects
stored inside it. This line sets up a loop that will execute for each
of the objects stored in the NSArray object:
for (i=0; i<[cells count]; i++)
This line sets the cell
local variable to be the
id of the ith element in the NSArray object:
id cell = [cellList objectAtIndex: i];
The expression [cell
tag]
<
radix
in the following
line returns YES if cell
should be enabled and NO
if it shouldn’t (YES and NO are specified by
#define
operators in the Foundation class
NSObjCRuntime.h
file):
[cell setEnabled: ([cell tag] < radix)];
The outermost message then sets the cell to be enabled or disabled as appropriate for the current radix. For example, if the radix is 8 (octal), all cells with tags less than 8 should be enabled (YES), while cells with tags 8 or greater should be disabled (NO).
There are a variety of ways to loop over the objects stored with an NSArray. You can create an integer variable and step through all of the variables, as we did earlier. Alternately, you can ask the array for an objectEnumerator and step that enumerator through the contents of the array. For example, the setRadix: method could be rewritten to look like the following (not necessary to implement):
- (IBAction)setRadix:(id)sender { NSEnumerator *enumerator; NSCell *cell; radix = [[sender selectedCell] tag]; // Disable the buttons that are higher than selected radix enumerator = [[keyPad cells] objectEnumerator]; while (cell = [enumerator nextObject]) { [cell setEnabled: ([cell tag] < radix) ]; } [self displayX]; }
This revised version of the setRadix:
method is smaller and more object-oriented, but some
people may find it harder to understand. It may also take a few
thousandths of a second longer to run; on the other hand, it may not.
Ultimately, both versions of the method work equally well, but the
object-oriented version is easier to debug and easier to maintain. In
general, you should use the NSEnumerator class for iterating through
NSArrays, rather than a for
loop with the
objectAtIndex: method.
Save all pertinent files and build and run the Calculator application.
With Calculator running, click the digit buttons to display the number 258.
Now click the Dec pop-up menu button and drag to Binary. Note that the number 258 changes to its binary representation and all the digit buttons except 0 and 1 are disabled, as shown in the window on the left in Figure 7-12. The buttons turn gray because Cocoa buttons automatically display their titles in gray when they are disabled.
Next, click the Binary pop-up menu button and drag to Octal. Note that the number changes to its octal representation and that the digit buttons 8 and 9 are disabled, as shown in the window on the right in Figure 7-12.
The changes to the setRadix: method bear mentioning, because they contain the essence of another important object-oriented concept: coherence . Being coherent means being logically or aesthetically ordered or integrated. In object-oriented programming, coherence means writing as little code as necessary by writing code that figures out what it needs to know when it runs, rather than having things preprogrammed. This way, if something changes, the code automatically reconfigures itself at runtime.
In this example, the setRadix: method disables those buttons in the matrix that have a tag that is equal to or greater than the current radix — so, for example, the buttons labeled 2-9 don’t work when the Calculator is in binary mode. But rather than hardcoding the keys, the setRadix: method needs to disable the keys for each radix; we have setRadix: find these keys by scanning through the associated NSArray object that contains the matrix cells. Likewise, rather than hardcoding into setRadix: the number of buttons in the matrix, we have setRadix: determine the number by asking the NSArray how many objects it contains. This way, we can change the number of cells in the matrix while in IB and not have to make any changes to the setRadix: method.
3.144.110.155