3.6 More Complex Associations


Realistic object-oriented systems involve associations among numerous objects from a variety of classes. Commercial software systems might use many tens of classes and hundreds or thousands of objects. The examples involved here do not approach that scale. However, several additional classes will be introduced that use the same techniques and illustrate how such complex and realistic systems are structured.

A more realistic version of the Frame class employing association will be devised. Association is necessary because the interface of the Frame class would become unbearably complex if all of the rich functionality of a window were captured directly and exclusively in this one interface. For example, only two shapes are currently drawable (a line and a circle). But many more shapes are commonly available, including ovals, rectangles, splines, and polygons, in addition to a variety of properties that can be defined for each shape, such as its color, line thickness, line pattern, fill pattern. Clearly, attempting to control all of these details through the Frame interface alone would create an extremely long and complex interface. Furthermore, the Frame class also needs to be extended to include a wide range of interactive elements through which the user can manipulate the user interface. These interactive elements include buttons, editable text, sliders, check boxes, scrollable lists, radio buttons, and others. Even the addition to the Frame class of one or two methods for each of these interactive elements is clearly a step toward a large, unruly interface.

The responsibilities for the graphical and interactive elements of a window will be partitioned among three associated classes:

  • Frame: a rectangular area on the display screen that may be moved and resized.
  • Canvas: an area within a Frame for drawing text and graphics and responding to mouse movements
  • Panel: an area within a Frame that contains interactive elements.

The Frame class is simplified so that it retains only those responsibilities not assigned to the Canvas and Panel classes. The Canvas class assumes the responsibilities for all drawing functions. Mouse events with a Canvas area are now associated with the Canvas and not the Frame. The Panel class assumes all responsibilities for managing interactive elements.

The definition for the revised Frame class and the new Canvas are shown below. The Panel class will be presented next.

The Frame Class (Version 4)
class Frame {                          // Version 4
     private:  
          // encapsulated implementation goes here
     public:
           Frame(char* name, Location p, Shape s);      // exact description
           Frame(char* name, Shape s, Location p);      // exact description
           Frame(char* name, Location p);               // default shape
           Frame(char* name, Shape s);                  // default location
           Frame(char* name );                          // name only
           Frame();                                     // all defaults;
     int   IsNamed(char* aName);                // is this your name?
     void  MoveTo(Location newLocation);        // move the window
     void  Resize(Shape    newShape);           // change shape
     void  Resize(float factor);                // grow/shrink by factor
          ~Frame();
   };

Notice that all of the methods related to drawing (e.g., DrawText, DrawLine) have been removed from the Frame class, as the drawing methods are to be placed in the newly defined Canvas class. The methods that remain in the Frame class are those that specifically pertain to the definition and management of the Frame itself. In this way, the Frame class presents an abstraction of a bordered area on the user's display that can, under program control, be changed in position and shape. The contents of the Frame are defined by what other classes (like the Canvas and Panel classes) add to the Frame.

The Canvas Class
class Canvas {
     private:  
          // encapsulated implementation goes here
     public:
           Canvas(Frame& fr, char* nm, Location loc, Shape sh);
     int   IsNamed(char* aName);                // is this your name?
     void  DrawText(char *text, Location loc);  // display text string
     Shape TextSize(char *msg);                 // get shape of string
     void  DrawLine(Location p1, Location p2);  // draw line segment
     void  DrawCircle(Location center, 
                      int radius);              // draw circle
     void  Clear();                             // erase entire Frame contents
     void  Clear(Location corner, 
                 Shape rectangle);              // erase rectangular area
          ~Frame();
   };

Notice that the methods for drawing text and graphical shapes have been moved to the Canvas class. Also notice that a Canvas object can only be constructed by associating it with a single Frame. Thus, it is not possible to have a Canvas object that is in two different Frames, nor is it possible to create a Canvas object that is not associated with a Frame.

The association between a Canvas object and a Frame object is established by the Canvas's constructor. By contrast, other examples have shown that the association between two objects can be created by a method other than a constructor (e.g., the ConnectTo methods in the Counter and Clock classes). The constructor can be used to establish a static association (one that is created when the Canvas object is created and one which does not change during the lifetime of the Canvas object). In other cases, the associations may be more dynamic. For example, a given Message object can be displayed in different Frames at different points in time during the Message object's lifetime. Dynamic associations are more naturally expressed as non-constructor methods so that these methods can be called during the lifetime of the object to change the association.

Three additional classes defining interactive control elements are introduced. By using objects of these new classes, a more interactive user interface can be created. These three classes are:

  • Panel: mentioned earlier, it is an area within a Frame that contains interactive elements, like Button and TextBox objects.
  • Button : captures the abstraction of a simple, pushable, named button that the user can "push" by clicking within the boundary of the button's displayed image. The button is displayed as a rectangle containing the button's name.
  • TextBox: provides a mechanism for the user to edit a passage of text that may be read subsequently by the program.

Together, these three classes provide basic controls for the user to enter data and trigger actions to be taken by the program.

The definition of the Panel class is given below. As in the Canvas class, the constructor for a Panel object takes a Frame object as a parameter. The location and shape of the Panel in the Frame with which it is associated is also required by the constructor. The overloaded Add method allows any number of Buttons and TextBoxes to appear in the Panel.

The Panel Class
class Panel {
 private:
        // hidden data
 public:
   Panel(Frame& fr, char *nm, Location loc, Shape sh);
   char* getName();
   void Add(Button& button);
   void Add(TextBox& tbox);
  ~Panel();
 };

The Button class is defined below. The Button class constructor requires that the name of the Button object be defined. This name is used in two ways. First, the name will appear on the user's display as the label on the Button's graphical representation. Thus, a Button object named "Start" will appear as a bordered rectangular box on the screen surrounding the word Start. Second, when the user "pushes" the Button (i.e., the user clicks within the bordered rectangular box corresponding to the Button) the function OnPush(char*) is called, where the name of the Button is passed as the argument. The OnPush(char*) function is a new function that is being added to the simple programming environment. Thus, it is possible for the programmer to determine which of several Buttons the user has pressed by using the argument to the OnPush function and the Button object's IsNamed method.

The Button Class
class Button {
 private:
                // hidden implementation
 public:
   Button(char* name, Location loc, Shape sh);
   int IsNamed(char* name);
 ~Button();
 };

The TextBox class, given below, allows the user to edit and/or enter data. Each TextBox appears to the user as a bordered rectangular area in which a typing cursor will appear when the mouse cursor is moved within the TextBox area. When this cursor is visible, the user may edit, erase, or add to any text that is visible in the TextBox. The TextBox will scroll long lines of text so that only a portion of the text may be visible at any one time. The current value of the TextBox may be set or queried by the program using the TextBox's methods SetText and GetText.

The TextBox is optionally constructed with a label which will appear to the left of the TextBox on the screen. The Shape of the TextBox must be wide enough to accomodate both the label and the length of the string that the user is expected to enter.

The TextBox Class
class TextBox {
private:
                // hidden implementation
public:
        TextBox( Location p, Shape s, char* label);
        TextBox( Location p, Shape s);
        TextBox( char* label);
        TextBox();
       ~TextBox();
  char* GetText();
  void  SetText(char* val);
};

A small system that uses all of the new classes is shown below. This system presents the user with a TextBox in which the user can enter a string and, when a button labelled Copy is pressed, the current contents of the TextBox are read by the program and written to a Canvas area. Notice that the Button and the TextBox are contained within (associated with) a Panel and that both the Panel and the Canvas are contained within (associated with) a Frame object. Also notice that the OnPush method uses the IsNamed method of the Button class to test the identity of the Button object that was pushed.

An Example System
Frame    window ("TestWindow", Location(100,100), Shape(500, 300));
Canvas   canvas (window, "DrawAreas",  Location(1, 1),  Shape(100, 100));
Panel    panel  (window, "Controls", Location(150, 10), Shape(300, 100));
Button   button ("Copy", Location(5, 5), Shape(50,30));
TextBox tbox    (Location(5,50), Shape(150,30), "Enter:");
char    *text;

void OnStart()        // called once when "Start" button pushed
{  canvas.Clear();
   panel.Add(button);
   panel.Add(tbox);
   text = (char*)0;
}
    
void OnPush(char *buttonLabel)
{  if (button.IsNamed(buttonLabel))
   {   canvas.Clear();
       canvas.DrawText(tbox.GetText(), Location(20, 20));
       text = tbox.GetText();
   }
}

void OnPaint()
{ canvas.DrawText(text, Location(20,20));
}

The Clock class can be extended to improve the Clock class's usability. The programmer may want to make use of several different Clocks (e.g., to time different events, to have different time intervals) and the programmer needs the flexibility of defining what actions should take place whenever a Clock causes a timer event. To allow for this flexibility, the Clock class is extended as shown below. Each Clock object is constructed with a name that may be used to uniquely identify the Clock. The Clock's interval may be given on construction or, whenever the Clock is stopped, by the SetInterval method. The Clock can be controlled by its Start and Stop methods. Finally, similar to a Button object, a Clock object has an IsNamed method that tests the name of the object against the character string passed as an argument. Notice that, as in the earlier defintion, the Clock may be connected to a Counter object. At the end of each time interval, a Clock object will either call the Next() method of a Counter object to which it is connected or, if it is not connected to a Counter object, it will call the OnTimerEvent() function as described below.

Revised Clock Class
class Clock {
private:
                // hidden implementation
public:
  Clock (char* name, int interval=1000);
  void SetInterval(int newInterval);
  void Start();
  void Stop();
  int  IsNamed(char* name);
  ~Clock();
};

An extension of the simple programming environment allows the programmer to define what action to take when a given Clock generates a timer event. The OnTimerEvent function is redefined to take a character string argument that is the name of the Clock which generated the timer event.

Due to the changes in the Frame class, the Message class must also be slightly modified. A Message object is no longer displayed directly in a Frame; instead it is displayed in a Canvas. This small change is shown in figure below.

The Modified Message Class
class Message {
private:
                //encapsulated implementation
public:
                 
    ...
    void  DisplayIn (Canvas&   whichCanvas);
    ...
};

The use of a Clock object, an object of the modified Message class, and the modified OnTimerEvent function is shown in the example below. This program is a revised version of the Blinking Text Hello World program. In this program a Clock object is used to control the blinking of the text contained in a Message object. The Clock's interval is defined in the constructor to be 500 milliseconds. The Clock is started in the OnStart method and its timer events are responded to by the OnTimerEvent method that tests the name passed as argument against the name of the Clock object.

An Example Using Clocks
Frame   window   ("Message Test", Location(100,100), Shape(200,200));
Canvas  canvas   (window, "Message Area", Location(10,10), Shape(180,180));
Message greeting ("Hello World!", Location(20,20));
Clock   timer    ("timer", 500);
int     onoff;
  
void OnStart() 
{  greeting.DisplayIn(canvas);
   greeting.Draw();
   onoff = 1;
   timer.Start();
};


void OnTimerEvent(char* clockName)
{
   if( timer.IsNamed(clockName) )
   {   if (onoff) { greeting.Clear(); onoff = 0; }
       else       { greeting.Draw();  onoff = 1; }
   }
}

void OnPaint() 
{ if (onoff) greeting.Draw();
}

Tasks

  1. Using the Counter, Message, and Frame classes write a program that implements a simple timer. Use the OnTimerEvent function to update the Counter object. Use a left mouse button click to control whether the OnTimerEvent updates the Counter object. The first left mouse button click "starts" the timing (subseqent OnTimerEvent function calls update the Counter object by using the Counter object's Next method). The second left mouse button click "stops" the timing. Thereafter, alternate left mouse button clicks start and stop the timing.
  2. Button Timer. Using the Clock, Counter, Message, Button, Frame, and other classes write a program that implements a one button, simple timer. Use a Clock object to trigger the call to the OnTimerEvent function that will update the Counter object. Use a Button object, labelled "Control", to control whether the OnTimerEvent updates the Counter object. Pushing the Button for the first time "starts" the timing (subsequent OnTimerEvent function calls update the Counter object by using the Counter object's Next method). Pushing the Button a second time "stops" the timing. Thereafter, pushing the Button alternately starts and stops the timing.
  3. Construct a system that displays three Messages, with each Message displaying the value of a different Counter. The Counter in "Tens" should be incremented once for every ten times the counter in "Units" is incremented; similarly for "Hundreds" and "Tens." Use three other Messages to identify the three changing values as "Units," "Tens," and "Hundreds."
  4. Construct a system that has a one-second Clock displayed on the screen that can be both started and stopped by the user clicking appropriately labeled buttons.

  5. Construct a system that has two Clocks, one operating on a one-second interval and one on a 0.1-second interval. Each Clock is connected to a different counter and each can be controlled by start and stop buttons. Both counters appear on the screen with appropriate labels.

  6. Construct a system that has a one second Clock displayed on the screen that can be started, stopped, and reset. To reset the Clock, the user must first stop the Clock, enter a new time in the TextBox, press the reset button, and then press the start button.



©1998 Prentice-Hall, Inc.
A Simon & Schuster Company
Upper Saddle River, New Jersey 07458

Legal Statement

 

 

 

ÿ