|
This Technote describes two serious problems in the
Notification Manager prior to Mac OS 9.0, one having to do with activate
events and the other with update events. These
problems can cause windows in your application to
be drawn redundantly or not at all. This Technote
provides a workaround for the active event problem
and some sample code, with explanations, for fixing
the update event problem.
If you're an application or application
framework developer and want to ensure the windows
in your application(s) are always updated and
activated properly, you should read this Note.
This Technote augments the information presented
in three chapters of Inside Macintosh
: "Event Manager" (Chapter 2) and "Window Manager"
(Chapter 4) of Macintosh Toolbox
Essentials and "Notification Manager"
(Chapter 5) of Processes
[Feb 1 1996]
|
Defining Notification Manager Problems
IMPORTANT:
Mac OS 9 introduced a
non-modal
Notification
Manager, which
does not suffer either of the problems described
in the technote. If your application requires
Mac OS 9 or later you can ignore this
technote.
|
You can use the Notification Manager to present
the user with a modal dialog (alert) that opens in
front of the windows of all applications. This
dialog actually appears in the window list of the
frontmost application during its call to
WaitNextEvent .
IMPORTANT:
Your application should not depend on the
details of the way Notification Manager presents
its dialog, including but not limited to the
fact that Notification Manager uses Dialog
Manager. The information is presented here only
in order to provide a full understanding of the
problem at hand.
|
The first thing to know is that Notification
Manager calls the Dialog Manager to manage the
dialog. More specifically, Notification Manager
calls ModalDialog with a dialog filter which in
turn calls the standard dialog filter as obtained
by GetStdFilterProc.
The fact that Dialog Manager, and in particular
ModalDialog and the standard dialog filter, provide
imperfect event handling means that some
window-oriented events are "swallowed" (i.e., never
provided to your application) while the dialog is
present.
Swallowed Deactivate Events (and Redundant
Activate Events)
Your application does not receive a deactivate
event for the (soon-to-be-former) front window when
the Notification Manager's dialog appears. This is
because ModalDialog has its own event loop
(partially implemented by the standard dialog
filter) and has no way of passing the deactivate
event off to your application's event loop.
When the user dismisses Notification Manager's
dialog, your application receives a redundant
activate event for the (recently-reinstated) front
window. Your application can make sure it
doesn't logically activate a window (i.e., enable
text fields, etc.) redundantly by simply checking
to see if the window is already active before
logically activating it.
Swallowed Update Events
In order to understand how update events get
swallowed, you need to understand how they would
have been generated in the first place.
Each window has a region, expressed in global
coordinates, which describes the portion of the
window that needs redrawing. This is called the
window's update region. During WaitNextEvent , the
Event Manager, Window Manager, and Process Manager
collaborate in walking the current window list. If
the update region of a window is found to be
non-empty, they generate an update event for that
window.
When your application receives the update event,
it calls BeginUpdate , (re)draws the image for the
window, and calls EndUpdate . The
BeginUpdate/EndUpdate pair of calls empties the
update region for the window so that subsequent
searches for windows which need updating do not
find that window.
These update regions are the key to
understanding how the Notification Manager swallows
update events.
The standard dialog filter, to which
Notification Manager's dialog filter passes
control, wants to ensure that background apps get
processing time by "solving" the problem described
in DTS Technote TN1145
Pending
Update Perils. The standard filter
simply calls BeginUpdate and EndUpdate every time
it's given an update event, regardless of the
window for which the event is bound.
This results in all windows in the current
window list having their update regions emptied
almost immediately. As a consequence, no update
events are generated for those windows, even though
they need to be (re)drawn. When the user dismisses
Notification Manager's dialog, only the windows
covered by Notification Manager's dialog get update
events, and then only for the region covered by
Notification Manager's dialog.
There is nothing straightforward your
application can do to prevent swallowed update
events. While your application innocently waits for
a call to WaitNextEvent to return, Notification
Manager suddenly puts up its dialog and seizes
control of the event loop until the user dismisses
the dialog. Your application can't even predict
when this will happen, much less prevent it or
easily work around it.
Back to top
Using the Sample Code Library
UpdateRegionSaver to Work Around the Problem
Although Apple has fixed
both of these problems in Mac OS 9.0, you might
consider using some of the sample code presented
here as a workaround on earlier systems. The sample
code will continue to work, albeit redundantly,
even on Mac OS 9.0 and above.
Most users of UpdateRegionSaver will only need
to make three very simple calls, shown in this
sample: SaveUpdateRegions , RestoreUpdateRegions ,
and DeleteUpdateRegions .
#ifndef __EVENTS__
# include <Events.h>
#endif
#include "UpdateRegionSaver.h"
pascal Boolean WaitNextEventWithNMSafeUpdates
(EventMask eventMask, EventRecord *theEvent,
UInt32 sleep, RgnHandle mouseRgn)
{
UpdateRegionSaver *root = SaveUpdateRegions ( );
EventRecord event;
Boolean result = WaitNextEvent
(eventMask,theEvent,sleep,mouseRgn);
RestoreUpdateRegions (root);
DeleteSavedUpdateRegions (root);
root = nil;
return result;
}
|
For a full listing of each of the three calls,
refer to Appendix A at the
end of this Technote.
Back to top
UpdateRegionSaver Reference
This section describes the data structures and
routines specific to the UpdateRegionSaver
library.
Data Structures
The UpdateRegionSaver library manipulates a data
structure of type UpdateRegionSaver.
typedef struct UpdateRegionSaver
{
RgnHandle fRgnH;
WindowRef fRef;
struct UpdateRegionSaver *fNext;
}
UpdateRegionSaver;
|
Field Descriptions
fRgnH
A region copied from the update region of a
window. It is in global coordinates (as is the
update region itself). Before being restored to
the window, it will be copied and converted to
the local coordinates of the window.
fRef
The address of the window from which the
copy of the update region came. Before restoring
the region, UpdateRegionSaver verifies that
there is still a window at this address in the
window list. (This is not a perfect test, but it
is the best test that can be made without
patching a trap or three.)
fNext
The address of the next structure in the
linked list. The last node in the list has a 0
here.
UpdateRegionSaver Routines
To get a list of update regions associated with
the windows in the current window list, call
SaveUpdateRegions . To restore the regions to their
owning windows, call RestoreUpdateRegions . To
destroy the list of regions, call
DeleteSavedUpdateRegions .
SaveUpdateRegions
To save the update regions associated with the
windows in your application's window list, call
SaveUpdateRegions.
SaveUpdateRegions returns a pointer to the root
of simple singly-linked list which contains a node
for each window. In each node your application will
find a window pointer, a copy of the window's
update region, and a pointer to the next node.
RestoreUpdateRegions
To restore a list of saved update regions to the
windows from which they came, call
RestoreUpdateRegions .
RestoreUpdateRegions copies the regions in the
list and converts the copies to the local
coordinates of each window before calling InvalRgn
to merge the region into any update region which
may already be there.
DeleteSavedUpdateRegions
To delete a list of saved update regions, call
DeleteSavedUpdateRegions.
Back to top
Summary
The Notification Manager may present a Dialog
Manager dialog whenever your application calls
WaitNextEvent . This impedes the flow of
window-related events (such as update or activate)
to your application's event loop. Make sure your
application doesn't logically activate a window
which is already active. Use the UpdateRegionSaver
library (or something like it) to ensure your
application will always get the update events it
requires.
Back to top
References
DTS Technote TN1145
Pending
Update Perils,
has a discussion of the problem
Notification Manager "solves," as mentioned
previously.
Chapter 6, "Dialog Manager," Inside
Macintosh: Macintosh Toolbox Essentials
has a description of the operation of
ModalDialog and the standard dialog filter.
Back to top
Change History
01-February-1996
|
First written.
|
01-November-2000
|
Updated to mention that the non-modal Notification
Manager in Mac OS 9.0 fixes both of the problems
described in this technote.
|
Back to top
Appendix A
UpdateRegionSaver.h
#pragma once
#ifndef __WINDOWS__
# include <Windows.h>
#endif
typedef struct UpdateRegionSaver
{
// We don't care about alignment since this is an internal
// runtime-only structure.
RgnHandle fRgnH;
WindowRef fRef;
struct UpdateRegionSaver *fNext;
}
UpdateRegionSaver;
// I can't figure why this next #ifdef would be necessary for
// pascal funcs,but CW7 for PPC says it is.
#ifdef __cplusplus
extern "C" {
#endif
pascal void RestoreUpdateRegions (UpdateRegionSaver *);
pascal void DeleteSavedUpdateRegions (UpdateRegionSaver *);
pascal UpdateRegionSaver * SaveUpdateRegions (void);
#ifdef __cplusplus
}
#endif
|
UpdateRegionSaver.c
#ifndef __LOWMEM__
# include <LowMem.h>
#endif
#include "UpdateRegionSaver.h"
static pascal Boolean IsWindowStillAround (WindowRef ref)
{
WindowRef scan = LMGetWindowList ( );
while (scan)
{
if (scan == ref) break;
scan = GetNextWindow (scan);
}
return !!scan;
}
pascal void RestoreUpdateRegions (UpdateRegionSaver *ursp)
{
while (ursp)
{
if (!EmptyRgn (ursp->fRgnH) && IsWindowStillAround (ursp->fRef))
{
RgnHandle localUpdateRgn = NewRgn ( );
if (localUpdateRgn)
{
Point zero;
GrafPtr keep = qd.thePort;
SetPort (ursp->fRef);
zero.h = qd.thePort->portRect.left;
zero.v = qd.thePort->portRect.top;
GlobalToLocal (&zero);
CopyRgn (ursp->fRgnH,localUpdateRgn);
OffsetRgn (localUpdateRgn,zero.h,zero.v);
InvalRgn (localUpdateRgn);
SetPort (keep);
DisposeRgn (localUpdateRgn);
}
}
ursp = ursp->fNext;
}
}
pascal void DeleteSavedUpdateRegions (UpdateRegionSaver *ursp)
{
while (ursp)
{
UpdateRegionSaver *next = ursp->fNext;
DisposeRgn (ursp->fRgnH);
DisposePtr ((Ptr) ursp);
ursp = next;
}
}
pascal UpdateRegionSaver * SaveUpdateRegions (void)
{
//
// This function saves as many update regions as it can.
// If for some reason memory is so low that some regions
// cannot be saved, this function makes a best effort.
// (Its best effort is rather stupid, but it does try.)
//
UpdateRegionSaver *root = nil;
WindowRef scan = LMGetWindowList ( );
while (scan)
{
UpdateRegionSaver *newUpdateRegionSaver =
(UpdateRegionSaver *) NewPtr (sizeof (UpdateRegionSaver));
if (!MemError ( ))
{
RgnHandle rgnH = NewRgn ( );
if (!rgnH)
{
DisposePtr ((Ptr) newUpdateRegionSaver);
newUpdateRegionSaver = nil;
}
else
{
GetWindowUpdateRgn (scan,rgnH);
newUpdateRegionSaver->fRgnH = rgnH;
newUpdateRegionSaver->fRef = scan;
newUpdateRegionSaver->fNext = root;
root = newUpdateRegionSaver;
}
}
scan = GetNextWindow (scan);
}
return root;
}
|
Back to top
Downloadables
|
Acrobat version of this Note (60K).
|
Download
|
Back to top
|