Creating a Color Meter Using Cocoa

06/15/2001

In this column weíre going to create a simple Cocoa application that will allow us to explore how we interact with interfaces and obtain information from user controls. This app is a standard Cocoa application -- a color meter -- rather than a document-based project.

The color meter we'll be making isn't complicated. It consists of four sliders -- a slider each for red, blue, green, and alpha (transparency) -- four text fields, and one "color well." So let's get started.

The interface

NSControl

In this project, I want to introduce you to Interface controls as a class, rather than as an image on the screen. Graphical interface devices in Cocoa are all descended from the abstract Application Kit class, NSControl.

These devices you are probably already familiar with in form -- buttons, sliders, scrollers, color wells, tables, text fields, and more -- but maybe not so familiar with in low-level function. I want to spend some time with the NSControl class to show you how you can take basic control over all the components of your interface. The methods of NSControl are the methods we use to obtain information from the interface such as text or numbers entered into a text field.

NSControl methods

The methods that will be of most use to us at this time are the ones that allow us to retrieve data from a control, and set the data contained in control. NSControl has several methods that allow us to do just this. These methods are called accessor methods, as they allow us to access the data owned by some object.

To get a number, or a string from a control, we use one of the following methods:

- doubleValue

- floatValue

- intValue

- objectValue

- stringValue

The method names identify what type of data they will return: double will return a number that is of type "double", float will return a float-typed number, and int an integer.

To get a string from the control we use stringValue. The objectValue method will return the object value contained in the controlís cell.

Keeping control values as objects makes it simple to move data around between different objects that might use that data, as most objects when taking number or string arguments require NSNumber or NSString data types.

To set the value of a controlís cell, we invoke the following methods:

- setStringValue:

- setDoubleValue:

- setFloatValue:

- setObjectValue:

- setIntValue:

For instance, if we had a text field in which we wanted to display a number, we would send the text field the following message:

[textField setDoubleValue:3.14159265358979323846];

The argument of each of the set-methods are of type indicated in the method name.

The naming scheme seen above with the accessor methods is standard throughout Cocoa. You will see these methods again and again in many Foundation and Application Kit classes, and has the advantage that you donít have to remember as much vocabulary to get stuff done. For example, NSString has these same methods, which allow one to extract an integer from a NSString typed variable. NSNumber also has a stringValue method that lets you return the a number as a string.

The implications of this naming scheme run deeper than simple convenience. Many nice features of the Cocoa frameworks will work only if this naming scheme is adhered to. We wonít talk about it now, but if you just canít wait, check out Key Value Coding, which is described in the NSKeyValueCoding informal protocol. The reference for this is found in the Foundation Framework class reference, NSKeyValueCoding (Objective-C).

NSControl also defines several other methods. There are methods that allow you to
format the text. There are methods that let you change the font with which text is
displayed within a control. You can enable or disable the control if you like by sending it a setEnabled:YES or setEnabled:NO message (remember, YES and NO is how Objective-C represents Boolean values). Every attribute of a control that you set in Interface Builder -- including placement, targets, and whatever you set in the Info panel -- you can change via code.

With the methods that are part of NSControl, you have total control over every aspect of your interface controls and components. If you want to see a full, detailed description of what NSControl has to offer, including all the methods that are a part of it, check out its class reference found here: NSControl (Objective-C)

Building our interface

Now that we have the means to work with our controls, let's build an interface and code it to do something fun.

The interface for the color meter consists of four sliders, four text fields that serve as labels, four text fields that dynamically display text, and one color well.

The interface for the color meter.

Text fields can be found on the Cocoa-Views palette. Our applicationís interface uses two of the ones found on this palette. On the right is a blank white rectangle. This is a text field that allows the user to type text into it. There is also a field that says "Informational Text".

These two text fields are both instances of the same class NSTextField. They differ only in the options that are set for display, font, and user interaction options. In fact, it is possible to turn these text fields into each other through the attributes info panel, or programmatically. In Cocoa, you have full control over every element of the interface.

Now, drag four each of these onto your main window; donít worry about organizing them until we get the sliders out. The sliders can be found on the Cocoa-More Views
palette. You will see several varieties of sliders here. Again, the situation is the same as before with the text fields. You can change the appearance of one slider to look like any of the ones you see here, both in the code and the Attributes info panel.

The last thing we need from the current palette is the color well object. Drag this somewhere onto your interface as well. You can see in the image below how I set up my interface. Organize yours in a similar fashion.

Designers' aids

Interface Builder provides several tools to help you quickly align your control objects in accordance to the Aqua Human Interface Guidelines. We talked about one in a previous column, and that is the pop-up guidelines.

By default, when you move an object next to another object, there will be certain "sweetspots" corresponding to ideal object separation distances and alignments. You can augment this feature by turning on Layout Rectangles from the Layout menu, or by pressing Command-L. Layout rectangles provide the added convenience that objects will momentarily lock into place as you move them near the Aqua sweetspots.

You can find Aqua sweetspots more easily by turning on "Layout Rectangles."

If you desire even more precise control over the placement of your interface objects, you can move selected objects around using the arrow keys. A single key stroke in any direction will move the selected object one pixel in that direction. If you hold the Option key and move the mouse cursor over another object, you will be provided information about how many pixels separate (in the applicable dimension) the selected object from the object under the pointer. The selected object can, in this case as well, be moved using the arrow keys.

When nudging, if you hold the Option key and move the mouse cursor over another object, you will find information about how many pixels separate (in the applicable dimension) the selected object from the object under the pointer.

Finally, if you prefer more automation, there's an alignment tool, found in the Tools menu, which has several options for aligning a group of selected objects, much like in any drawing program.

For more automation, use the alignment tool.

Setting interface object attributes

Before we continue with the interface, let's change the behavior of the sliders. Open the inspector to view the attributes of the objects. The inspector allows you to set many of the runtime attributes and characteristics of certain interface objects. The options are different for each class of object.

The Info panel also allows you to change the way interface objects adapt to window resizing. You can also view any connections to the object, and even associate a custom class with the object.

Our purpose here is to change the way the in which sliders send action methods. The default option is to send a message to whomever is listening after the person operating the slider has stopped dragging the indicator about.

We want the sliders to continuously send action messages as the slider is dragged to update the color displayed in the color well. This is accomplished by enabling the "Continuous" option in the attributes panel of the inspector.

Another thing we can change is the bounds of the slider, that is, the smallest
and largest values our slider will take on at its extremes. Because the way weíre going to create a custom color is to instantiate an NSColor object with values of red, green, blue, and alpha (transparency), which takes value between zero and one, we want to set the minimum value of all of our sliders to zero and the maximum value to one. "Current" is the value the control is set at initially. You can set this to whatever you want (as long as it's within the bounds); Iíve set mine to .5, for no reason in particular.

Weíre not going to change anything about the way our text fields behave, but if you look at the attributes for one of our text fields, youíll see that you can change the text font, color, size, alignment, and editability. You can, of course, change these if you like; I just left them as they were to make things easier.

The controller object

In object-oriented programming, there are several paradigms for organizing objects and classes in an application. Our application is simple so our needs for organization are simple.

We will employ what is known as the Model-View-Controller paradigm. "View" is the interface of your application -- the sliders, text fields, and color well in our
application. "Model" is the data model for our application.

Our application doesnít deal with data at all, so we wonít have a model object. "Controller" is an object that that links your interface to the data model. In this paradigm, the interface should not know anything about how the data is stored and managed, and the model should have no knowledge of how to display the interface. It's the controller objectís responsibility to communicate between these two realms. Let's make our controller object.

From the classes tabbed view, find the NSObject class in the list of available classes. We want to create a subclass of it. Do this by selecting "Subclass" from the Classes menu, and name it "Controller".

Now add the objects and actions shown in the picture below with the same names. New objects and actions can be created from the same Classes menu.

Adding objects and actions.

The next step is to instantiate our new class so we can connect the outlets and actions to their appropriate interface objects. The names should explain how to wire them up. Remember, wire actions by starting at the interface object -- a slider -- and control-drag to the instance of the Controller. Objects are wired in the opposite direction. Remember, wire in the direction messages would travel.

Once you have made all the connections, you need to add the interface and implementation files of our controller class to our project in Project Builder. This is done easily enough by going back to the Classes tab, selecting the Controller class, and then selecting Create Files from the Classes menu.

Close Interface Builder and return to Project Builder.

Coding

The way our color meter application operates is as follows: The user of the application will be able to slide the color sliders about for each component of the final color: red, green, blue, and alpha. As the user manipulates the controls, the color will update in real time. Additionally, the value of the slider will be displayed in the text field to the right of each slider. Now, all we have to do is supply the code that will create a color in the color well that corresponds to the values of red, green, blue, and alpha.

The first thing we want to do is declare four instance variables of type "float" that store values of each component of the final color. In the interface file Controller.h insert after the IBOutlet instance variables the following four variables:

float redValue;
float greenValue;
float blueValue;
float alphaValue;

Additionally, we want to add a method to this class that will update the color displayed in the color well. That method is

In the first line, we are asking the sender who is invoking the setBlue method for the floatValue of its data, and storing that in blueValue. In this case, the sender can be one of two objects in the interface.

If you recall, we wired both the Blue slider control and the Blue text field to the same action. Depending on the user's actions, either of these objects can be the sender of the message invoking this method. This is nature of all IBAction methods. That is, the action method argument is the sender object. This makes it easy to get information from the interface, while maintaining a good deal of flexibility in our code, as was demonstrated here. It also means we donít have to write an additional action methods for additional controls to interact with. We can add as many controls as we want to change the blue color value and wire them all to this same action. This is also the power of polymorphism, as all Cocoa controls respond to the floatValue method in the same way, by returning a float-typed number.

The purpose of the middle two lines is to synchronize the values displayed in the slider and the text field. Thus, if the slider was the sender, the text field would take on the appropriate value, and vice versa. The setFloatValue: method does the exact opposite as floatValue. Where floatValue returns the value of the control as a float, setFloatValue: takes a float argument and sets the controls data value to the argument.

The last line simply tells the controller object (self, since this method is sending a message from itself to itself) to update the color displayed in the color well. We could have put the appropriate code to update the color in the color well in each of the "set...methods", but one of the goals of object-oriented programming is to reuse as much code as possible. Thus, by extracting the update code from each method and placing it in its own method, which is called by the "set...methods", we are reusing that code. The clear benefit of this is that if we want to change that code for whatever reason, we need only do it in one place, rather than four.

The setGreen:, setRed:, and setAlpha commands are exactly the same as setBlue:, except everywhere you see Blue, you replace it with Red, Green, or Alpha; and everywhere you see blue, you replace it with red, green, or alpha.

Since updateColor is not an Interface Builder action method like the others, we have to define it manually (that's why we had to add it to the interface file earlier as well). Here is the code for the updateColor: method:

What we do first is create a new color by calling the colorWithCalibratedRed: green: blue: alpha: method and store the returned color object in the aColor variable, which is declared locally on the same line. The NSColor method we invoke here is one of many that are available for creating colors. There are some NSColor class methods that return a present color, such as redColor. If you want to read more about NSColor, check out its class reference page here: NSColor (Objective-C).

Finally, in the same way we display a number in a text field using setFloatValue:, we display a color in the color well object by sending it a setColor: message with the newly created color object.

The end

And thatís it! Compile your code (hopefully, you wonít have any errors), run it, and play around with it. If you want to see another cool freebie in Cocoa, click on the color well and before your very eyes watch the system color-picker appear. Now we didnít add any code to update the text fields and sliders to reflect the new color being set in the color well through the picker, but I encourage you to read the class references for the classes we talked about, and see if you canít make that work (if you get stuck, write it up as a Talk Back and we'll figure it out online).

After you compile the code and run the application, you should have an interface that looks like this.

In the next few columns, Iím going to be doing less complete applications, and talk more about individual classes from the Foundation framework that deal with strings, numbers, and collections. Hope you enjoyed this one. Talk to you next time!

Michael Beam
is a software engineer in the energy industry specializing in seismic application development on Linux with C++ and Qt. He lives in Houston, Texas with his wife and son.