Developing Java Applications: A Tutorial | Creating a Custom View Class

Creating a Subclass of NSView

If you have completed the tutorial so far, you're done. You need not go any further--unless you would like to try a more interesting and challenging variation of the TempImageView class you created earlier. In this "extra credit" part of the tutorial, you will create a class for objects that know how to draw themselves and know how to respond to user events. These classes are custom subclasses of NSView.

Custom subclasses of NSView are ususally constructed differently than subclasses of other Application Kit classes because the custom NSView subclass is responsible for drawing itself and, optionally, for responding to user actions. Of course, you can do custom drawing in a subclass that doesn't inherit directly from NSView, but usually instances of these classes draw themselves adequately. This section describes in general terms what you must do to create a custom NSView subclass.

Define a Custom Subclass of NSView

The differences are slight between the Interface Builder procedures for defining a direct subclass of NSView and for defining a subclass of any Application Kit class that inherits, directly or indirectly, from NSView. The following is a summary of the required procedure in Interface Builder:

  1. Drag a CustomView object from the Views palette and drop it in the window.
  2. Resize the CustomView object to the dimensions you would like it to have.
  3. Select NSView in the Classes display of the nib file window, choose Subclass from the Class menu, and name your subclass.
  4. Add any necessary outlets or actions.
  5. Assign the class you defined to the CustomView object.

    Do this by selecting the object and selecting the class in the Classes display of the inspector.

  6. Make any necessary connections.
  7. Generate the "skeletal" .java file; before you choose the Classes>Create Files command, be sure to select first the class and then Java in the Attributes display of the inspector.

If you are unsure how to complete any of these steps, refer to the Defining the Custom View Subclass and Connecting the View Object sections of this tutorial.

Implement the Code for a Custom NSView

You can implement your custom NSView to do one or two general things: to draw itself and to respond to user actions. The basic procedures for these and related tasks are given below.

Important
The information provided in this section barely scratches the surface of the concepts related to NSView, including drawing, the imaging model, event handling, the view hierarchy, and so on. This section intends only to give you an idea of what is involved in creating a custom view. For a much more complete picture, see the description of the NSView class in the Objective-C API reference.

Drawing

All objects that inherit from NSView must override the drawRect method to render themselves on the screen. The invocation of NSView's display method, or one of the display variants, leads to the invocation of drawRect . Before drawRect is invoked, NSView "locks focus," setting the Window Server up with information about the view, including the window device it draws in, the coordinate system and clipping path it uses, and other PostScript graphics state information.

In the drawRect method, you must write the code that transmits drawing instructions to the Window Server. The drawRect method has one argument: the NSRect object defining the area in which the drawing is to occur (usually the bounds of the NSView itself or a subrectangle of it). The range of options the Java Yellow Box APIs provide is currently more limited than on the Objective-C side, which has the whole suite of PostScript client-side functions and operators. For drawing in Java, you can use the following classes:

Invalidating the View

With each cycle of the event loop, the Window Server ensures that each NSView in a window that requires redrawing is given an opportunity to redisplay itself. Besides implementing drawRect to draw your custom NSView, your application must indicate that an NSView requires redrawing when data affecting the view changes.

This indication is called "invalidation." Invalidation marks an entire view or a portion of a view as "invalid," and thus requiring a redisplay. NSView defines two methods for marking a view's image as invalid: setNeedsDisplay , which invalidates the view's entire bounds rectangle, and setNeedsDisplayInRect , which invalidates a portion of the view.

You can also force an immediate redisplay of a view with the display and displayRect methods, which are the counterparts to the methods mentioned above. However, you should use these and related display ... methods sparingly, and only when necessary. Constant forced displays can markedly affect application performance.

You should never invoke drawRect directly.

Event Handling

If an NSView expresses a willingness to respond to user events, it is made the potential recipient of any event detected by the window system. The view then just must implement the appropriate NSResponder method (or methods) that correspond to the event the view is interested in. (NSView inherits from NSResponder.)

What this means in practical terms is that an NSView must at a bare minimum do two things:

See the NSResponder and NSEvent class descriptions in the API reference for further information.

An Example

The TemperatureView class is similar to the TempImageView class implemented in the second part of the tutorial. Instead of displaying a different image when the temperature changes to a certain range, it draws a circle of a different color. To illustrate basic event handling, the TemperatureView class changes the thickness of the view's border each time the user clicks the view.

/* TemperatureView */

import com.apple.yellow.application.*;
import com.apple.yellow.foundation.*;

public class TemperatureView extends NSView {
    protected NSBezierPath sun;
    protected int temperature;
    protected int thickness;

    static public final int SpringSun=0;
    static public final int SummerSun=1;
    static public final int WinterSun=2;

    public TemperatureView(NSRect frame) {
        super(frame);

        float shortest = frame.width() >= frame.height()?frame.height():frame.width();
        NSRect rect;
        NSColor color;

        shortest *= 0.75;
        rect = new NSRect(((frame.width() - shortest) /2), 
                         ((frame.height() - shortest) /2), shortest, shortest);
        sun = NSBezierPath.bezierPathWithOvalInRect(rect);
        thickness = 1;
    }

    public void drawRect(NSRect frame) {
        NSColor color;
        if (temperature == WinterSun) {
            color = NSColor.lightGrayColor();
        } else if (temperature == SummerSun) {
            color = NSColor.orangeColor();
        } else {
            color = NSColor.yellowColor();
        }
        color.set();
        sun.fill();
        NSGraphics.frameRectWithWidth(frame, (float)thickness);
    }

    public void tempDidChange(int degree) {
        if (degree < 45) {
            temperature = WinterSun;
        } else if (degree > 75) {
            temperature = SummerSun;
        } else temperature = SpringSun;
        setNeedsDisplay(true);
    }

    public void mouseDown(NSEvent e) {
        if (thickness == 3) {
            thickness = 1;
        } else {
            thickness++;
        }
        setNeedsDisplay(true);
    }


    public boolean acceptsFirstResponder() {
        return true;
    }

}

Previous | Next
© 1998 Apple Computer, Inc.