Important: The information in this document is obsolete and should not be used for new development.
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:
- Behavior classes usually have fewer fields and methods than event-handling classes (especially view classes), so they tend to be small, simple, and efficient.
- Because view classes contain so much code to handle their view functions, it's good to avoid gumming them up with additional responsibilities.
The sample code shown in this recipe is from the DemoDialogs application.
- Define a view hierarchy that contains the two views.
- Define a change-behavior class:
- Override the
DoEvent
method to call theChanged
method of the behavior's owner.
- Define an update-behavior class:
- Override the
DoBehaviorUpdate
method to synchronize the behavior's owner with the object that generated the change.
- 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.
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 fileDemoDialogs.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 theDoEvent
method to intercept events sent to an object. It calls the object'sChanged
method, passing on the event number. CallingChanged
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'sTBehavior
class uses theDoBehaviorUpdate
method to let a behavior object respond when its owner'sDoUpdate
method is called. TheTEventHandler::DoUpdate
method calls theDoBehaviorUpdate
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 theTTestApplication::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::MakeHomeBrewDialogThis 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.
When a user clicks in one scroll-bar view or drags its thumb, MacApp calls the view's
- 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.HandleEvent
method. TheHandleEvent
method calls theDoEvent
method, which is intercepted by the view's change behavior. The TChangeBehavior object'sDoEvent
method calls theChanged
method for its owner, the scroll-bar view where the user clicked.Calling the object's
Changed
method generates a call to theDoUpdate
method of each of its dependents. The TUpdateBehavior object of the second scroll-bar view intercepts theDoUpdate
call in itsDoBehaviorUpdate
method and synchronizes its scroll-bar view with the user-changed scroll bar.Using Dependencies to Update a Document's Views--
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.
An OutlineFor 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 theDoUpdate
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:
- Define a data class,
TYourData
, to store the data to be displayed.- Define a change ID constant that indicates a view should redraw itself:
const ChangeID mRedraw =600
;- 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.
- Define a document class,
TYourDocument
:
- Add a data field with code like the following:
TYourData
*fYourData
;- Set
fYourData
toNULL
in the constructor method. Allocate storage and initializefYourData
in the initialization method. Free it in the destructor method.- Define a method,
AddDependentView
, that adds a passed view as a dependent of thefYourData
field, using code like the following:
void TYourDocument::AddDependentView(TView* theDependentView)
->AddDependent(
{
fYourDatatheDependentView
);
}- In the
DoMakeViews
method, or any method that creates a view for the document, call theAddDependentView
method for each created view that displays the data fromfYourData
.- Define a method,
DataChanged
, to be called whenever the user makes a change to the document's data. Use code similar to the following:
voidTYourDocument
::DataChanged
(TObject* changedBy)
{
fYourData
->Changed(mRedraw, changedBy);
}The
DataChanged
method calls theChanged
method of thefYourData
field. This results in a call to theDoUpdate
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 callDataChanged
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.