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 arent 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 windows 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 windows 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 wont 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 wouldnt have to pass the events 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 windows 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.