
- Home
- Magazine
- Conference & Seminars
- News
- Archives
- Forums
- Store
- Directory
- Editorial
- Advertising
- User/Login
- Contact



The following three shortcuts have made my life much simpler while developing a new MacApp 3.0.1 application using C++.
#define try \
FailInfo fi; \
if (fi.Try()) {
#define catch \
fi.Success(); \
} \
else
#define try2 \
if (fi.Try()) {
#define success fi.Success()
#define resignal fi.ReSignal()
Thus you can write code that looks like the following:
try {
…
}
catch {
…
resignal;
}
or:
try …
catch ;
or:
try {
…
success;
return;
…
}
catch ;
These macros nest properly so that you can write the following:
try {
…
try {
…
}
catch {
…
resignal;
}
…
}
catch {
…
resignal;
}
The Try2 macro is used if you want a second, or third, Try clause at the same level as a previous one. E.g.:
try {
…
}
catch {
…
resignal;
}
…
try2 {
…
}
catch {
…
resignal;
}
The Catch macro could easily be modified to automatically resignal, but I felt it best not to hide this, and to save the few bytes in the situation where I didn't need to resignal.
Initialize IMyDocument DoInitialState Free FreeData DoNeedDiskSpace DoRead DoWrite
And it is important that DoRead and DoWrite perform operations in the same order. I got tired of trying to remember to update each of these methods, and of having to go back and forth between DoRead and DoWrite to make sure I was doing things in the right order. I therefore decided to localize all these functions into one common method. The price I pay for this is a slight performance hit due to the extra switches in my common routine, a slight violation of clean segmentation, and a violation of the tenant of a single routine performing a single function. I felt that these were a small price to pay for the increased convenience. When my app is complete, I can always do away with this common routine, and place the appropriate code in each of the specific methods.
My common routine is defined as follows:
enum tDocIterType {
forReading,
forWriting,
forSizing,
forInitialize,
forIDoc,
forDoInitial,
forFreeData,
forFree
};
void DoSRWIIDF( tDocIterType iterType,
TFile* aFile,
long& dataForkBytes,
long& rsrcForkBytes,
Boolean modifier = false);
Each of my standard methods is now quite simple and implemented similarly to the following:
pascal void TMyDocument::DoNeedDiskSpace(
TFile* itsFile,
long& dataForkBytes,
long& rsrcForkBytes) {
// count us
DoSRWIIDF( forSizing,
itsFile,
dataForkBytes,
rsrcForkBytes);
} // end of TMyDocument :: DoNeedDiskSpace
My common routine locks the document, so I can read and write to it comfortably, allocates any needed Streams, and for each member variable in the document performs a switch to perform the required action. The core of the code follows:
#pragma segment DocReadWriteEtc
void TMyDocument::DoSRWIIDF( tDocIterType iterType,
TFile* aFile,
long& dataForkBytes,
long& rsrcForkBytes,
Boolean modifier) {
TStream* aStream = NULL;
VOLATILE( aStream);
// lock us just in case
SignedByte myState = LockHandleHigh( (Handle) this);
try {
// get stream for i/o
switch (iterType) {
case forSizing:
aStream = new TCountingStream;
((TCountingStream*) aStream)->ICountingStream();
aStream->SetPosition( 0);
inherited::DoNeedDiskSpace( aFile,
dataForkBytes,
rsrcForkBytes);
break;
case forReading:
case forWriting:
aStream = new TFileStream;
((TFileStream*) aStream)->IFileStream( aFile);
aStream->SetPosition( 0);
if (iterType == forReading)
inherited::DoRead( aFile, modifier);
else inherited::DoWrite( aFile, modifier);
break;
}
// field 1
switch (iterType) {
case forSizing: // fall thru
case forWriting:
aStream->WriteBytes( &field1, sizeof( field1));
break;
case forReading:
aStream->ReadBytes( &field1, sizeof( field1));
break;
case forInitialize:
…
break;
case forIDoc:
…
break;
case forDoInitial:
…
break;
case forFreeData:
…
break;
case forFree:
…
break;
}
// field 2
…
// free stream
if (iterType == forSizing)
dataForkBytes += aStream->GetSize();
FreeIfObject( aStream);
// unlock us
HSetState( (Handle) this, myState);
}
catch {
// free stream
FreeIfObject( aStream);
// unlock us
HSetState( (Handle) this, myState);
resignal;
}
} // end of TMyDocument :: DoSRWIIDF
Now whenever I add, change, or delete a field, it is a simple matter of modifying the one "block" of code.
The Behavior basically looks at each character the user types into a TEditText and beeps if this is an illegal character, or if it is a legal character simply passes it along the behavior chain for normal operation. The behavior will also examine each character in a pasted string and beep if the string contains any illegal characters or pass the string along. Using the behavior is quite simple and relies on the fact that there is only one floating TEView per dialog window and that this view is usually the window's target after the window has been created. First create your window and establish it as a dialog so that the floating TEView will exist. Then create the behavior:
TBhvrInputFilter* aCharBeeper = new TBhvrInputFilter;
Then call the '"I" method, passing the window:
aCharBeeper->IBhvrInputFilter( aWindow);
This will initialize the Behavior and attach it to the floating TEView. It finds the floating TEView by first looking at the window's target to see if is a member of TDialogTEView; if not, it searches the window's sub views until it finds a TDialogTEView to attach to.
Next tell the Behavior which TEditText views you wish to check by passing the identifier(s) of these views:
aCharBeeper->AddView( aEditText->GetIdentifier()); aCharBeeper->AddView( anotherEditText->GetIdentifier());
And lastly, tell the behavior which characters are allowed. The default is to not allow any characters. However there are several methods for specifying the legal characters.
aCharBeeper->AllowNumbers(); aCharBeeper->AllowControlChars(); aCharBeeper->AllowChar( '/');
The above sequence will allow numbers and the slash character, the common characters for a date input view, but will beep on all other characters. It will also allow all control characters so that normal editing can take place.
I then subclassed TBhvrFTEVFilter again to create a class that would do post processing of the user's input. This turned out to be a little more difficult than I thought it would be at first, but still simpler than any other way I could think of. The difficulty lay in the fact that when I call inherited::DoKeyEvent or inherited::DoMenuCommand, the TDialogTEView actually created command objects, rather than acting immediately. This was solved by my examining the event queue after calling the inherited method, looking for a TTECommand. If I find one, I remove it from the queue, call its DoIt, Commit, and Free methods. I can then easily get the resulting edited text and act as I wish.
In hindsight, I realized that I may have been able to use MacApp's dependency and notification techniques to accomplish this post processing. However, I now had something that worked, and I believe I might have had problems with characters after the first typed character, and "if it ain't broke..."




