March 92 - A Clear View of Ten Behaviors
A Clear View of Ten Behaviors
G. Gordon Apple
In the beginning were the system and the toolboxes.
Then came object programming and the MacApp framework . Subclasses were brought forth abundantly after their kind, and views-types proliferated. Their number and complexity intimidated many, preventing them from approaching the framework.
Now there are behaviors that allow generic views to take on characteristics and capabilities as needed. This article discusses a set of drawing (actually, object manipulation) behaviors, showing how behaviors can provide an enhanced level of functional encapsulation and program modularity.
Out of the Chaos
MacApp beginners, myself included, tend to want to dump all their code into subclasses of TView. When I first read the Programming with MacApp books (Wilson, Rosenstein, and Shafer), I was surprised by the use of "helper" classes where the subview mostly consisted of "diversionary" or "deflection" methods that simply pass the calls to the helper class.
Most of these deflection methods involved event handling; menu commands or mouse downs, drawing, highlighting, and cursor control. Therefore, I was pleasantly surprised when behaviors were introduced to provide exactly that type of support in a generic TView or any subclass of TView (actually, any TEventHandler). I set out to see what behaviors might imply in code organization.
The program I chose was originally done in Pascal with MacApp 2 and was based on the ExampleDraw program in the Programming with MacApp book. A good place to start seemed to be the C++ version of the same. Unfortunately, it was also written for MacApp 2, so major code surgery lay ahead. This gave me the opportunity and the excuse to totally revamp the code organization to use behaviors, one of MacApp 3's most powerful and versatile features.
The program document consists mainly of a list of shape objects that can be drawn on the screen or the printed page. I extended the architecture to include more general items such as Views, PICTs, and now MooVs. However, they are all still manipulated as if they are draw objects.
I decided to create the behaviors and partition the code by functionality rather than by common elements or code similarity. I hoped this would result in more versatile modules. I took the approach of a creator itemizing the functionality (behaviors) imparted to the creations. This resulted in:
The Ten Behaviors
- Those on the list, show yourselves within the View in listed order and whenever your domain is revealed and requires updating. (TDrawItems)
- Those who are touched or who are within a selected area shall themselves be selected and appropriately adorned to highlight their status. (TSelectItems, TItemsSelectionAdorner)
- Those who are selected, move right, left, up or down when commanded, remembering your roots in case the command be undone. (TMoveItems)
- The selected, take on the indicated colors, patterns, and sizes which have been selected from abundant menus and palettes. (TModifyItems)
- The selected, your extremities shall be grabbed and your dimensions adjusted. (TResizeItems).
- The selected, the order of your appearance may be advanced or retarded, even to the beginning or the end. (TReorderItems)
- New items shall be created in your midst, cloned from the palette and sketched in accordance with current parameters. (TCreateItems)
- The selected, clone yourselves and give up your clones to the temporary holding area. (TCopyItems)
- The selected, hide until freed, or clone yourselves as before, or accept as your own the clones arriving from the temporary holding area. (TCutPasteItems)
Well, that should about do it. But didn't I say "Ten Behaviors?" What about those items in limbo, the temporary holding area where everybody gets clipped and bored? Not much going on there. Only the first behavior from this list is needed. Also, the View in limbo is not quite so pure in that diversion methods were not provided for interfacing to the outside world. Word and pictures were provided for, but not our own private I/O. Therefore, the slightly impure view (a subclass of TView) simply deflects the necessary actions to the tenth behavior:
- Tell, when asked, what types of items can be supplied from the desk scrap, give of such when asked, and tell how much viewing space they require. Also, give up the data to the desk scrap before sleeping or dying. (TClipboardItems)
Using the Behaviors
The "Ten Behaviors" provide a display, drawing, and editing environment that works in a generic TView and, I hope, can also be applied to any reasonable subclass of TView.
To implement the full set of drawing features, the behaviors are attached to the TView as shown in Figure 1. TView contains a single behavior reference member that points to the first behavior object if any are present. Otherwise it is NULL. Each behavior links to the previous and next behavior to form a two-way linked chain. If the View's (EventHandler's) behavior reference in not NULL, all events, draw messages, cursor control, etc. are diverted to the first behavior. By default, the behavior simply propagates the message to the next behavior. Each behavior had a method that allows it to find the "owning" Event Handler (or View). If there is no next behavior, the message is passed directly back to the EventHandler's (e.g., the view's) equivalent method. To attain conditional or absolute control over this message propagation, simply override the appropriate method in a behavior subclass.
Normally, a PrintHandler behavior gets attached to the main view. The TDrawAdorner gets automatically attached whenever any other adorner is attached. We don't use the draw adorner here, but MacApp attaches one whether or not we make use of it. The order of behavior attachment is mostly irrelevant except that TCreateItems, TResizeItems, and TSelectItems should be in the indicated order. Note that these are not class relationships. These are actual behavior objects linked in a chain. (Adorners are in a list.)
The document has a TShapeList that contains all of the draw objects, whether rectangles, ovals, views, PICTs, or MOOVs. Each behavior derives from TItemsBehavior which has a reference to this same list. Thus each behavior has the same list reference. Multiple storage could have been eliminated by referencing back through the view to the document. This is an almost trivial change due to the use of the accessor method GetShapeList(). [By the way, I'm becoming a real convert to private field variables with accessor methods. They are more work initially, but they prove their worth when code changes and debugging are required. This way, everyone has to always come through the front door rather than slipping in through a window. At least you can find out who's trashing the place.]
The behaviors were implemented so as to be almost independent. In one case, a reference to a clicked shape is stored in TShapeList rather than requiring a reiteration of the list. If the first iterating behavior is missing, the other behavior will go ahead and do its own iteration.
The "Ten Behaviors" pretty much describe the basic functions of each behavior. Most of the basic code fragments are available in the C++ version of the Programming with MacApp book, although many require modification for MacApp 3. A list of field variables and methods, generated by Object Master, is shown in Figure 2.
Note again that the TView is just that-a generic TView, not a subclass. The TDrawItems behavior simply draws the list in the view. We debated whether to make this a behavior or an adorner. They both have draw methods. We decided that a behavior was preferred for our purposes, but the choice was not obvious. Having the Draw behavior separate from the other behaviors allowed it to easily be attached to any view, e.g., the clipview, where only displaying is required.
TMoveItems allows items to be dragged about the screen. For example, this can be used with TDrawItems and TSelectItems (no selection adorner) for a more sophisticated puzzle similar to the simple one supplied with HyperCard 2, or a game of checkers or chess.
TModifyItems allows change in appearance of items without moving them. An example would be trying different color schemes for interior decoration or fabric selection.
TReorderItems can be used for an address selection flip-pad (read only, supplied by a catalog company ) or for arranging slides.
TResizeItems would likely be used only if TMoveItems were also present.
TCreateItems would likely be used only if a selection palette is available to choose what is going to be created.
TCopyItems can be used in a read-only object source file, such as a template library.
TCutPasteItems allows items to be removed from or added to the clipboard.
Several of the above also require TSelectItems, with or without selection adornment (highlighting).
Clearly a large number of combinations of behaviors are possible. They can even be inserted and removed as needed. For example, an interactive multimedia authoring environment could allow a teacher more freedom to change the contents of a program used by students.
The Clipboard
In the quest to use pure TViews for most drawing environments, we kept getting bashed by the clipboard. No matter how long we stared at it, there were four clipboard-related methods that just wouldn't go away.
- CalcMinFrame
- ContainsClipType
- GivePasteData
- WriteToDeskScrap
We decided to use a slightly specialized clipboard view. We decided to use the behavior mechanism and a TClipboardItems behavior even though the latter is a little more than a convenient code bucket. You'll see why shortly.
Views already have a means to communicate with behaviors through the behavior chain. Someone with foresight added a method to TEventHandler so we could call down the behavior hallway chain and say, "Charlie Brown, where are you?" If Charlie Brown is there, he shouts back down the hall, "I'm here in room 314." The configuration to do this is shown in Figure 3.
The TClipboardItems behavior class was assigned a fixed identifier of 'CVIL'. The view code is as follows:
#pragma segment ShapeClip
pascal void TShapeView::CalcMinFrame(VRect& minFrame)
/* override */
{
TBehavior* theBehavior = GetBehaviorWithIdentifier('CVIL');
if (theBehavior)
((TClipboardItems*)theBehavior) -> CalcMinFrame(minFrame);
else
inherited::CalcMinFrame( minFrame);
}
#pragma segment ShapeClip
pascal Boolean TShapeView::ContainsClipType(ResType aType)
/* override */
{
TBehavior* theBehavior = GetBehaviorWithIdentifier('CVIL');
if (theBehavior)
return ((TClipboardItems*)theBehavior)
-> ContainsClipType(aType);
else
return inherited::ContainsClipType( aType);
}
#pragma segment ShapeClip
pascal long TShapeView::GivePasteData(Handle aDataHandle,
ResType dataType) /* override */
{
TBehavior* theBehavior = GetBehaviorWithIdentifier('CVIL');
if (theBehavior)
return ((TClipboardItems*)theBehavior)
-> GivePasteData(aDataHandle, dataType);
else
return inherited::GivePasteData( aDataHandle, dataType);
}
#pragma segment ShapeOpen
pascal void TShapeView::IShapeView( TDocument* itsDocument,
TView* itsSuperview,
VPoint& itsLocation,
VPoint& itsSize,
SizeDeterminer itsHSizeDet,
SizeDeterminer itsVSizeDet)
{
this -> IView(itsDocument, itsSuperview, itsLocation,
itsSize, itsHSizeDet, itsVSizeDet);
}
#pragma segment ShapeClip
pascal void TShapeView::WriteToDeskScrap() /* override */
{
TBehavior* theBehavior = GetBehaviorWithIdentifier('CVIL');
if (theBehavior)
((TClipboardItems*)theBehavior) -> WriteToDeskScrap();
inherited::WriteToDeskScrap(); /* Write a QuickDraw picture. */
}
In the case of CalcMinFrame, ContainsClipType, and GivePasteData, if the indicated behavior is present, the view diverts the call to the clipboard behavior. Otherwise, it calls the inherited method. WriteToDeskScrap calls the inherited method regardless, because most applications will at least output a PICT or TEXT in addition to a private scrap type and MacApp automatically provides these as defaults.
It works great! I've been using it for several months now and its simple, non-intrusive code which can be used in any view class. Just don't forget to attach the behavior. If you do, it won't crash, but it won't handle your private scrap either. The exact code used for the view and the behavior is included on the subscription code disk.
These conditioned diversions to a behavior could be added to MacApp in these four methods in TView. (The "else" part of each method should be deleted in that case.) It is non-intrusive and results in identical functionality to that at present if the indicated behavior is not present. It would allow a generic TView to be used in the clipboard. It could be hard-wired for a specific identifier, or the identifier could be set in another field variable, although I don't believe that is really necessary.
Past and Future
The code for these behaviors is largely derived from the C++ version of Programming with MacApp. The TShapeView Class was essentially eliminated except for its use as the clipboard View. The TShapeViewHelper unit was totally revamped and reorganized into separate behaviors. TShapeResizer and TItemsSelectionAdorner were added. The entire project was brought completely up-to-date from MacApp 2 through interim alpha and beta versions to the present version (3.0b3 at this writing).
The TStream classes and TStreamObject class were eliminated because they are now incorporated into MacApp. Other types of draw shapes have now been added, including the handling of views as draw objects, (including TTEViews) and PICTS.
We have plans to incorporate the graphical linking objects that were demonstrated last year at the Phoenix MADA conference (see the demo on the Phoenix conference CD ROM). We also hope to include all the drawing and text edit menus into our floating palette for ease of incorporation into other programs. We plan to handle multiple drawing layers in the same view.
Conclusion
Behaviors allow another level of abstraction and encapsulation. More importantly, they allow a level of portability and reusability that has not existed before, even in an object programming environment.
We discovered that behaviors provide a much clearer demarcation of responsibilities between what MacApp provides and what the application programmer must provide. This can't do anything but help, considering the sense of overwhelming intimidation many feel when confronted with the raw guts of the beast. Behaviors are a lot less intimidating. After all, what is a View if not simply a place for drawing and clicking? Do we really need to face a six-foot fine-print scroll list (in Object Master) of TView method names?