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 10 - Working With Objects


Recipes--Objects

The recipes and sample code in this section describe how to define a class and how to create, initialize, and delete objects of that class type. They also show how to access inherited class types and how to determine whether an object descends from a specified class.

Recipe--Defining a Class

This recipe demonstrates how to define a file-based document class, using the TIconDocument class as an example.

To define a subclass of TFileBasedDocument, you perform these steps:

  1. Provide a class definition.
  2. Provide runtime type information in the class implementation.
  3. Provide constructor and destructor methods.
  4. Provide an initialization method.
  5. Override additional methods.

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

Provide a Class Definition

If your document class uses PowerTalk to turn documents into electronic mail, it should descend from MacApp's TMailingDocument class. If it uses MacApp's Edition Manager support for publish and subscribe, it should descend from TEditionDocument. The IconEdit application does not support mailers or publish and subscribe, so its document class descends from TFileBasedDocument, which supports using streams to write data to disk and read it back.

The definition of your document class should look something like the definition of the TIconEditApplication class:

class TIconDocument : public TFileBasedDocument
{
   MA_DECLARE_CLASS;          // RTTI: Place after class
                              //     declaration!
   private:
   TIconBitMap*fIconBitMap;   // The icon's bitmap.
   TIconEditView*fIconView;   // A reference to the single view
                              // in the document window.
   CRGBColor   fColor;        // Color property of document.

   public:
   TIconDocument();              // Constructor.
   virtual ~TIconDocument();     // Destructor.
   void IIconDocument(TFile* itsDocument);// Initialization.
   virtual void DoMakeViews(Boolean forPrinting);
   .
   .     // Not all methods shown.
   .
}; // End TIconDocument.
MA_DECLARE_CLASS is a macro that provides runtime type information for the class. If your class requires RTTI, this macro must be the first item in the class definition after the class declaration. Note that the destructor method, ~TIconDocument, is virtual. For more information on constructors and destructors, see Chapter 2, "Basic Operations."

Provide Runtime Type Information in the Implementation

If your class requires RTTI, you add code like the following to the class implementation:

// Start of TIconDocument implementation.
#undef Inherited
#define Inherited TFileBasedDocument

// Use pragma statement to specify code segment. Code segments are
// necessary only for code built to run on 68K Macintosh computers.
#pragma segment AOpen
MA_DEFINE_CLASS_M1(TIconDocument, Inherited);
The code shown here should appear at the beginning of your class implementation. You use the MA_DEFINE_CLASS_M1 macro, or one of its variations, to register the class so that the class can call Inherited to access methods of its parent classes, and so your application can create objects of the class type by name, ID, or signature. The MA_DEFINE_CLASS_M1 macro is described in "Registering Class Information," beginning on page 30.

Provide Constructor and Destructor Methods

A constructor method sets the fields of its class to default or safe values. It has the same name as the class and is called automatically whenever an instance of the class is created with the new routine. The constructor for the icon document class is shown below. The #pragma segment statement specifies a segment for the constructor, for 68K builds only. Code samples in this book generally omit the segment statement, but if your application will ever run on 68K-based machines, it is good practice to include a segment statement for each method in your application.

#pragma segment AOpen
TIconDocument::TIconDocument()
{
   // Set bitmap to NULL so that if IIconDocument intialization
   // fails, the TIconDocument::Free method will work correctly.
   fIconBitMap = NULL;  
   // Set other fields to safe or initial values.
   fIconView = NULL;
   fColor = gRGBBlack;
}
An alternative approach that provides the same result is to initialize the document's fields with an initialization list:

TIconDocument::TIconDocument():
   fIconBitMap(NULL),
   fIconView(NULL),
   fColor(gRGBBlack)
{
}
A destructor method frees any memory allocated by the class and performs any other necessary cleanup. It has the same name as the class, preceded by a tilde symbol (~), and is called automatically whenever an instance of the class is freed with the delete routine. This is the destructor for TIconDocument:

TIconDocument::~TIconDocument()
{
   // Dispose of the bitmap if it isn't NULL.
   fIconBitMap = (TIconBitMap*)FreeIfObject(fIconBitMap);
}
It isn't necessary to call Inherited in a constructor or destructor method. However, you should declare a destructor method with the key word virtual if its base class has a virtual destructor. This ensures that all destructors in the class hierarchy are called automatically when an object of that type is deleted. For more information, see "Virtual Destructors," beginning on page 34.

By convention, every MacApp class has at least a constructor method and a virtual destructor method, even if the methods are empty. Having them is not strictly necessary, but it prevents some C++ compilers from emitting pages of warning messages.

Provide an Initialization Method

The initialization method for your document class should perform the following tasks:

The following is the initialization method for the TIconDocument class:

void TIconDocument::IIconDocument(TFile* itsFile)
{
   TIconBitMap* anIconBitMap;
   // Call parent's initialization. Pass file and scrap type values.
   this->IFileBasedDocument(itsFile, kSignature);
   // Install failure handler in case of problem creating bitmap.
   FailInfo fi;            
   Try(fi)
   {  // Code that might fail.

      // Allocate a new icon bitmap, initialize it, and store a
      // reference to it. Release failure handler if successful.
      anIconBitMap = new TIconBitMap;
      anIconBitMap->IIconBitMap();
      fIconBitMap = anIconBitMap;
      fi.Success(); 
   }
   else
   {  // Code to recover from a failure.

      // Call Free to free the object. Destructor method will
      // free the bitmap, if one was created.
      this->Free();
      // Propagate the failure to the next handler in the stack.
      fi.ReSignal();
   }
} // TIconDocument::IIconDocument
The IIconDocument method calls IFileBasedDocument, to initialize all parent document classes. The constants passed to IFileBasedDocument specify a file type and a scrap type for the document.

After calling IFileBasedDocument, the IIconDocument method performs any initialization specific to the icon document, which consists entirely of creating a new icon bitmap. A more complicated document class might require substantial additional code.

The failure handling in IIconDocument calls this->Free() to free the document if a failure occurs, then calls ReSignal to propagate the failure. Freeing the document causes its destructor method to be called, and the destructor for TIconDocument frees the bitmap, if the document has one.

Note
This approach to failure handling is simple but not robust, so it may not be appropriate for all applications. A safer implementation would be for IIconDocument to free the bitmap 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 Additional Methods

You define subclasses to modify and extend the behavior of their parent classes. To do so, you add fields and methods and override virtual methods of the parent class. For example, a document class normally overrides the DoMakeViews method, since MacApp's document classes can't know what kind of views your documents use.

When you override a method, you add a declaration to the class definition in the header file and you add code for the method to the implementation file. The TIconDocument::DoMakeViews method is declared in the file UIconEdit.h:

virtual void DoMakeViews(Boolean forPrinting); // Override.
The code for the TIconDocument::DoMakeViews method, shown on the following page, is located in the implementation file UIconEdit.cp.

// TIconDocument::DoMakeViews:
void TIconDocument::DoMakeViews(Boolean forPrinting) // Override.
{
   TWindow* aWindow;
   TIconEditView* iconView;
   TStdPrintHandler*aPrintHandler;

   // If for printing, need only the view.
   if (forPrinting)
   {
      // Call global view server object to create the view. It returns a generic
      // view (TView), so we cast it to an icon-editing view.
      iconView = (TIconEditView *)gViewServer->DoCreateViews(this,
                              NULL, kIconEditViewId, gZeroVPt);
      FailNIL(iconView);
   }
   // Otherwise, need view and window.
   else
   {
      FailNIL(aWindow = gViewServer->NewTemplateWindow(kIconWindowId, this));
      // Get a reference to the view from the window.
      iconView = (TIconEditView*)(aWindow->FindSubView('ICON'));
      FailNIL(iconView);
   }
   // Save reference to view in document field.
   fIconView = iconView;

   // Create a print handler.
   aPrintHandler = new TStdPrintHandler;

   // Initialize the print handler. Pass "this" for a document reference.
   // Pass icon view for the view reference. Specify fixed horizontal and
   // vertical page size.
   aPrintHandler->IStdPrintHandler(this,
                           iconView,
                           !kSquareDots, 
                           kFixedSize,
                           kFixedSize);
} // TIconDocument::DoMakeViews 
When the document is opened for printing, DoMakeViews creates an icon view only; otherwise, it creates a full window hierarchy.

Recipe--Creating, Initializing, and Deleting an Object

This recipe shows how to

The sample code shown in this recipe uses the TIconDocument class from the IconEdit application.

Create and Initialize an Instance of a Class

You can create an instance of a class using the new operator:

TIconDocument* anIconDocument;
anIconDocument = new TIconDocument;// Create a TIconDocument object.
anIconDocument->IIconDocument(itsFile);// Initialize it.
This code is from the TIconApplication::DoMakeDocument method. The call to new results in a call to MacApp's MAOperatorNew routine to allocate the document object. The code shown here does not need to do any additional error checking because MAOperatorNew calls FailNIL on the allocated block of memory.

Delete the Object When It Is No Longer Needed

You can delete an object when it is no longer needed by calling the object's Free method or by using MacApp's FreeIfObject routine. For example, the DoIt method of a TCloseFileDocCommand object makes the following call, after first saving the document (if necessary):

fDocument->CloseAndFree();
The TDocument::CloseAndFree method in turn makes these two calls:

this->Close();
this->Free();
The Free method of TObject calls the ShallowFree method, which calls delete. The delete operator calls the object's destructor method, then calls MAOperatorDelete, which attempts to free the object from the object heap.

To delete an object with the convenience routine FreeIfObject, the destructor method of TDocument uses the following line:

fPrintInfo = (TPrintInfo*)FreeIfObject(fPrintInfo);
FreeIfObject saves you a step by checking whether the passed object is equal to NULL before calling Free on the object. Since it returns NULL, it also allows you to assign the return value to the object that is freed, as in the line above. Note, however, that you must cast the return value to the type of the freed object.

Don't use delete directly on an object that descends from TObject. Calling Free or FreeIfObject instead gives the Free method a chance to check whether the object is in use, and refrain from deleting it until it is safe to do so.

Recipe--Dynamic Casting Between Class Types

MacApp provides a macro, MA_DYNAMIC_CAST, that works together with the RTTI mechanism to provide safe dynamic casting--casting that is based on current, runtime class information. In fact, you can use the MA_DYNAMIC_CAST macro to safely access any class type in an object's class hierarchy, even a type that is included through multiple inheritance. This is referred to as side casting. You can read about dynamic casting and side casting in Chapter 2, "Basic Operations."

The TMailingApplication::OpenOld method uses the following dynamic cast:

MMailable* mailDoc = MA_DYNAMIC_CAST(MMailable, aDocument);
if (mailDoc)
   mailDoc->ReadLetter(kForDisplay);
The MA_DYNAMIC_CAST macro expands into code that determines whether the mixin class MMailable is part of the class definition for aDocument. If so, the macro returns a reference to an MMailable object; if not, it returns NULL. The code checks the returned value to see if the cast was successful.

Note
Since the MMailable class is a mixin class, this example demonstrates both side casting and dynamic casting.

Recipe--Determining Whether an Object Descends From a Specified Class

MacApp's RTTI mechanism, together with methods defined in the TObject class, allows you to determine whether an object descends from a given class.

To determine whether an object descends from a class, you use code like the following, from the TDragDropBehavior::SetOwner method:

#if qDebug
// Verify that the owner is a TView.
if (!fOwner->DescendsFrom(TView::GetClassDescStatic()))
{
   ProgramBreak("###A TDragDropBehavior attached to a non-view object");
   return;
}
#endif // qDebug
MacApp uses DescendsFrom to check for error conditions in debug versions of an application (hence the qDebug compiler flag in the code above), but you can use it in nodebug versions as well. The GetClassDescStatic method returns a ClassDesc object containing RTTI for the class. You can call it for any class that includes RTTI.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
25 JUL 1996