Building the Calculator’s Controller Class

It’s time to start thinking about the Objective-C object that will control our Calculator — that is, respond to button clicks, calculate the values that the user wants, and display the results. By convention, this kind of object, which performs behind-the-scenes work and communicates with the user interface, is called a Controller .

Controllers generally don’t have main event loops; instead, they perform actions in response to events that are received and interpreted by other objects. A good rule of thumb is to place as little code in your Controller as is necessary for it to do its job. If it is possible to create a second Controller that is used only for a clear, particular purpose, do so — the less complicated you make your application’s objects, the easier they are to debug. In addition to controlling the overall flow of the application, Calculator’s Controller will contain the code to perform the basic arithmetic and thus can be thought of as the computational engine or back end (albeit a very simple one) of the application.

Designing the Controller Class

Cocoa doesn’t provide you with a Controller class — it’s up to you to write one for your application. (IB and the AppKit are fabulous, but they can’t do everything for you — at least not yet!)

Before you start coding, it’s a good idea to sit down and think about your problem. What does the Controller have to do? What kind of messages will it need to respond to? What kind of internal state does it have to keep in order to perform those functions? Recall that we want our Calculator to allow a user to type in the sequence “2*5=” by clicking four buttons in a window and to display (in order) 2, 2, 5, and 10 in a text output area. Thus, for our Calculator, the answers are fairly straightforward.

Here’s what our Calculator must do:

  • Clear the display and all internal registers (value holders) when a “clear” button is clicked.

  • Allow the user to click a digit button on the numeric keypad and display the corresponding digit immediately after it is typed.

  • Allow the user to click a function button (e.g., “add”, “subtract”).

  • Clear the display when the user starts entering a second number.

  • Perform the appropriate arithmetic operation when the user presses the “equals” button or another function button.

Our Calculator must also maintain the following state to perform these functions:

  • The first number entered

  • The function button clicked

  • The second number entered

It turns out that to work properly, our Controller object needs two more pieces of information:

  • A flag that indicates when a function button has been clicked — if the flag is set, the text display area (which we’ll call readout) should be cleared the next time that a digit button is clicked, because the user is entering a second number

  • The location in the readout text display area where the numbers should be displayed

These bullets indicate that we are using Objective-C to create a simulation of a real, physical calculator. That’s what object-oriented programming is often about: constructing progressively better simulations of physical objects inside the computer’s memory, and then running them to get real work done. When the simulation is functionally indistinguishable from the real-life object being simulated, the job is finished.

Creating the Controller Class

Every Objective-C class, except NSObject, is based on (and inherits from) another class. The NSObject class itself is the most fundamental Objective-C class, because it defines the basic behavior of all objects and is at the root of all inheritance hierarchies. Because we don’t need any special behavior in our Calculator other than what is already defined in the AppKit, our Controller class will be a subclass of NSObject.

We’ll start building our Controller class by subclassing it from the NSObject class in IB.

  1. Click the Classes tab in IB’s Nib File window to view the AppKit’s object hierarchy.

  2. Scroll to the far left in the Classes pane using the horizontal scroller at the bottom of the Nib File window, and then select the NSObject class by clicking it. (See Figure 5-10.) You can also rapidly jump to the NSObject class by typing the word “NSObject” into the Classes pane’s Search field.

NSObject (root) class (left) and new Controller classes (right)

Figure 5-10. NSObject (root) class (left) and new Controller classes (right)

The NSObject class name is displayed in gray, which means that you can’t change any of its properties or built-in behaviors without subclassing it. So that’s what we’ll have to do.

  1. Click IB’s Classes menu item at the top of the screen, then choose Subclass NSObject, as shown in Figure 5-11 (or simply hit the Return key when NSObject is highlighted).

Classes menu in IB

Figure 5-11. Classes menu in IB

  1. A new class called MyObject will appear under NSObject in the class hierarchy.

  2. Change the name from “MyObject” to “Controller”, and hit Return. (See Figure 5-10.)

You’ve just created a new Objective-C class called Controller. Right now it doesn’t do anything different from the NSObject class. Next, we’ll give the Controller class some custom behavior by adding some outlets and actions.

Outlets and Connections

Cocoa uses a powerful system known as “outlets and connections” to give you an easy way to send messages between user interface objects such as windows, buttons, other controls, and your own custom objects. An outlet is simply an instance variable in an Objective-C class that has the type id and thus can store a pointer to an object. The value of this instance variable is usually set to the id of another object in the nib — that is, a user interface object. Thus, outlets normally point to interface objects.

When an outlet is set to store the id of another object in the nib file, IB calls this a connection. Cocoa maintains connections for you. When object specifications are saved in a nib file, the connections you set up between them in IB are saved as well. These connections are automatically restored when the nib file is loaded back into IB.

Outlets can also be given a specific type. When you do so, IB will give you a warning if you attempt to connect the outlet to an object that is not of that type (or a class of that type).

For example, suppose that you have two object specifications in a nib file: objects A and B. Suppose also that object A contains an outlet, or id variable, that points to object B. When Cocoa loads this nib file, it will first create new instances of object A and object B, then will automatically set the outlet in object A to point to object B; that is, it sets the outlet A to be the id of object B.

Outlets therefore give you an easy way to track down the ids of objects that are dynamically loaded with nib files. They are the mechanism that Cocoa provides for wiring up an interface in IB without writing any code.

Adding Outlets to an Object

There are two ways to add outlets to a class: either by entering them in IB’s Class Info dialog, or by hand, using an editor to type them into the class interface (.h) file for your class. In the latter case, you can choose IB’s Classes Read Files menu command to inform IB about the outlets that exist for the class. We’ll see how to add outlets using IB in this chapter and by hand in the next chapter.

After adding an outlet, you use IB to initialize where it points. You do this by setting up a connection from the object containing the outlet to the object to which you want it to point, and then choosing the outlet from the list of outlets in IB’s Connections Info dialog. When you make a connection between an outlet in an object and another object in IB, IB sets the instance variable in the first object to the id of the object to which it is connected. That’s all!

In the following steps, we’ll add and initialize an outlet called readout in IB.

  1. If necessary, select Controller in the Classes pane of IB’s Nib File window.

  2. Now type Command-1 to display IB’s Attributes Info dialog for the Controller class. The Info dialog window should be titled “Controller Class Info”.

  3. Make sure that the Objective C radio button is selected in the Attributes Info dialog, as shown in Figure 5-12.

  4. Click the Add button at the bottom of the Controller Class Info dialog, and the outlet called myOutlet will appear.

  5. Change the name of this outlet to “readout” by typing the new name followed by Return.

If you make a mistake, you can double-click the outlet to change the name again. You can also click the Remove button to remove an outlet. When you’re done, the Class Info dialog should look like the one on the right in Figure 5-12.

Creating an outlet in the Controller Class Info dialog

Figure 5-12. Creating an outlet in the Controller Class Info dialog

We’ll eventually set the readout Controller outlet to point to the Calculator’s text display area (NSTextField) object in the Calculator window. Then the Controller will be able to send messages to the NSTextField via the outlet.

Next, we’ll add action methods to the Controller class.

Adding Actions to the Controller

An action is a special type of Objective-C method. Action methods are special because they take a single argument called sender , the id of the object that sent the message invoking the action method. Using IB, we can arrange for an object’s action method to be invoked automatically in response to a user event, such as a button click, menu choice, or slider drag. Thus, an action method is an event handler .

In Chapter 3, we used the takeIntValueFrom: action method to make an NSTextField automatically take its value from the NSSlider object when the slider knob was moved (see Figure 3-16). Here, we’ll create our own action methods in the Controller class and arrange to have them invoked when the user clicks our Calculator’s buttons.

  1. Click the 0 Actions tab in the Controller Class Info dialog.

  2. Click the Add button at the bottom of the Controller Class Info dialog; the myAction: action will appear, as shown in Figure 5-13.

Creating an action (left) in the Controller Class Info dialog; four actions in Calculator (right)

Figure 5-13. Creating an action (left) in the Controller Class Info dialog; four actions in Calculator (right)

  1. Rename myAction: as clear: and hit Return.

  2. Add the clearall:, enterdigit:, and enterOp: actions to your Controller class in a similar fashion. You don’t have to type the colons (:) when renaming actions, because IB will automatically append them.

Notice that IB alphabetizes the actions as you add them. Your Controller Class Info dialog should now look like the one on the right in Figure 5-13.

In light of our discussion of the design of the Controller class, the function of these four actions should seem fairly self-evident. We’ll go over the details later.

Notice that there’s only one action to handle all of the digit button clicks (enterDigit: ) and only one action to handle all of the function buttons (enterOp: ). We’ll determine which digit or function button is clicked by using the single argument of these actions, the id of the message’s sender. By querying the sender of the action message, the enterDigit: and enterOp: methods can determine which digit or function button was clicked. The method will then perform the appropriate action. This is a much more economical means of method dispatch than creating a separate method for each button on our Calculator — it takes less code and it runs virtually as fast.

Creating the Controller Class Files

Now that we’ve set up an outlet and several actions for our Controller class, we need to tell IB to create the Controller.h class interface file and the Controller.m class implementation file. Then we’ll add the appropriate functionality (code) to these class files and eventually compile them with the Objective-C compiler. IB’s Create Files for Controller command in the Classes menu generates these files from the class specifications we made in the Nib File window and the Class Info dialog.

  1. Make sure the Controller class is selected in the Classes pane of the Nib File window.

  2. Choose Classes Create Files for Controller.

A sheet will unfold from the Nib File window enabling you to specify the filenames for the class files to be created, as shown in Figure 5-14. Because the name of the class is Controller, the default names for the class files are Controller.h and Controller.m. After creating these class files, IB will insert them into the Calculator project.

Saving the Controller class files created by IB

Figure 5-14. Saving the Controller class files created by IB

  1. Click Choose on the sheet to use the default filenames (Controller.h, Controller.m) and insert them into the Calculator project.

  2. Now click the PB icon in your Dock to see how these class files fit into your project.

The two new files should be located in the Classes group but may be located in the Other Sources group. If you like, you can move these files from one file group to another in PB’s Groups & Files pane; the organization is for your benefit only and is ignored by PB. When you have a large project, you may even want to create your own groups and subgroups of files.

These new Controller class files contain only a skeleton of what we want in the Controller class. To make our Controller work, we have to add some logic and write some Objective-C code.

  1. Click the Finder icon in your Dock and investigate which files have been created as part of your Calculator project — there are several!

These project files reside in your ~/Calculator folder. We recommend that you compare the files in the ~/Calculator folder in the Finder with those listed in the Groups & Files pane in PB.

Adding Code to Make the Controller Class Work

To make the Controller work, we need to understand a little bit about a four-function calculator. The basic four-function calculator has three registers: an X and a Y register, both of which hold numbers, and an operations register, which holds the current operation. The readout always displays the contents of the X register. Clicking a function button stores that function in the operations register and sets a flag. If the flag is set, the next time a digit button is clicked, the number in the X register is moved to the Y register and the X register is set to 0.

We’ll get the Controller class working in stages, testing them one at a time. Generally, this is a good approach to writing any program, large or small. Object-oriented programming makes it easy to test the individual parts, because they are all fairly self-contained.

First, we’ll get numeric entry and the clear keys working. Later, we’ll handle the arithmetic functions.

  1. Back in PB, click Controller.h in PB’s Groups & Files pane to open the file in PB’s main window, as shown in Figure 5-15. (If you double-click instead of single-click Controller.h, a separate editor-type window will open.)

Controller.h interface file in PB

Figure 5-15. Controller.h interface file in PB

Looking at the code in PB’s window (in Figure 5-15), note that the Controller class is a subclass of NSObject, as we specified in IB. Note also that Objective-C declarations have been generated for the outlet (readout) and the four action methods in the Controller class that you set up in IB.

Following is the Controller.h file. The lines generated by IB are shown in regular type, and the lines that you need to insert are shown in bold type.

/* Controller.h */

#import <Cocoa/Cocoa.h>

@interface Controller : NSObject
{
    IBOutlet id readout;    BOOL      enterFlag;
                      BOOL      yFlag;
                      int       operation;
                      double    X;
                      double    Y;
}
- (IBAction)clear:(id)sender;
- (IBAction)clearAll:(id)sender;
- (IBAction)enterDigit:(id)sender;
- (IBAction)enterOp:(id)sender;- (void)displayX;
@end

IB generated the first two (non-bold) lines because we subclassed NSObject to create theController class (importing <Cocoa/Cocoa.h> includes the NSObject class interface file, as well as the rest of the Cocoa classes). Because we added readout as an outlet in the Class Info dialog, IB also generated the IBOutlet id declaration for it (see the sidebar IBOutlet and IBAction). Finally, IB generated the four action method declarations because we added the four actions in IB’s Class Info dialog. Note that the single argument for all of these action methods, sender, was generated automatically.

  1. Insert the five new instance variables and one new method indicated by the lines shown earlier in bold type in the Controller.h file. We’ll discuss the new displayX “non-action” method a bit later.

  2. Save the Controller.h class file (Command-S).

  3. Still in PB, double-click Controller.m in the Groups & Files pane to open a new editor-type window with the Controller implementation code inside. (See Figure 5-16.)

Controller.m interface file in a PB editor window

Figure 5-16. Controller.m interface file in a PB editor window

Following is the Controller.m file. As with the Controller.h file, we list the lines generated by IB in regular type and the lines you need to insert in bold type.

/* Controller.m */

#import "Controller.h"

@implementation Controller

- (IBAction)clear:(id)sender 
{   X = 0.0;
                     [self displayX];
}

- (IBAction)clearAll:(id)sender 
{   X = 0.0;
                     Y = 0.0;
                     yFlag = NO;
                     enterFlag = NO;
                     [self displayX];
}

- (IBAction)enterDigit:(id)sender 
{   if (enterFlag) {
                       Y = X;
                       X = 0.0;
                       enterFlag = NO;
                     }
                     X = (X*10.0) + [ [sender selectedCell] tag];
                     [self displayX];
}

- (IBAction)enterOp:(id)sender
{
}
- (void)displayX
                  {
                     id s = [NSString stringWithFormat:@"%15.10g", X ];
                     [readout setStringValue: s];
                  }

@end

IB generated the line that imports Controller.h because every class implementation file must import its own interface file. Most of the other lines generated by IB are simply stubs for the action methods that we set up in the IB’s Class Info dialog. IB generates code in class files more for convenience than for any other reason.

  1. Insert the code shown above in bold type into the Controller.m file.

  2. Save the Controller.m class file (Command-S).

The Controller class sends messages to instances of the NSTextFieldCell and NSMatrix classes. In particular, the newly added displayX method displays the contents of the X register by sending the setStringValue: message to readout, the outlet that we created in IB. Later we’ll use IB to initialize readout to point to the NSTextFieldCell object (near the top of the Calculator window).

When a message is sent to an object of a class, the class interface file definition for that class should be #import-ed in the class definition. But #import statements for the NSTextFieldCell and NSMatrix class interface file definitions are not listed in the code we’ve shown. What’s going on? Fortunately, the #import Controller.h line in Controller.m, together with the #import <Cocoa/Cocoa.h> line in Controller.h, takes care of importing the NSTextFieldCell and NSMatrix class interface file definitions for us. In fact, they import all the Application Kit class definitions.

In Cocoa, the AppKit class headers are all precompiled, so it’s quite fast to import them all, provided that <cocoa/cocoa.h> is imported before any symbols are #define-d. (A precompiled header file has been preprocessed and parsed, thereby improving compile time and reducing symbol table size.) This is why IB inserts the #import <Cocoa/Cocoa.h> line in all class interface files it generates.

The clearAll: method in the Controller.m file sets the X and Y registers to 0.0 and the two flags to false, and then sends the displayX message to self (the Controller object itself ) to display 0.0 in the text display area. The clear: method is similar but only needs to set the X register to 0.0 and then redisplay. We’ll discuss the enterDigit: and enterOp: methods after we finish setting up the user interface and making all the connections.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.226.66