TweetFollow Us on Twitter

Aug 98 Getting Started

Volume Number: 14 (1998)
Issue Number: 8
Column Tag: Getting Started

Window Management

by Dave Mark and Dan Parks Sydow

How a Mac program handles multiple types of windows

Back in February we covered window-related events. In that column's WindowMaster program you saw how an application that displays a single window handles the opening, dragging, resizing, updating, and closing of that window. Those basic window-handling techniques are important because they're used in just about every Mac program -- but they don't go far enough. A real-world Macintosh application usually allows for more than one window to be open at any given time. Not only that, such a program often allows more than one type of window to be open at a time. For instance, a drawing application might open a single window that serves as a tool palette, and then allow any number of drawing windows to also open. When such a program is running, and one of the windows needs updating, how does the program know what to draw in the window that's in need of servicing? Last month's PictLister example program presented a solution that worked for simple programs. This month we'll hold off on moving to an entirely new topic so that we can dig deeper into just how a Mac program works with windows. In this column we'll alter the PictLister code to come up with a solution that works for both simple and complex programs.

Window Management Using Global Variables

When a window-related event occurs (such as a mouse click in a window's close box), the system keeps track of the affected window. When a program calls WaitNextEvent(), the system supplies the program with a WindowPtr that references this window. Last month's program PictLister opened two windows -- a list window and a picture window -- and kept track of each using a global window pointer variable. When the system returned a WindowPtr to PictLister, the program compared it to it's global variables to see which window was in need of handling. Since PictLister allowed only two windows to be open, this technique was easy to implement. The simple scenario presented in PictLister is representative of some programs (a game, for instance, might display only a control window and a playing-area window). But many more applications are more complex. If a program defined a half dozen window types, and allowed any number of each to be open, you can see how difficult it would be to define a global WindowPtr variable for each, and then keep track of which windows were open throughout the running of the program. Obviously, for some applications a more sophisticated window-handling technique is called for.

Window Management Using Piggybacking

The WindowRecord data type is used to hold the information that defines a window. When a window is created from a WIND resource, that resource's information is read from disk and stored in some of the fields of a WindowRecord in memory. The first field of a WindowRecord is port, a graphics port (type GrafPort) that specifies the drawing environment of a window. The WindowPtr data type is used to provide a pointer to this first field of a WindowRecord. While your work with variables of type WindowPtr has probably lulled you into thinking of a WindowPtr variable as a pointer to a window, it is in fact simply a reference to only the drawing environment of a window. To access the other fields of a window's WindowRecord you use a variable of the WindowPeek data type. Like a variable of type WindowPtr, a variable of type WindowPeek points to the first field of a WindowRecord. Unlike the WindowPtr, though, a WindowPeek allows access to the other WindowRecord fields. Figure 1 illustrates this (for simplicity many of the fields of the WindowRecord have been omitted).

Figure 1. Window access using a WindowPtr and a WindowPeek.

Most Toolbox functions that require window access settle for a WindowPtr as a parameter. For instance, to hide a window you pass HideWindow() a pointer to the window to hide. This snippet, which creates a window from a WIND resource and then hides the window, demonstrates:

WindowPtr   window;

window = GetNewWindow(kWindID, nil, kMoveToFront);
HideWindow( window );

For those infrequent times when you need to access a field of a window other than the port field, you need to typecast the window's WindowPtr reference to a WindowPeek. The WindowRecord field of interest is then accessed using the WindowPeek. In this next snippet the last field in a window's WindowRecord -- the refCon field -- is accessed in this manner. The refCon field, incidentally, is a long value that can be used to associate with a window four bytes of information.

WindowPeek   wPeek;
Long            windData;

wPeek = (WindowPeek)window;
windData = wPeek->refCon;

Now that you know some of the sordid details of how a window's information is stored and accessed, you're ready to see how to capitalize on this system. A WindowRecord holds a lot of information about a window, but it doesn't hold application-specific information. For instance, if you want your program to associate a flag with each window, there's no provision in the WindowRecord data type to allow you to store this information. For instance, consider a program that inverts the contents of some windows but not others (perhaps it's a photo darkroom utility that displays negatives in some windows). For such a program you might want to assign a Boolean value named invert to each window. To add this -- or any other -- information to a window you'll want to expand the WindowRecord.

To accomplish this window record expansion you define your own "peek" data type. Like the Apple-defined WindowPeek type, your data type will allow access to a WindowRecord. Your data type will, however, go on to allow access to an additional field (or fields) that you define. Figure 2 enhances Figure 1 to show how a variable of my own InvertWindPeek data type is used to access a window's WindowRecord and an additional field -- one of type Boolean.

Figure 2. Window access using a WindowPtr, WindowPeek and a InvertWindPeek.

To create your own expanded window record data type, define a struct containing the data to be associated with a window. If you make the first field of your application-defined data type a WindowRecord, then your new data type acts much like the Apple-defined WindowPeek data type. Define an additional field in the struct for each piece of data you want associated with one of your program's windows. The effect is that your data type consists of a WindowRecord with additional data piggybacked onto it -- thus the term piggybacking technique. Using piggybacking, here's how the InvertWindPeek data type discussed above might look:

typedef   struct
{
   WindowRecord   w;
   Boolean         invert;
} InvertWindRecord, *InvertWindPeek;

To make use of such an application-defined type, first allocate enough memory to hold such a structure:

Ptr      wStorage;

wStorage = NewPtr( sizeof( InvertWindRecord ) );

NewPtr() returns a generic pointer that points to a block of memory the size of one InvertWindRecord structure. This pointer can now be passed to NewWindow() to create a new window that is stored in a block of memory the size of an InvertWindRecord:

WindowPtr      window;

window = NewWindow( wStorage, &r, "\pUntitled", kVisible,
                     documentProc, kMoveToFront, kHasGoAway, 0L );

The Toolbox routine GetNewWindow()creates a window based on information stored in a WIND resource. Another Toolbox routine, NewWindow(),creates a window based on information that is instead supplied by the routine's arguments. We'll look at the arguments in more detail as we walk through the MorePictLister code. Variable window is a WindowPtr that now points to a window that consists of a WindowRecord followed in memory by a Boolean. To gain access to the Boolean field, typecast window to an InvertWindPeek, then use the peek variable to write to or read from the invert field. Here the above-created window's invert field is assigned a value of true:

InvertWindPeek   invertPeek   ;
Boolean            invertFlag = true;

invertPeek = (InvertWindPeek)window;
invertPeek->invert = invertFlag;

The piggybacking technique can be used to add any amount -- and any type -- of data to windows. The MorePictLister project uses this technique to add two fields to the WindowRecord that holds most of a window's data.

MorePictLister

This month's example is MorePictLister -- a reworking of last month's PictLister program. MorePictLister does all the things the original program did. MorePictLister opens a list window that lists all the PICT resources available to your program. Double-clicking on a picture name in the list results in the opening of a new window that displays the selected picture. Like PictLister, MorePictLister opens a single list window. Unlike PictLister, MorePictLister displays each selected picture in its own window. Figure 3 illustrates this.

Figure 3. Windows in the MorePictLister application.

Creating the MorePictLister Resources

Start by creating a folder named MorePictLister inside your development folder. Launch ResEdit and create a file named MorePictLister.rsrc inside your MorePictLister folder.

MorePictLister requires the same resources that PictLister did, less the two WIND resources. There's one MBAR menu bar resource with an ID of 128, and three MENU resources with IDs of 128, 129, and 130. Figure 4 shows the menu resources.

Figure 4. The three MENU resources.

Add to the resource file as few or as many PICT resources as you want -- copying and pasting several from the Scrapbook is a quick means of getting some pictures into the file. Use ResEdit's Get Resource Info item from the Resource menu to display the Get Resource Info window for each PICT, supplying a name and checking the Purgeable check box. Make sure to save the resource file when finished.

Creating the MorePictLister Project

Launch CodeWarrior and create a new project based on the MacOS:C_C++:MacOS Toolbox:MacOS Toolbox Multi-Target stationary. Uncheck the Create Folder check box. Name the project MorePictLister.mcp and designate the that the project end up in the MorePictLister folder. Remove SillyBalls.c and SillyBalls.rsrc from the project; we will not be using these files in this project. From the Finder, drag and drop your MorePictLister.rsrc file into the project window. Click the OK button if you see the Add Files dialog box. This project doesn't require any of the standard ANSI libraries, so you can pare down the project file removing the ANSI Libraries folder from the project window.

Next, choose New from the File menu to create a new source code window. Save it with the name MorePictLister.c, then add the new file to the project by choosing Add Window from the Project menu. The MorePictLister source code appears in its entirety in the source code walk-through. You can type it into the MorePictLister.c file as you read, or you can save your fingers some work by simply downloading the whole project from MacTech's ftp site ftp://ftp.mactech.com/src/mactech/volume14_1998/14.08.sit.

Walking Through the Source Code

Armed with a knowledge of the piggybacking technique and a familiarity with last month's PictLister source code, the walk-through of MorePictLister can progress swiftly. Here we'll go light on the list code (it was covered last month) and instead focus on the code that implements the piggybacking technique.

As usual, the source code listing starts off with a number of #defines -- most of which come directly from last month's listing. Unfamiliar constants are discussed as they are used.

/********************* constants *********************/

#define kListWindow          0
#define kDAWindow            1
#define kUnknownWindow       2
#define kPictWindow          3
#define kNilWindow           4

#define kSleep               7
#define kMoveToFront         (WindowPtr)-1L
#define kListHasGoAway       false
#define kPictHasGoAway       true   
#define kVisible             false

#define kListDefProc         0
#define kDrawIt              true
#define kHasGrow             true
#define kHasHScroll          true
#define kHasVScroll          true
#define kFindNext            true

#define kMinWindWidth        150
#define kMinWindHeight       60
#define kWindOrigWidth       200
#define kWindOrigHeight      255
#define kWindOrigLeft        20   
#define kWindOrigTop         50   
#define kBumpWindowHoriz     300
#define kBumpWindowVert      50

#define kBaseResID           128
#define kListWindID          kBaseResID
#define kPictureWindID       kBaseResID+1

#define mApple               kBaseResID
#define iAbout               1

#define mFile                kBaseResID+1
#define iQuit                1

PictLister uses the piggybacking technique to tie the list to the list window and to tie the PICT to the picture window. This is done by embedding a WindowRecord in each of the following typedefs.

typedef   struct
{
   WindowRecord     w;
   short            wType;
   ListHandle       list;
} ListWindRecord,   *ListWindPeek;

typedef   struct
{
   WindowRecord     w;
   short            wType;
   short            pictResID;
} PictWindRecord,   *PictWindPeek;

Since NewWindow() allows you to allocate your own memory for your windows, you can allocate one of the above structures instead, passing a pointer to the struct to NewWindow(). When the system provides the MorePictLister program with a WindowPtr that points to a window that needs handling, how does the program know which struct type is piggybacked on top of the window? That's what the wType field is for. When the struct is allocated, a window type is associated with it by setting the wType field to either kListWindow or kPictWindow (see the first set of #defines above). You'll see how all this works as we go along. Last month's PictLister program required global variables to keep track of each of the two windows, the list window's list, and the picture window's picture. Thanks to piggybacking, MorePictLister can dispense with all four of those global variables. Instead, we need just a single global variable -- the familiar gDone which is used to indicate when it's time to exit the main event loop.

/********************** globals **********************/

Boolean         gDone;

As always, we provide a function prototype for each function in the source file.

/********************* functions *********************/
void      ToolBoxInit( void );
void      MenuBarInit( void );
void      CreateListWindow( void );
void      EventLoop( void );
void      DoEvent( EventRecord *eventPtr );
void      HandleMouseDown( EventRecord *eventPtr );
short     WindowType( WindowPtr window );
void      DoContentClick(   EventRecord *eventPtr, 
                              WindowPtr window );
void      CreatePictureWindow( ListHandle pictList );
void      DoGrow( EventRecord *eventPtr, WindowPtr window );
void      DoUpdate( EventRecord *eventPtr );
void      DoActivate( WindowPtr window, Boolean becomingActive );
void      HandleMenuChoice( long menuChoice );
void      HandleAppleChoice( short item );
void      HandleFileChoice( short item );

The main() routine initializes the Toolbox, sets up the menu bar, and opens a the list window, then enters the main event loop. Recall that last month's program used main() to also open, then hide, the one picture window that was to be used to display the picture associated with a selected item in the list window. Here we forego that step. MorePictLister doesn't limit the user to displaying just one picture at a time. Instead, the program opens a new window for each selected picture.

/************************ main ***********************/

void   main( void )
{
   ToolBoxInit();
   MenuBarInit();
   
   CreateListWindow();
   
   EventLoop();
}

Both ToolBoxInit() and MenuBarInit() do what's expected of them.

/******************** ToolBoxInit ********************/

void   ToolBoxInit( void )
{
   InitGraf( &qd.thePort );
   InitFonts();
   InitWindows();
   InitMenus();
   TEInit();
   InitDialogs( nil );
   InitCursor();
}

/******************** MenuBarInit ********************/

void   MenuBarInit( void )
{
   Handle            menuBar;
   MenuHandle      menu;
   menuBar = GetNewMBar( kBaseResID );
   SetMenuBar( menuBar );
   menu = GetMenuHandle( mApple );
   AppendResMenu( menu, 'DRVR' );
   
   DrawMenuBar();
}

CreateListWindow() creates the program's list window. After declaring a host of local variables, several of our #defines are used in a call to SetRect(). Setting up a rectangle that defines the size and location of the window is necessary because we haven't defined the window's look in a WIND resource, as we did last month. After setting up the rectangle, a block of memory the size of a ListWindRecord structure is reserved.

/****************** CreateListWindow *****************/

void   CreateListWindow( void )
{
   Rect          r;
   Rect          dataBounds;
   Point         cSize, cIndex;
   short         i, dummy, numPicts;
   Handle        rHandle;
   short         resID;
   ResType       theResType;
   Str255        rName;
   WindowPtr     window;
   ListWindPeek  lwPeek   ;
   ListHandle    pictList;
   Ptr           wStorage;
   SetRect(   &r, kWindOrigLeft, kWindOrigTop,
               kWindOrigLeft + kWindOrigWidth, 
               kWindOrigTop + kWindOrigHeight);

   wStorage = NewPtr( sizeof( ListWindRecord ) );

The window is created with a call to NewWindow(). Let's quickly look at the arguments passed to this Toolbox routine. The first, wStorage, is a pointer to the area in memory that will hold the window. The Rect argument r defines the initial boundaries and screen location of the window. The string is the title that's to appear in the window's title bar. kVisible is a Boolean value (defined as false) that specifies whether the window is initially visible. The Apple-defined constant documentProc specifies the look of the window (a document-style window that includes a grow box). kMoveToFront is a constant (defined to be the odd-looking value (WindowPtr)-1L) that specifies whether the window should appear in front of all other open windows. kListGoAway is a Boolean value (defined as false) that tells whether this list window should include a close box. The last argument is of type long, and is used to fill the window's refCon field with supplemental data (we're leaving this field unused, so zero is passed).

   window = NewWindow(   wStorage, &r, "\pPicture List", 
                              kVisible, documentProc, kMoveToFront, 
                              kListHasGoAway, 0L );

The new window's port is designated as the active port, and the font is set to the one that's to be used in the display of the list items.

   SetPort( window );
   TextFont( systemFont );

Next, we prepare to create a list one column wide and zero rows deep, with a cell size to be calculated by the List Manager, and an overall list size that is the same as the list window (less room for the list's 15-pixel wide scroll bars). This code comes directly from last months example.

   SetRect( &dataBounds, 0, 0, 1, 0 );
   SetPt( &cSize, 0, 0 );
   SetRect (&r, 0, 0, kWindOrigWidth-15, kWindOrigHeight-15);

The list is created via a call to LNew(). Here the call is similar to that used last month. Instead of the list being returned to a global ListHandle variable, though, now we save the list to the local ListHandle variable pictList.

   pictList = LNew(   &r, &dataBounds, cSize, kListDefProc,
                           window, kDrawIt, kHasGrow, kHasHScroll,
                           kHasVScroll );

The selFlags field of a ListRec specifies how the list reacts to clicks in the list. Using the flag lOnlyOne tells the List Manager that only one item at a time can be highlighted in this list.

   (**pictList).selFlags = lOnlyOne;

Our next step is to set the fields in our piggybacking list struct. Before we can access the struct fields we need to cast the window's reference, which is a WindowPtr, to a ListWindPeek -- a pointer to a ListWindRecord. Then we'll set wType to kListWindow to mark the window as the list window, and save the list handle to the struct's list field for later recall.

   lwPeek = (ListWindPeek)window;
   
   lwPeek->wType = kListWindow;
   lwPeek->list = pictList;

This next section of code adds the rows to the list. Just as we did last month, we add one row to the list for every available PICT resource. The only difference between this code and last month's is that all occurrences of the global ListHandle variable gListHandle have been changed to the local variable pictList.

   numPicts = CountResources( 'PICT' );
         
   for ( i = 0; i < numPicts; i++ )
   {
      rHandle = GetIndResource( 'PICT', i + 1 );
      GetResInfo( rHandle, &resID, &theResType, rName );

      dummy = LAddRow( 1, i, pictList );
      SetPt( &cIndex, 0, i );

      if ( rName[ 0 ] > 0 )
         LAddToCell( &(rName[1]), rName[0], cIndex, pictList );
      else
         LAddToCell( "<Unnamed>", 10, cIndex, pictList );
}

Finally, the window is made visible, and LSetDrawingMode() is called to enable drawing in the list.

   ShowWindow( window );
   LSetDrawingMode( true, pictList );
}

EventLoop() and DoEvent() remain unchanged from last month.

/********************* EventLoop *********************/

void   EventLoop( void )
{      
   EventRecord      event;
   
   gDone = false;
   while ( gDone == false )
   {
      if ( WaitNextEvent( everyEvent, &event, kSleep, NULL ) )
         DoEvent( &event );
   }
}

/********************** DoEvent **********************/

void   DoEvent( EventRecord *eventPtr )
{
   char   theChar;
   Boolean   becomingActive;
   
   switch ( eventPtr->what )
   {
      case mouseDown: 
         HandleMouseDown( eventPtr );
         break;
      case keyDown:
      case autoKey:
         theChar = eventPtr->message & charCodeMask;
         if ( (eventPtr->modifiers & cmdKey) != 0 ) 
            HandleMenuChoice( MenuKey( theChar ) );
         break;
      case updateEvt:
         DoUpdate( eventPtr );
         break;
      case activateEvt:
         becomingActive = (   (eventPtr->modifiers & activeFlag) 
                                    == activeFlag );
         DoActivate(   (WindowPtr)eventPtr->message, 
                           becomingActive );
         break;
   }
}

HandleMouseDown() has just one change from last month's version. Take a look at the code under the inGoAway case label. A pointer to the window that's in need of handling is a part of the EventRecord that the system sent to the program. Instead of comparing this window pointer to a global list window pointer, as last month's program did, here we call our own WindowType() function to see if the window to close is the list window. WindowType() is described next.

/******************* HandleMouseDown *****************/

void   HandleMouseDown( EventRecord *eventPtr )
{
   WindowPtr   window;
   short         thePart;
   long            menuChoice;
   
   thePart = FindWindow( eventPtr->where, &window );
   
   switch ( thePart )
   {
      case inMenuBar:
         menuChoice = MenuSelect( eventPtr->where );
         HandleMenuChoice( menuChoice );
         break;
      case inSysWindow : 
         SystemClick( eventPtr, window );
         break;
      case inContent:
         DoContentClick( eventPtr, window );
         break;
      case inGrow:
         DoGrow( eventPtr, window );
         break;
      case inDrag : 
         DragWindow(   window, eventPtr->where, 
                           &qd.screenBits.bounds );
         break;
      case inGoAway:
         if ( TrackGoAway( window, eventPtr->where ) )
         {
            if ( WindowType( window ) == kPictWindow )
               CloseWindow( window );
         }
         break;
   }
}

WindowType() is called anytime our MorePictLister program wants to identify a window's type; pass WindowType() a WindowPtr, and the function returns one the application-defined constants that are used to categorize the window. If the window has a negative windowKind field, it's a Desk Accessory (by Apple's own definition). If the window's wType field is kListWindow or kPictWindow, one of those constants is returned, else kUnknownWindow is returned.

/********************* WindowType ********************/

short   WindowType( WindowPtr window )
{
   if ( window == nil )
      return( kNilWindow );
   if ( ((WindowPeek)window)->windowKind < 0 )
      return( kDAWindow );
   
   if ( ((ListWindPeek)window)->wType == kListWindow )
      return( kListWindow );
   
   if ( ((ListWindPeek)window)->wType == kPictWindow )
      return( kPictWindow );
   
   return( kUnknownWindow );
}

DoContentClick() is called when the mouse is clicked in the specified window's content region. The functionality of the routine is the same as last month's version, though the implementation has changed slightly. Here we no longer use the global list handle variable. Instead, we peek in the list window's struct to get the list handle that's stored with the window. Next, a call to LClick() is made. This routine handles all types of clicks, from clicks in the scroll bars to clicks in the list cells. LClick() returns true if a double-click occurs. In that case, we'll invoke the application-defined routine SetWindowPict() to determine which picture is to be displayed in the picture window.

/******************* DoContentClick ******************/

void   DoContentClick( EventRecord *eventPtr, WindowPtr window )
{
   ListHandle      pictList;
   ListWindPeek   lwPeek;

   if ( window != FrontWindow() )
   {
      SelectWindow( window );
   }
   else if ( WindowType( window ) == kListWindow )
   {
      SetPort( window );
      
      GlobalToLocal( &(eventPtr->where) );

      lwPeek = (ListWindPeek)window;
      pictList = lwPeek->list;

      if ( LClick(   eventPtr->where, eventPtr->modifiers, 
                        pictList ) )
         CreatePictureWindow( pictList );
   }
}

In last month's example, a double-click on a list item resulted in a call to an application-defined function named SetWindowPicture(). There we displayed the appropriate picture in the program's one picture window. The piggybacking technique makes it easy to keep track of multiple windows, so in this version of the program we handle a list item selection by opening a new picture window; the user can have any number of pictures displayed at once. CreatePictureWindow() takes care of the work of opening a picture window and assigning a picture to that window. First, a number of variables are declared.

/***************** CreatePictureWindow ***************/

void   CreatePictureWindow( ListHandle pictList )
{
   Cell           cell;
   Handle         rHandle;
   Rect           r;
   short          resID;
   ResType        theResType;
   Str255         rName;
   WindowPtr      window;
   PictWindPeek   pwPeek;
   PicHandle      pic;
   Ptr            wStorage;

CreatePictureWindow() performs many of the chores that last month's SetWindowPicture() handled. The function moves to the first cell in the list, then calls LGetSelect() to move through the list to find the selected cell and put the coordinates of that cell in variable cell. If a highlighted cell is found, we use cell.v to retrieve the appropriate PICT resource. We'll be basing the size and location of the picture window on the size of the picture, so here we store the size of the picture in Rect variable r. The left and top coordinates of r are then shifted right kBumpWindowHoriz pixels and down kBumpWindowVert pixels. That leaves the overall size of the rectangle unaffected, but guarantees that the top-left corner of the soon-to-be-created picture window won't end up in the very upper-left corner of the screen where it would be partly obscured by the menu bar. Next, a call to GetResInfo() is made to obtain the PICT resource's name.

   SetPt( &cell, 0, 0 );

   if ( LGetSelect( kFindNext, &cell, pictList ) )
   {
      rHandle = GetIndResource( 'PICT', cell.v + 1 );
      pic = (PicHandle)rHandle;

      r = (**pic).picFrame;
      OffsetRect( &r, kBumpWindowHoriz, kBumpWindowVert );

      GetResInfo( rHandle, &resID, &theResType, rName );

Next, memory for a PictWindRecord is allocated and the new storage is used to create the new picture window.

      wStorage = NewPtr( sizeof( PictWindRecord ) );
      window = NewWindow(   wStorage, &r, rName, kVisible,
                                 noGrowDocProc, kMoveToFront, 
                                 kPictHasGoAway, 0L );

The new window's title was set in the call to NewWindow(), but now we'll check to see if the PICT resource was named. If it wasn't, then we make that fact known by using the string "<Unnamed>" for the window's title. At this point the window is all set up, so we display it and make it active.

      if ( rName[ 0 ] == 0 )
         SetWTitle( window, "\p<Unnamed>" );
      ShowWindow( window );
      SelectWindow( window );

Finally, the PictWindRecord's wType field is set to kPictWindow and the PICT's resource ID is stored in the PictResID field for use when updating the window (as described just ahead in DoUpdate()).

pwPeek = (PictWindPeek)window; pwPeek->wType = kPictWindow; pwPeek->pictResID = resID; } }

DoGrow() is called when the mouse is clicked in a window's grow box. The routine begins by calling WindowType() to determine what kind of window is to be resized (picture window's can't be resized, but here we're allowing for a future enhancement that may allow them to be). If the window is a list window, we first establish the minimum and maximum size of the window. Next, we call GrowWindow(). If the window was resized, we call SizeWindow() and LSize() to resize the window and to let the List Manager know that the list has been resized.

/*********************** DoGrow **********************/

void   DoGrow( EventRecord *eventPtr, WindowPtr window )
{
   Rect         r;
   Cell         cSize;
   long         windSize;
   ListHandle   pictList;

   if ( WindowType( window ) == kListWindow )
   {
      r.top = kMinWindHeight;
      r.bottom = 32767;
      r.left = kMinWindWidth;
      r.right = 32767;

      windSize = GrowWindow( window, eventPtr->where, &r );
      if ( windSize )
      {
         SetPort( window );
         EraseRect( &window->portRect );

         SizeWindow( window,
               LoWord (windSize),
               HiWord(windSize), true );

         pictList = ((ListWindPeek)window)->list;
         LSize(   LoWord(windSize)-15,
                  HiWord(windSize)-15, pictList );

Next, local variable cSize is set to the current cell size in. The horizontal part of cSize is then used to change the cells width to match the new width of the resized window. A call to LCellSize() resizes all the cells, and a call to InvalRect() forces an update.

         HLock( (Handle)pictList );
         cSize = (*pictList)->cellSize;
         HUnlock( (Handle)pictList );

         cSize.h = LoWord( windSize ) - 15;
         LCellSize( cSize, pictList );
         InvalRect( &window->portRect );
      }
   }
}

Back in DoEvent() you saw that DoUpdate() is called to handle an update event. This version of DoUpdate() is similar to last month's. The primary difference is that now list access is carried out by accessing the list field of the list window's ListWindRecord structure. DoUpdate() begins by retrieving the WindowPtr from the EventRecord, setting the port, and then calling BeginUpdate().

/********************** DoUpdate *********************/

void   DoUpdate( EventRecord *eventPtr )
{
   WindowPtr      window;
   Rect           r;
   ListWindPeek   lwPeek;
   ListHandle     pictList;
   PictWindPeek   pwPeek;
   PicHandle      pic;

   window = (WindowPtr)(eventPtr->message);
   SetPort( window );
   BeginUpdate( window );

If the window is the list window, we redraw the grow box, gain access to the window's list, then call LUpdate() to let the List Manager update the list.

   if ( WindowType( window ) == kListWindow )
   {      
      DrawGrowIcon( window );

      lwPeek = (ListWindPeek)window;
      pictList = lwPeek->list;
      LUpdate( (**pictList).port->visRgn, pictList );      
   }

If the window is the picture window, we determine the size of the window's picture by looking at the size of picture window's port. In CreateWindowPicture() we based the size of the picture window on the size of the picture that was to be displayed in it. The picture resource ID is held in the pictResID field of the picture window's PictWindRecord, so we pull the ID from that field, use it in a call to GetPicture() to load the corresponding PICT resource into memory, and then call DrawPicture() to do the drawing. A call to EndUpdate() tells the program that updating is finished.

   else if ( WindowType( window ) == kPictWindow )   
   {
      r = window->portRect;

      pwPeek = (PictWindPeek)window;
      
      pic = GetPicture( pwPeek->pictResID );

      DrawPicture( pic, &r ); 
   }

   EndUpdate( window );
}

DoActivate() handles activate events. The picture window doesn't need any special activate event processing, so this routine focuses on the list window.

/********************* DoActivate ********************/

void   DoActivate( WindowPtr window, Boolean becomingActive )
{   
   ListWindPeek   lwPeek;
   ListHandle      pictList;
      
   if ( WindowType( window ) == kListWindow )
   {
      lwPeek = (ListWindPeek)window;
      pictList = lwPeek->list;

      if ( becomingActive )
         LActivate( true, pictList );
      else
         LActivate( false, pictList );
      
      DrawGrowIcon( window );
   }
}

The remaining menu-handling code is identical to last months code -- so we don't need to comment on the HandleMenuChoice(), HandleAppleChoice(), or HandleFileChoice() routines.

/****************** HandleMenuChoice *****************/

void   HandleMenuChoice( long menuChoice )
{
   short   menu;
   short   item;
   
   if ( menuChoice != 0 )
   {
      menu = HiWord( menuChoice );
      item = LoWord( menuChoice );
      
      switch ( menu )
      {
         case mApple:
            HandleAppleChoice( item );
            break;
         case mFile:
            HandleFileChoice( item );
            break;
      }
      HiliteMenu( 0 );
   }
}

/***************** HandleAppleChoice *****************/

void   HandleAppleChoice( short item )
{
   MenuHandle   appleMenu;
   Str255         accName;
   short         accNumber;
   
   switch ( item )
   {
      case iAbout:
         SysBeep( 10 );
         break;
      default:
         appleMenu = GetMenuHandle( mApple );
         GetMenuItemText( appleMenu, item, accName );
         accNumber = OpenDeskAcc( accName );
         break;
   }
}

/****************** HandleFileChoice *****************/

void   HandleFileChoice( short item )
{
   switch ( item )
   {
      case iQuit:
         gDone = true;
         break;
   }
}

Running MorePictLister

Select Run from the Project menu to run MorePictLister. Like last month's program, MorePictLister displays a menu bar featuring the , File, and Edit menus, and the Picture Lister window. Double-click on an item in the list of the list window and a new window that displays the selected picture appears. Choosing another list item opens still another window.

Till Next Month

Next month we'll take a look at Apple events. Apple defines four specific event types that it refers to as the four required Apple events. To date our relatively simple projects haven't included support for Apple events, so it's time to get with the program! By supporting Apple events, you can make your own Mac application more "Finder-friendly." For instance, if your application is running when the user chooses the Shut Down command from the Finder's Special menu, you'll want the operating system to be able to quit your application along with all other running programs. Apple events make this possible. Until then, experiment with this month's piggybacking technique to create a program that opens and keeps track of all manner of windows. See you next month...


Dan Parks Sydow is the author of over a dozen programming books, including "Foundations of Mac Programming" by IDG Books. Dan's lending a hand on this Getting Started article.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Corel Painter 20.1.0.285 - Digital art s...
Corel Painter lets you advance your digital art style with painted textures, subtle glazing brushwork, interactive gradients, and realistic Natural-Media. Easily transition from traditional to... Read more
iTubeDownloader 6.5.19 - Easily download...
iTubeDownloader is a powerful-yet-simple YouTube downloader for the masses. Because it contains a proprietary browser, you can browse YouTube like you normally would. When you see something you want... Read more
OmniFocus 3.8 - GTD task manager with iO...
OmniFocus is an organizer app. It uses projects to organize tasks naturally, and then add tags to organize across projects. Easily enter tasks when you’re on the go, and process them when you have... Read more
Hazel 4.4.5 - Create rules for organizin...
Hazel is your personal housekeeper, organizing and cleaning folders based on rules you define. Hazel can also manage your trash and uninstall your applications. Organize your files using a familiar... Read more
Macs Fan Control 1.5.7 - Monitor and con...
Macs Fan Control allows you to monitor and control almost any aspect of your computer's fans, with support for controlling fan speed, temperature sensors pane, menu-bar icon, and autostart with... Read more
Acorn 6.6 - Bitmap image editor.
Acorn is a new image editor built with one goal in mind - simplicity. Fast, easy, and fluid, Acorn provides the options you'll need without any overhead. Acorn feels right, and won't drain your bank... Read more
OnyX 3.8.2 - Maintenance and optimizatio...
OnyX is a multifunction utility that you can use to verify the startup disk and the structure of its system files, to run miscellaneous maintenance and cleaning tasks, to configure parameters in the... Read more
macOS Catalina 10.15.5 - An Operating Sy...
macOS Catalina gives you more of everything you love about Mac. Experience three all-new media apps: Apple Music, Apple TV, and Apple Podcasts. Locate a missing Mac with the new Find My app. And now... Read more
macOS High Sierra Security Updates 10.13...
macOS High Sierra introduces new core technologies that improve the most important functions of your Mac. From rearchitecting how it stores your data to improving the efficiency of video streaming to... Read more
Numi 3.28 - Menu-bar calculator supports...
Numi is a calculator that magically combines calculations with text, and allows you to freely share your computations. Numi combines text editor and calculator Support plain English. For example, '5... Read more

Latest Forum Discussions

See All

Steam Link Spotlight - Signs of the Sojo...
Steam Link Spotlight is a feature where we look at PC games that play exceptionally well using the Steam Link app. Our last entry was XCOM: Chimera Squad. Read about how it plays using Steam Link's new mouse and keyboard support over here. | Read more »
Steampunk Tower 2, DreamGate's sequ...
Steampunk Tower 2 is a DreamGate's follow up to their previous tower defence game. It's available now for both iOS and Android as a free-to-play title and will see players defending their lone base by kitting it out with a variety of turrets. [... | Read more »
Clash Royale: The Road to Legendary Aren...
Supercell recently celebrated its 10th anniversary and their best title, Clash Royale, is as good as it's ever been. Even for lapsed players, returning to the game is as easy as can be. If you want to join us in picking the game back up, we've put... | Read more »
Pokemon Go Fest 2020 will be a virtual e...
Niantic has announced that Pokemon Go Fest will still take place this year although understandably it won't be a physical event. Instead, it will become a virtual celebration and is set to be held on 25th and 26th July. [Read more] | Read more »
Marvel Future Fight's major May upd...
Marvel Future Fight's latest update has now landed, and it sounds like a big one. The focus this time around is on Marvel's Guardians of the Galaxy, and it introduces all-new characters, quests, and uniforms for players to collect. [Read more] | Read more »
SINoALICE, Yoko Taro and Pokelabo's...
Yoko Taro and developer Pokelabo's SINoALICE has now opened for pre-registration over on the App Store. It's already amassed 1.5 million Android pre-registrations, and it's currently slated to launch on July 1st. [Read more] | Read more »
Masketeers: Idle Has Fallen's lates...
Masketeers: Idle Has Fallen is the latest endeavour from Appxplore, the folks behind Crab War, Thor: War of Tapnarok and Light A Way. It's an idle RPG that's currently available for Android in Early Access and will head to iOS at a later date. [... | Read more »
Evil Hunter Tycoon celebrates 2 million...
Evil Hunter Tycoon has proved to be quite the hit since launching back in March, with its most recent milestone being 2 million downloads. To celebrate the achievement, developer Super Planet has released a new updated called Darkness' Front Yard... | Read more »
Peak's Edge is an intriguing roguel...
Peak's Edge is an upcoming roguelike puzzle game from developer Kenny Sun that's heading for both iOS and Android on June 4th as a free-to-play title. It will see players rolling a pyramid shape through a variety of different levels. [Read more] | Read more »
Clash Royale: The Road to Legendary Aren...
Supercell recently celebrated its 10th anniversary and their best title, Clash Royale, is as good as it's ever been. Even for lapsed players, returning to the game is as easy as can be. If you want to join us in picking the game back up, we've put... | Read more »

Price Scanner via MacPrices.net

New 16″ MacBook Pros now on sale for up to $2...
Apple reseller DataVision is now offering new 16″ Apple MacBook Pros for up to $255 off MSRP, each including free shipping. Prices start at $2194. DataVision charges sales tax for NY, NJ, PA, and CA... Read more
Apple now offering Certified Refurbished iPho...
Apple is now offering Certified Refurbished iPhone Xr models in the refurbished section of their online store starting at $499. Each iPhone comes with Apple’s standard one-year warranty, ships free,... Read more
Sale! Get a 10.2″ 32GB WiFi iPad for only $27...
Walmart has new 10.2″ 32GB WiFi iPads on sale for $50 off Apple’s MSRP, only $279. These are the same iPads sold by Apple in their retail and online stores. Be sure to select Walmart as the seller... Read more
Apple resellers offer new 2020 Mac minis for...
Apple resellers are offering new 2020 Mac minis for up to $50 off Apple’s MSRP with prices available starting at $759. Shipping is free: (1) B&H Photo: – 2020 4-Core Mac mini: $759 $40 off MSRP... Read more
Sprint is offering the Apple iPhone 11 free t...
Did you miss out on Sprint’s recent free iPhone SE promotion? No worries. Sprint has the 64GB iPhone 11 available for $0 per month for new lines when you trade-in a qualifying phone in any condition... Read more
Apple has clearance 2019 13″ 1.4GHz MacBook P...
Apple has Certified Refurbished 2019 13″ 1.4GHz 4-Core Touch Bar MacBook Pros available today starting at $979 and up to $440 off original MSRP. Apple’s one-year warranty is included, shipping is... Read more
Apple restocks 2019 MacBook Airs starting at...
Apple has clearance, Certified Refurbished, 2019 13″ MacBook Airs available again starting at $779. Each MacBook features a new outer case, comes with a standard Apple one-year warranty, and is... Read more
Apple restocks clearance Mac minis for only $...
Apple has restocked Certified Refurbished 2018 4-Core Mac minis for only $599. Each mini comes with a new outer case plus a standard Apple one-year warranty. Shipping is free: – 3.6GHz Quad-Core... Read more
Apple’s new 2020 13″ MacBook Airs on sale for...
B&H Photo has Apple’s new 2020 13″ 4-Core and 6-Core MacBook Airs on sale today for $50-$100 off Apple’s MSRP, starting at $949. Expedited shipping is free to many addresses in the US. The... Read more
B&H continues to offer clearance 2019 13″...
B&H Photo has clearance 2019 13″ 4-Core MacBook Pros available for up to $300 off Apple’s original MSRP, with prices starting at $1149. Expedited shipping is free to many addresses in the US. B... Read more

Jobs Board

Security Officer - *Apple* Store - NANA (Un...
**Security Officer \- Apple Store** **Description** About NMS Built on a culture of safety and integrity, NMSdelivers award\-winning, integrated support services to Read more
Transition Into Practice Program (TIP) - Sept...
…Academy-Transition into Practice (TIP) Residency program at St Mary Medical Center in Apple Valley, CA. **We are seekingRegistered Nurses who are:** + New graduate Read more
Essbase Developer - *Apple* - Theorem, LLC...
Job Summary Apple is seeking an experienced, detail-minded Essbase developer to join our worldwide business development and strategy team. If you are someone who Read more
Senior Software Engineer @ *Apple* - Theore...
Job Summary Apple is looking for a seasoned senior software engineer to join our worldwide business development and strategy team. This is an opportunity to lead a Read more
Cub Foods - *Apple* Valley - Now Hiring Par...
Cub Foods - Apple Valley - Now Hiring Part Time! United States of America, Minnesota, Apple Valley Retail Operations Post Date May 18, 2020 Requisition # 119230 Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.