Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Programmer's Guide to MacApp / Part 2 - Working With MacApp
Chapter 26 - Working With Dependencies


Recipes--Dependencies

The recipes and sample code in this section demonstrate how to use dependencies and behaviors to synchronize two control views and how to use dependencies to synchronize multiple document views so that a change in the document's data is reflected in each view.

Recipe--Using Dependencies and Behaviors to Synchronize Control Views

The Home Brew Controls menu command in the DemoDialogs sample application displays a dialog box with several specialized control views, including a pair of synchronized horizontal scroll bars. After you click one scroll bar or drag its thumb, the thumb in the other scroll bar moves an equal distance in the opposite direction.

The Home Brew Controls dialog box provides an example of using dependency relationships and behavior objects to synchronize the behavior of two views. Although the same results could be obtained by subclassing and adding methods to the view classes involved, there are advantages to using behavior objects instead:

To use behavior objects to synchronize two control views, you perform these steps:

  1. Define a view hierarchy that contains the two views.
  2. Define a change-behavior class:

    • Override the DoEvent method to call the Changed method of the behavior's owner.

  3. Define an update-behavior class:

    • Override the DoBehaviorUpdate method to synchronize the behavior's owner with the object that generated the change.

  4. Set up the views, dependents, and behaviors:

    • Create the views.
    • Make each view a dependent of the other.
    • Add a change behavior to each view.
    • Add an update behavior to each view.

The sample code shown in this recipe is from the DemoDialogs application.

Define a View Hierarchy That Contains the Two Views

The DemoDialogs application defines a window and view hierarchy for the Home Brew Controls dialog box in the file DemoDialogs.r. You can define a hierarchy for your views using a view-editing application. For more information, see "Working With View Resource Templates," beginning on page 425.

The following is a partial listing of the view resource for the Home Brew Controls dialog box:

resource 'View' (cHomeBrewControls, purgeable)
{  MAThreeOh, 
   {
   ViewSignatureAndClassname
      {'wind', 1501, "", 'WIND', enabled, noIdle, {}, MAThreeOh,

      // Some text omitted.

   ViewSignatureAndClassname
      {'sbar', 101, "", 'Scr1', enabled, noIdle, {}, MAThreeOh, 
      {20, 50}, {16, 300}, sizeVariable, sizeVariable, shown,
      doesntWantToBeTarget, handlesCursor, letsSubViewsHandleCursor,
      noCursorID, handlesHelp, letsSubViewsHandleHelp, noHelpID, 1, 
      NoDrawingEnvironment {}, 
      NoAdorners {}, 0, 
      ScrollBar {mHScrollBarHit, notHilited, notDimmed, sizeable,
               noInset, 700, dontPreferOutline, h, 0, -1000, 1000}, 
      NoSubviews},

   ViewSignatureAndClassname
      {'sbar', 101, "", 'Scr2', enabled, noIdle, {}, MAThreeOh, 
      {40, 50}, {16, 300}, sizeVariable, sizeVariable, shown,
      doesntWantToBeTarget, handlesCursor, letsSubViewsHandleCursor,
      noCursorID, handlesHelp, letsSubViewsHandleHelp, noHelpID, 1, 
      NoDrawingEnvironment {}, 
      NoAdorners {}, 0, 
      ScrollBar {mHScrollBarHit, notHilited, notDimmed, sizeable,
               noInset, 700, dontPreferOutline, h, 0, -1000, 1000}, 
      NoSubviews}
      }
};
Note that the two scroll-bar views have IDs 'Scr1' and 'Scr2'.

Define a Change-Behavior Class

The DemoDialogs application defines a change-behavior class as follows:

class TChangeBehavior : public TBehavior
{
   MA_DECLARE_CLASS;
public:
   virtual ~TChangeBehavior();      // Destructor.
   virtual void DoEvent(EventNumbereventNumber,
                   TEventHandler*source,
                   TEvent*    event); // Override.
};
The change-behavior class overrides the DoEvent method to intercept events sent to an object. It calls the object's Changed method, passing on the event number. Calling Changed causes the object to notify its dependents that it has changed. DemoDialogs implements the TChangeBehavior::DoEvent method as follows:

void TChangeBehavior::DoEvent(EventNumbereventNumber,
                        TEventHandler*source,
                        TEvent*     event) // Override.
{
   fOwner->Changed(eventNumber, event);
   Inherited::DoEvent(eventNumber, source, event);
}

Define an Update-Behavior Class

MacApp's TBehavior class uses the DoBehaviorUpdate method to let a behavior object respond when its owner's DoUpdate method is called. The TEventHandler::DoUpdate method calls the DoBehaviorUpdate method of the first enabled behavior.

The DemoText application defines the TUpdateBehavior class to synchronize the operation of two scroll-bar view classes. The class is defined as follows:

class TUpdateBehavior : public TBehavior
{
   MA_DECLARE_CLASS;
public:
   virtual ~TUpdateBehavior();         // Destructor.
   virtual void DoBehaviorUpdate(
                        ChangeID       theChange,
                        TObject*       changedObject,
                        TObject*       changedBy,
                        TDependencySpace*dependencySpace);
                                       // Override.
};
The DoBehaviorUpdate method is implemented as follows:

void TUpdateBehavior::DoBehaviorUpdate(
                        ChangeID       theChange,
                        TObject*       changedObject,
                        TObject*       changedBy,
                        TDependencySpace*dependencySpace)
{
   // If the change is a scroll-bar hit we're interested in...
   if (((theChange == mVScrollBarHit) ||
      (theChange == mHScrollBarHit)) && (changedObject != fOwner))
   {
      // Get the current setting from the changed object (the scroll
      // bar this behavior's owner is synchronized with). Set the
      // owner's setting to the opposite value.
      ((TScrollBar *)(fOwner))->SetLongVal(
            -((TScrollBar *)(changedObject))->GetLongVal(), kRedraw);
   }
   else
      Inherited::DoBehaviorUpdate(theChange, changedObject,
                           changedBy, dependencySpace);
}
When a user clicks a scroll bar, this method gets the setting of the scroll-bar view that changed and sets the synchronized scroll bar to the opposite value.

Set Up the Views, Dependents, and Behaviors

The DemoDialogs application sets up its synchronized scroll bars in the TTestApplication::MakeHomeBrewDialog method, implemented as follows:

void TTestApplication::MakeHomeBrewDialog(CommandNumber aCommandNumber)
{
   TWindow *aWindow;
   TScrollBar *firstScrollBar, *secondScrollBar;
   TChangeBehavior *aChangeBehavior;
   TUpdateBehavior *anUpdateBehavior;

   // Create the window and view hierarchy. Get references to the two
   // scroll bars and make them depend on each other.
   aWindow = gViewServer->NewTemplateWindow(
                              (short)aCommandNumber, NULL);
   FailNIL(aWindow);
   firstScrollBar = (TScrollBar *)(aWindow->FindSubView('Scr1'));
   FailNIL(firstScrollBar);
   secondScrollBar = (TScrollBar *)(aWindow->FindSubView('Scr2'));
   FailNIL(secondScrollBar);
   firstScrollBar->AddDependent(secondScrollBar);
   secondScrollBar->AddDependent(firstScrollBar);

   // Add a change-behavior object to each scroll-bar view.
   aChangeBehavior = new TChangeBehavior;
   aChangeBehavior->IBehavior(kNoIdentifier);
   firstScrollBar->AddBehavior(aChangeBehavior);
   aChangeBehavior = new TChangeBehavior;
   aChangeBehavior->IBehavior(kNoIdentifier);
   secondScrollBar->AddBehavior(aChangeBehavior);

   // Add an update-behavior object to each scroll-bar view.
   anUpdateBehavior = new TUpdateBehavior;
   anUpdateBehavior->IBehavior(kNoIdentifier);
   firstScrollBar->AddBehavior(anUpdateBehavior);
   anUpdateBehavior = new TUpdateBehavior;
   anUpdateBehavior->IBehavior(kNoIdentifier);
   secondScrollBar->AddBehavior(anUpdateBehavior);

   aWindow->Open();
}  // TTestApplication::MakeHomeBrewDialog
This method uses MacApp's global view server object to create the window and view hierarchy. It then extracts references to the two scroll-bar views to be synchronized and adds each as a dependent of the other. It then adds a TChangeBehavior object and a TUpdateBehavior object to each scroll-bar view.

Note
MacApp's sample applications demonstrate many useful features, but they do not always include the failure handling a commercial application would require. For example, the MakeHomeBrewDialog method should include failure-handling code so that if either of the scroller subviews cannot be found (and thus one of the calls to FailNIL causes a failure), the window will be freed.
When a user clicks in one scroll-bar view or drags its thumb, MacApp calls the view's HandleEvent method. The HandleEvent method calls the DoEvent method, which is intercepted by the view's change behavior. The TChangeBehavior object's DoEvent method calls the Changed method for its owner, the scroll-bar view where the user clicked.

Calling the object's Changed method generates a call to the DoUpdate method of each of its dependents. The TUpdateBehavior object of the second scroll-bar view intercepts the DoUpdate call in its DoBehaviorUpdate method and synchronizes its scroll-bar view with the user-changed scroll bar.

Using Dependencies to Update a Document's Views--
An Outline

In the model/view/controller paradigm, a controller notifies its views when the data in the underlying model changes. You can use dependencies to implement a similar organization between a document, its data object, and one or more views that display that data.

For example, an application could use this approach for a document that is capable of displaying its data as a spreadsheet, a chart, or a text table. When a user saves changes made in one view, the document object calls the data object's Changed method. This generates a call to the DoUpdate method of each open view, allowing the views to redraw themselves to reflect the new data.

To use dependencies to ensure that multiple views are updated when a document's underlying data changes, you follow these general guidelines:

  1. Define a data class, TYourData, to store the data to be displayed.
  2. Define a change ID constant that indicates a view should redraw itself:

    const ChangeID mRedraw = 600;
  3. Define a view class (or classes):

    • Override the DoUpdate method. When the passed change ID is equal to mRedraw, cause the view to redraw itself (unless, perhaps, it is the view that generated the change). Drawing should involve asking the view's document for the current data.

  4. Define a document class, TYourDocument:

    • Add a data field with code like the following:

       TYourData*   fYourData;
    • Set fYourData to NULL in the constructor method. Allocate storage and initialize fYourData in the initialization method. Free it in the destructor method.
    • Define a method, AddDependentView, that adds a passed view as a dependent of the fYourData field, using code like the following:

       void TYourDocument::AddDependentView(TView* theDependentView)
      {
      fYourData
      ->AddDependent(theDependentView);
      }
    • In the DoMakeViews method, or any method that creates a view for the document, call the AddDependentView method for each created view that displays the data from fYourData.
    • Define a method, DataChanged, to be called whenever the user makes a change to the document's data. Use code similar to the following:

       void TYourDocument::DataChanged(TObject* changedBy)
      {
      fYourData->Changed(mRedraw, changedBy);
      }

      The DataChanged method calls the Changed method of the fYourData field. This results in a call to the DoUpdate method of each view that is a dependent of the data object, allowing the view to redraw itself to reflect the new data (if it isn't the view that generated the change).

      Note that you probably want to call DataChanged only when a data change is accepted, such as when a user changes text in a data-entry field and then tabs to another field or activates a different window, causing validation to be performed on the text. You don't necessarily want to call DataChanged while the user is in the midst of typing an entry, drawing a line, or so on.

Conclusion

While this outline leaves many implementation details to the reader, it should indicate some of the power provided by MacApp's dependency mechanism.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
25 JUL 1996