Window Barrier
Volume Number: 4
Issue Number: 1
Column Tag: ABC's of C
Breaking the Four Window Barrier
By William Rausch, Kennewick, WA
It seems that every good Macintosh example program (of a text editor, graphics
program, or whatever) is hung up on the idea of four windows. Of course, those that
aren’t such good example programs only use one window! This limit always seems to be
a result of the unnecessary use of global data structures.
Most multi-window Macintosh example programs contain some lines that look
like:
WindowPtr some_ windows[how_many];
data_structure some_data[how_many];
where how_many is defined to be four. The programmer will claim that it is easy to
change this number to be as large as necessary, and the application can be easily
recompiled. That is fine for the programmer, but what about the end user. He is stuck
with whatever number the programmer happens to choose. If the programmer happens
to choose a number of windows and data structures that perfectly fits a 512K
Macintosh, then the user is probably not able to take advantage of the extra memory of
a Macintosh Plus, much less a Macintosh with even more memory.
In actuality, it is not necessary to predefine any number of windows, or the data
structures that are associated with them. Since the Macintosh has an operating system
that knows all about windows and how to keep track of them, it makes little sense for
the application programmer to duplicate this effort.
The window data structure contains a 32-bit field called refCon which Inside
Macintosh describes as “the window’s reference value field, which the application may
store into and access for any purpose”. One such purpose, which is mentioned but not
demonstrated in many books on Macintosh programming, is to store a handle to some
application specific data structure which contains the information that is pertinent to
that window.
Since every event given to an application by the Event Manager will contain a
pointer to the window concerned, there is no need for the application to keep an array
of WindowPtrs; and since each window contains a refCon field, there is no need to keep
an array of data structures. Instead, allocate the necessary space for the data structure
at the time the new window is created, and put its handle in the refCon field of the
window’s data structure. Then, whenever an event is processed, just pass the
WindowPtr along to the functions which are going to process the event. These functions
then can get access to the data structure when they need it.
The code fragments which follow demonstrate the concepts described:
enum window_type
{
type_a,
type_b,
type_c
};
typedef union
{
struct
{
... /* type_a window data */
} a;
struct
{
... /* type_b window data */
} b;
struct
{
... /* type_c window data */
} c;
} content,*content_ptr,**content_hndl;
typedef struct
{
enum window_type xxx; /* optional, appl. specific */
content_hndl yyy; /* could include union directly */
...
} window_data, *data_ptr, **data_hndl;
...
make_ window(type)
enum window_type type;
{
WindowRecord *a_ window;
data_hndl a_data;
content_hndl a_content;
...
a_window =
(WindowRecord *)NewWindow(...);
a_data =
(data_hndl)NewHandle(sizeof(w_data));
a_content = (content_hndl)NewHandle
(sizeof(content));
(**a_data).xxx = type;
(**a_data).yyy = a_content;
a_ window->refCon = (long)a_data;
...
}
...
event_loop()
{
EventRecord the_ event;
WindowPtr which_ window;
int window_code;
...
while (TRUE)
{
if (GetNextEvent(everyEvent,
&the_ event))
{
switch (the_ event.what)
{
case mouseDown:
window_code = FindWindow
(the_ event.where,
& which_ window);
switch ( window_code)
{
case inSysWindow:
SystemClick(&the_ event,
which_ window);
break;

case inContent:
process_ window( which_ window);
break;
...
}
break;
...
}
}
else
{
}
}
}
...
process_ window(the_ window)
WindowRecord *the_ window;
{
data_hndl the_data;
...
the_data =
(data_hndl)(the_ window->refCon);
switch ((**the_data).xxx)
{
case type_a:
process_a_ window(the_ window);
break;
case type_b:
process_b_ window(the_ window);
break;
case type_c:
process_c_ window(the_ window);
break;
}
...
}
This general technique may, in some very particular cases, slow things down a
little. Generally, this won’t be the case because most applications spend a majority of
their time processing NULL events. If you wanted to speed things up a little bit and still
maintain the dynamic allocation capabilities of this programming approach, use just
one globally declared WindowPtr and one data_handle and just update them to always
point at the window/data_structure that the current event refers to. Then you wouldn’t
have to pass the event’s WindowPtr to the called function(s).
Of course, most applications need to be able to find all of their windows at times,
e.g., when Quitting the application so that changes can be saved, or when updating the
windows. At those times, it is still possible to avoid using a global array merely by
calling FrontWindow() and following the linked list of pointers stored in the
nextWindow field of the window’s data structure.
This approach to creating windows in an application, which I call “fire and
forget” (and which could be considered as a small first step towards object oriented
programming), will allow your application to create as many new windows (and their
associated data structures) as the user desires. It is also a convenient way of
manipulating different types of windows in a single application. (It is very easy, for
example, to imagine MacDraw and MacWrite in a single application!) Of course, your
application has to check available memory each time it tries to create a new
window/data structure to make sure there is enough available, but that is a small
price to pay for the increased utility the user can get from having a program that
“grows” with his hardware.