Previous Book Contents Book Index Next

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


The recipes and sample code in this section demonstrate how to perform a variety of operations with views and adorners.

Working With Views--A General Outline

This outline describes the steps most MacApp applications take to work with views.

  1. Your application can use view classes provided by MacApp or view classes you have defined based on MacApp's view classes.
  2. If you use a view-editing application to define a 'View' resource that describes a view or view hierarchy, use an include statement to include the resource into your application's resource file.
  3. Use a #include statement to include a text resource file into your application's resource file.
  4. Define a constructor method to initialize the fields of your view class to safe or default values.
  5. Define an initialization method for your view class to allocate any storage needed by the class.
  6. Define a destructor method to free any storage allocated in the initialization method and to do any other necessary cleanup when your object is deleted.
  7. To ensure that your view class can be created from a resource, you register your view classes as described in "Registering Class Information," beginning on page 30.
  8. Override the DoMakeViews method of your document object class to create the views for your document. Your implementation of DoMakeViews must handle the cases of opening a document for Finder printing and opening a document for display (see following recipe).
  9. Override the DoPostCreate method of your view object class to perform view initialization that can't be specified by your constructor or as part of a view resource definition.
  10. Override the Draw method of your view object class to handle drawing in your view.
  11. To recalculate the size of your view, override the CalcMinFrame method of your view object class.
  12. To highlight in your view, override the DoHighlightSelection method.
  13. To use an adorner with your view, you can either specify the adorner in your resource file or add it procedurally.

Recipe--Creating Views for Displaying and Printing Versus Printing Only

An application normally needs a complete view hierarchy, including a window, to display data. In some cases, however, the application may not need the full hierarchy for printing.

It is a common source of error to attempt to create a window or view from a 'View' resource without registering all view classes in the resource with the MA_REGISTER_CLASS macro.
The following code sample, from the TIconDocument::DoMakeViews method in the IconEdit sample application, demonstrates how to create a different view for printing only:

if (forPrinting)
   // If only for printing, need only the view.
   iconView = (TIconEditView *)gViewServer->DoCreateViews(
                  this, NULL, kIconEditViewId, gZeroVPt);
   // Otherwise, need view and window.
   aWindow = gViewServer->NewTemplateWindow(kIconWindowId, this);
   // Get reference to the view.
   iconView = (TIconEditView*)(aWindow->FindSubView('ICON'));
fIconView = iconView;
The NewTemplateWindow method reads the resource specified by the kIconWindowId resource ID from the resource file, creates a window and view hierarchy, and returns a reference to the window. The DoCreateViews method performs a similar operation for the resource specified by the kIconEditViewId resource ID, which doesn't include a window.

This sample code calls the window object's FindSubView method to get a reference to a specific subview in the hierarchy, the icon view.

Recipe--Calculating the Size of a View

Sometimes MacApp needs to know the current size of a view, such as when your application is printing or when the view has changed size and its scroll bars need to be adjusted. To determine the current size of a view, MacApp calls the CalcMinFrame method of the view object.

The default version of CalcMinFrame, defined in TView, computes the minimum frame based on the fLocation and fSize fields. This minimum size is sufficient for some view classes, but if your view has more complicated operations, you may need to override CalcMinFrame. The following version of CalcMinFrame is from the TIconEditView class from the IconEdit application. In calculating the view's minimum size, this method takes into account the current zoom magnification and adds a couple of pixels for a border.

void TIconEditView::CalcMinFrame(VRect& minFrame) 
   minFrame = VRect (  
         kIconHBits * fMagnification + kBorder + kBorder,
         kIconVBits * fMagnification + kBorder + kBorder);

Recipe--Using an Adorner With a View

An adorner is a graphical object that knows how to draw in the view to which it is attached. Adorners are described in detail in "Adorners," beginning on page 213. Adorners can be used for many purposes, and MacApp defines adorner classes for erasing, framing, highlighting, and other drawing operations in views.

To use an adorner with a view, you perform these steps:

  1. Create and initialize an instance of an adorner.
  2. Call the AddAdorner method of the view.

The sample code shown in this recipe is for a hypothetical application.

Create and Initialize an Instance of an Adorner

Your adorner object may be based on one of MacApp's adorner classes or on a subclass you have defined. You can create an adorner by calling new:

TMyAdorner * anAdorner = new TMyAdorner;
In the IMyAdorner initialization method for your adorner class, you call IAdorner, passing an IDType value to identify your adorner and a Boolean value indicating whether the adorner should be freed when the view it is attached to is deleted:

IAdorner(kMyAdornerID, kFreeOnDeletion);
You pass kDontFreeOnDeletion if you don't want the adorner freed with its view. This is often the case, since it is common to associate one instance of an adorner with many views. In this example, kMyAdornerID would have a definition something like the following:

const IDType kMyAdornerID = 'MyAd';
Instead of creating an adorner to add to a view, you can use one of MacApp's global adorner objects. These adorners are described in "MacApp's Adorner Classes," beginning on page 213.

Call the AddAdorner Method

To add an adorner object to a view, you simply call the view's AddAdorner method:

myView->AddAdorner(anAdorner, kAdornLast, kInvalidate);
In the AddAdorner call, you pass the adorner to add, a value indicating the drawing priority for the adorner, and a value indicating whether to invalidate the adorner's view, thus forcing it to be redrawn. MacApp's priority constants for adornment specify whether the adorner should be drawn last (as specified in this call), drawn after the view, drawn before the view, or drawn first. For more information, see "Adorner Priorities," beginning on page 215.

MacApp supplies the constants kInvalidate and kDontInvalidate to specify whether or not a view should be invalidated (forcing redrawing).

Recipe--Using a Document Class With a Text-Editing View

To define a file-based document class that works with a text-editing view, you perform these steps:

  1. Create a 'View' resource with a text-editing view.
  2. Call InitUTEView from your main routine.
  3. Provide a document class definition.
  4. Provide constructor and destructor methods.
  5. Provide an initialization method.
  6. Override the FreeData method.
  7. Override the DoMakeViews method.
  8. Override the DoNeedDiskSpace method.
  9. Override the DoWrite method.
  10. Override the DoRead method.
  11. Override the application's DoMakeDocument method.
  12. Make additional modifications.

The sample code shown in this recipe is from the DemoText application. The TTEDocument class shown in this recipe implements the methods needed to edit text but is not intended as a complete text-editing solution.

Create a 'View' Resource With a Text-Editing View

The easiest way to define a 'View' resource containing a window with a view hierarchy is to use a view-editing application. The window should have a scroller view plus vertical and horizontal scroll-bar views. The scroller view serves as the superview for a text-editing view based MacApp's TTEView class. You can see a text version of this window and view hierarchy in the file DemoText.r. The scroll bars work with the scroller to let the user scroll text.

The DemoText application defines a window with resource ID number 1004, containing a TTEView class with view resource ID 'TEVW'. To specify these resources in your application, you define constants such as the following:

// 'View' resource ID for DemoText text-editing window.
const ResNumber kWindowRsrcID = 1004;
// Resource ID for TTEView within window view hierarchy.
const IDType kTTEViewSubView = 'TEVW';

Call InitUTEView From Your Main Routine

To use MacApp's TTEView class, you must call the InitUTEView initialization routine. You typically do so in your main routine, with a line like the following:


Provide a Document Class Definition

You define a document class to work with a text-editing view. In the DemoText application, the document's parent class can be either TFileBasedDocument or, if the application is built to use PowerTalk mailers, TMailableDocument (a subclass of TFileBasedDocument). This recipe uses TFileBasedDocument. For a recipe that demonstrates the use of mailers, see "Recipe--Adding Mailer Support to a Document Class," beginning on page 628.

class TTEDocument : public TFileBasedDocument
   MA_DECLARE_CLASS;          // Include MacApp's RTTI.
   Handle      fDocText;      // The text owned by the document.
   TEStyleHandlefStyles;      // Style handle, if any.
   STHandle    fElements;     // Handle to element array, if any.
   TTEView*    fTEView;       // The view that displays the text.
   TextSpecs   fTextSpecs;    // Specifies properties of the text.
   ResNumber   fViewResourceID;// ID of 'View' resource for text view.
   // Initialization and freeing.
   TTEDocument();             // Constructor.
   virtual ~TTEDocument();    // Destructor.
   virtual void ITEDocument(  // Initialization.
                        const OSType itsScrapType,
                        ResNumber itsViewResourceID);
   virtual void DoInitialState();// Called when creating or reverting.

   // Disk operations.
   virtual void DoNeedDiskSpace(TFile* itsFile,
                           long& dataForkBytes,
                           long& rsrcForkBytes);

   virtual void DoRead(TFile* aFile, Boolean forPrinting);
   virtual void DoWrite(TFile* aFile, Boolean makingCopy);

   // Making views and windows.
   virtual void DoMakeViews(Boolean forPrinting);
   .  // Some methods not shown.
}  // End TTEDocument.

Provide Constructor and Destructor Methods

The constructor method puts all fields of the document into a default or safe state to ensure that freeing can take place correctly if there is an error during creation of the document.

   fDocText = NULL;
   fElements = NULL;
   fStyles = NULL;
   fTEView = NULL;
   fTextSpecs = gDefaultSpecs;
   fViewResourceID = kDefaultViewID;
In this constructor, the TTEDocument class sets fTextSpecs (which specifies properties of the text, such as font name, size, and color) to refer to a global variable. The global variable gDefaultSpecs is initialized in the IDemoTextApplication method.

The constructor sets the fViewResourceID field to kDefaultViewID; this is the default 'View' resource MacApp installs in a document. The initialization method, shown in the next section, sets the fViewResourceID field to the passed value.

The destructor method deletes any storage allocated by the document. For the TTEDocument class, it just disposes of the object's text handle.

   fDocText = DisposeIfHandle(fDocText);

Provide an Initialization Method

The initialization method for the TTEDocument class calls IFileBasedDocument (the initialization method of its parent), creates a handle to store document text, and sets the fViewResourceID field to the passed view resource ID.

void TTEDocument::ITEDocument(TFile* itsFile,
                            const OSType itsScrapType,
                            ResNumber itsViewResourceID)
   // Let parent classes perform their initialization.
   this->IFileBasedDocument(itsFile, itsScrapType);
   FailInfo fi;
      // Allocate a handler for storing the document's text.
      fDocText = NewPermHandle(0);
   fViewResourceID = itsViewResourceID;
If a failure occurs, ITEDocument calls this->Free() to free the document, then calls ReSignal to propagate the failure. Freeing the document causes its destructor method to be called, freeing the fDocText handle if it isn't NULL.

This approach to failure handling is simple but not robust, and it may not be appropriate for all applications. A safer implementation would be for ITEDocument to dispose of the handle directly, if necessary, then call ReSignal, allowing the calling routine (typically the application's DoMakeDocument method) to free the document object with its own failure handling.

Override the FreeData Method

The TFileBasedDocument::RevertDocument method calls the FreeData method as part of reverting to a previous version of a document. The TTEDocument class overrides the FreeData method to free the text storage allocated in the initialization method.

Override the DoMakeViews Method

A document class typically overrides the DoMakeViews method to create the kind of views it works with. In TTEDocument, the DoMakeViews method uses the global view server object to create a window and view hierarchy, gets a reference to the TTEView object in the hierarchy, creates a print handler to handle printing for the document, and calls the ShowReverted method as a means of setting up the window to display. (For more information on reverting, see "Reverting to a Previous Version of a Document," beginning on page 176.)

void TTEDocument::DoMakeViews(Boolean /*forPrinting*/ ) 
   TWindow* aWindow;
   TStdPrintHandler* aHandler;
   aWindow = gViewServer->NewTemplateWindow(fViewResourceID, this);
   // Next call uses a cast because FindSubView returns a TView.
   fTEView = (TTEView*) aWindow->FindSubView(kTTEViewSubView);
   aHandler = new TStdPrintHandler;
               this,       // Its document.
               fTEView,    // Its view.
               !kSquareDots,// Does not have square dots.
               kFixedSize, // Horizontal page size is fixed.
               !kFixedSize);// Vertical page size is variable.
   // Use the show-reverted mechanism to set up the fTEView view object.

Override the DoNeedDiskSpace Method

The DoNeedDiskSpace method determines how much disk space will be required to store the document's text.

void TTEDocument::DoNeedDiskSpace(TFile*itsFile,
                           long& dataForkBytes,
                           long& rsrcForkBytes) // Override.
   TEStyleHandle styles;
   STHandle elements;

   dataForkBytes += GetHandleSize(fDocText);

   if (fPrintInfo)
      rsrcForkBytes += fPrintInfo->GetSize() + kRsrcTypeOverhead
                  + kRsrcOverhead;
   if ((fTEView->fStyleType == kWithStyle) && (fTEView->fHTE))
      fTEView->ExtractStyles(styles, elements);
      rsrcForkBytes += GetHandleSize((Handle)styles)
                  + kRsrcTypeOverhead + kRsrcOverhead
                  + GetHandleSize((Handle)elements)
                  + kRsrcTypeOverhead + kRsrcOverhead;

   rsrcForkBytes += sizeof(TextSpecs) + kRsrcTypeOverhead
               + kRsrcOverhead;

   // Get resource file overhead. 
   Inherited::DoNeedDiskSpace(itsFile, dataForkBytes, rsrcForkBytes);
This method gets the size of the text handle, then adds the size of the document's print info record. If the document contains style information, it adds the amount of space needed to write that information. It also calls Inherited, to determine any disk space needed by other classes in the document class hierarchy.

Override the DoWrite Method

The DoWrite method writes the document's text and style data to disk.

void TTEDocument::DoWrite(TFile* aFile,
                     Boolean makingCopy) // Override.
   long numChars;
   TextSpecsHdl hTextSpecs;

   // Write out the text. 
   numChars = GetHandleSize(fDocText);
   SignedByte savedState = LockHandleHigh(fDocText);// Lock handle.
   FailOSErr(aFile->WriteData(*fDocText, numChars));
   HSetState(fDocText, savedState);       // Restore state of handle.

   if (fTEView->fStyleType == kWithStyle)
      TEStyleHandle styles;
      STHandle elements;

      fTEView->ExtractStyles(styles, elements);
      // NOTE: Ought to include failure handling to free new handles on error.
      PermHandToHand((Handle &) styles);
      AddResource((Handle)styles, kTextStyleRsrcType, kStylesRsrcID, gEmptyString);
      PermHandToHand((Handle &) elements);
      AddResource((Handle)elements, kTextStyleRsrcType, kElementsRsrcID,

   // Write the text specification resource, after converting it to a handle.
   hTextSpecs = (TextSpecsHdl)NewPermHandle(sizeof(TextSpecs));
   MABlockMove(&fTextSpecs, *hTextSpecs, sizeof(TextSpecs));
   AddResource((Handle)hTextSpecs, kTextSpecsRsrcType, kTextSpecsRsrcID,
   Inherited::DoWrite(aFile, makingCopy);
The TTEDocument::DoWrite method shown above performs these steps:

  1. It writes the data from the document's text handle to the file.
  2. If the document contains style data, DoWrite converts style and element data to resources and adds them to the document's resource map so that they will be saved with the file.
  3. It adds the document's text specification resource to the resource map so that it will be saved with the file.
  4. It calls Inherited to complete the writing process.

The general case of writing a document is described in Chapter 27, "Working With Streams."

Override the DoRead Method

The DoRead method reads the document's text and style data from disk.

void TTEDocument::DoRead(TFile* aFile,
                     Boolean forPrinting) // Override.
   long numChars;
   TextSpecsHdl hTextSpecs;
   Boolean oldTempAllocation;
   FailInfo fi;

   // Read in the text.

   // The file may have been created by someone else, so limit it to 32 KB!
   if (numChars > kUnlimited) 
      gApplication->ShowError(0, messageAlert + kFileTooBig);
      numChars = kUnlimited;

   // Set size of handle to hold text we will read. Lock handle, and provide
   // failure handling to unlock it in case of an error.
   SetPermHandleSize(fDocText, numChars);
   SignedByte savedState = LockHandleHigh(fDocText);

      FailOSErr(aFile->ReadData(*fDocText, numChars));
      HSetState(fDocText, savedState);
      HSetState(fDocText, savedState);// Restore state of handle.
   // Read in TEStyleRec.
   fStyles = (TEStyleHandle)Get1Resource(kTextStyleRsrcType, kStylesRsrcID);
   if (fStyles)

   // Read in the STElement array, using a permanent allocation.
   oldTempAllocation = TemporaryAllocation(FALSE);
   fElements = (STHandle)(Get1Resource(kTextStyleRsrcType, kElementsRsrcID));
   if (fElements)

   // Read the text specs resource.
   hTextSpecs = (TextSpecsHdl)Get1Resource(kTextSpecsRsrcType, kTextSpecsRsrcID);
   if (hTextSpecs)
      MABlockMove(*hTextSpecs, &fTextSpecs, sizeof(TextSpecs));

   Inherited::DoRead(aFile, forPrinting);
The TTEDocument::DoRead method shown above performs these steps:

  1. It determines the number of text characters to be read and limits it to a 32 KB maximum.
  2. It sets the fDocText handle to the needed size and reads the text.
  3. It reads the TEStyleRec resource.
  4. It attempts to read a STElement array resource; if successful, it detaches the resource.
  5. It attempts to set the fTextSpecs field by reading a resource; if unsuccessful, it calls DoInitialState to set the field to a default value.
  6. It calls Inherited to complete the reading process.

The general case of reading a document is described in Chapter 27, "Working With Streams."

Override the Application's DoMakeDocument Method

In your application class, you override the DoMakeDocument method to create and initialize a text-editing document object. The DoMakeDocument method of the TDemoTextApplication class calls ITEDocument, passing the kWindowRsrcID constant defined earlier. Remember that this ID specifies the 'View' resource for a window hierarchy containing a scroller, scroll bars, and a TTEView object.

TDocument* TDemoTextApplication::DoMakeDocument(
                           CommandNumber /* itsCommandNumber */,
                           TFile* itsFile)
   TTEDocument* aTEDocument = new TTEDocument;
   aTEDocument->ITEDocument(itsFile, kFileType, kWindowRsrcID); 
   return aTEDocument;

Make Additional Modifications

The TTEDocument class implements additional methods that are not included in the steps shown above:

Sets up various text-handling menus (supported in the DemoText application), which include menu items for changing the style, size, font, color, and justification of document text.
Handles the menu choices enabled in DoSetupMenus.
Helps set up the document's text before calling Inherited to complete showing of the document.
The TTEDocument class also includes methods to handle the text-editing menu choices it supplies. Your text-editing document class is likely to require additional methods as well.

Previous Book Contents Book Index Next

© Apple Computer, Inc.
25 JUL 1996