Wednesday, May 2, 2007

Using the Palm OS Progress Manager

The Progress Manager was introduced with Palm OS 3.0. It provides application developers with a way of giving status feedback to users during long operations, and offers users the chance to abort or cancel the long operation. The Progress Manager is useful when an application has to perform either a sequence of operations - for example, connecting to a server - or to carry out a complex and time consuming algorithm. In either case the user may want to interrupt the operation before it is completed.

Using the Progress Manager has four stages: launching the dialog box, updating the dialog box's display to show the current status, listening for events in the progress dialog and finally removing the dialog box when the operation is complete. To demonstrate the use of the Progress Manager I have created a sample application, which simulates a slow operation. In the example a loop counting to 1000 is initiated when the user presses a 'Start' button. The application launches a progress dialog and updates the user as to how far the count has reached. When the count reaches 1000 the operation is complete, and the progress dialog box is removed. The code below shows the event handler for the 'Start' button. The call to launch the progress dialog box is PrgStartDialog. This call is made just before the slow operation commences. PrgStartDialog takes two parameters, a title for the dialog box and a pointer to a callback function.


switch
(eventP>data.ctlSelect.controlID)
{
case MainStartButton:
{
ProgressPtr prg;
Long i=0, cnt=0;
EventType event;

prg = PrgStartDialog("Counting to 1000...", callbackFunction);

//Do something complicated...


while (i<1000)>

{

i++;

cnt++;

StrIToA(message, i);

if (cnt==10)

{

PrgUpdateDialog(prg, 0, i, message, false);

cnt=0;

}

EvtGetEvent(&event, 0);

if (!PrgHandleEvent(prg, &event))

if (PrgUserCancel(prg))

break;

}

PrgStopDialog(prg, false);

break;

}

default:

break;

}

Once the complex operation is underway, we need to make periodic calls to the Progress Manager to update the state of the progress dialog. This provides an opportunity to give the user feedback on how far into the operation we are, and how much longer they might have to wait. This call is made with PrgUpdateDialog. The function takes a pointer to the progress dialog (which was returned by PrgStartDialog) and error flag, a stage indicator, a text message and a boolean flag to determine whether to redraw the dialog or not. The interesting parameters are the stage indicator and the text message. The stage indicator represents the progress that the application has made so far, and can be used in our callback function to give the user some meaningful feedback. In a sequential operation we might pass an indicator to say which step of the sequence we have reached. (In the sample application I pass the current count.) The text message parameter is also used to give feedback to the user. This parameter is usually used to pass dynamic text information to the progress dialog box. For example, if the application was connecting to a server we might use the message parameter to pass the phone number that we had dialled.
Because the progress dialog box has a 'Cancel' button, which the user can press to stop the operation, we need to look for events in the Progress Manager. We do this by taking control of the event loop during the processing of long operation. To get events we make an EvtGetEvent call to the Event Manager. This gives us the next event from the event queue. We immediately pass this event to the Progress Manager, with a call to PrgHandleEvent. The Progress Manager calls our callback function to get the information that it should display. It should be noted that PrgHandleEvent internally makes a call to the system event handler (SysHandleEvent), so any system events - like the user pressing the power key - will be handled correctly.
The callback function was established when we made the call to PrgStartDialog. It is called during the PrgHandleEvent handler to give the application an opportunity to set the messages that will be displayed on the progress dialog. In the sample code we use callbackFunction, and its definition is shown below. This structure is defined in the header file progress.h and is copied below for reference. It is a complex structure, but fortunately the callback function only has to use a couple of fields, the rest are for system use. The most significant task of callbackFunction is to fill the textP field. This is used to give the user an indication of the progress through the operation. A typical use would be to perform a look up on the value given in the stage field and to set an appropriate text message describing the stage. The text message we sent with the call to PrgUpdateDialog can also be used to supplement the display with some dynamic information. For example, if we were connecting to a server we might have a value that indicated we were at stage 1. We would set textP to 'Dialing:' and then concatenate the value in the message field to provide the number we were dialing. This would give the output: 'Dialing: 01119 998229'. When we change textP we need to set the textChanged flag to true to force the dialog box to redraw. Finally, callbackFunction can set a bitmap to display on the progress dialog, and again this can be determined by the stage that the operation is at.
typedef struct
{
Word stage; // current stage
CharPtr textP; // buffer to hold text to display
Word textLen; // length of text buffer
CharPtr message; // additional text for display
Err error; // current error
Word bitmapId; // resource ID of bitmap to display
Word canceled:1; // true if user has pressed the cancel button
Word showDetails:1; // true if user pressed down arrow for more details
Word textChanged:1; // if true then update text (defaults to true)
Word timedOut:1; // true if update caused by a timeout
ULong timeout; // timeout in ticks to force next update (for animation)
DWord barMaxValue;
DWord barCurValue;
CharPtr barMessage; // text for display below the progress bar.
Word barFlags; // reserved for future use.
} PrgCallbackData;
Boolean callbackFunction(PrgCallbackDataPtr cbP)
{
if (cbP->stage<250)>textP, "Starting: ");
StrCat(cbP->textP, cbP->message);
cbP->bitmapId = startBitmap;
}
else if (cbP->stage>750)
{
StrCopy(cbP->textP, "Finishing: ");
StrCat(cbP->textP, cbP->message);
cbP->bitmapId = endBitmap;
}
else
{
StrCopy(cbP->textP, "In the middle: ");
StrCat(cbP->textP, cbP->message);
cbP->bitmapId = middleBitmap;
}
cbP->textChanged = true;
return true;
}
In most cases PrgHandleEvent will return true, allowing us to continue processing our task. If it returns false we should call PrgUserCancel. This will return true if the user pressed the cancel button on the progress dialog box. When the user presses cancel we should perform any tidy up that is necessary and then abort the long operation. The last call we make to the ProgressManager is made when our operation is complete. This is a call to PrgStopDialog, and it removes the progress dialog box from the screen.
Source : http://www.developer.com/lang/other/article.php/615961