This should really be copyrighted by the OnBoard C group since much of this has been gleaned from that discussion group but I don't know if that would be a legally binding copyright. The purpose of the copyright, anyway, is just to make sure that noone charges for the information, such as it is, that we're giving away for free.
Disclaimer. The information in this FAQ is a compilation from members of the OnBoard C group and various other sources including the internet. The author/maintainer does not guarantee any of the information found in this FAQ. Use this FAQ AT YOUR OWN RISK and with your own judgement (in fact, this is pretty good advice for all the information found on the internet).
Location. The latest version of this document can be obtained (for free) from http://onboardc.sourceforge.net/cookbook.html. If you have any questions, comments, corrections or suggestions, please don't hesitate to send them to wade@adventure101.com.
Thanks. A special thanks goes to the following people (listed, here, in alphabetical order) who contributed large sections of this document. If I've left anyone out, please contact me at the above address. The major contributors include: "Ian Bailey" <baileyi AT-SIGN bigpond PERIOD com>, "Andrew Empson" <andrew PERIOD empson AT-SIGN xtra PERIOD co PERIOD nz>, and Roger Lawrence.
Changes. The following changes have been made to get to the versions that are described.
v0.7.0, 0.7.1, 0.7.2
This document is organized as a description of some of the differences between ordinary code and GUI code followed by a cut-and-paste treatment of various items (buttons and lists, for example) that you may want to use to build your own Palm code.
This paper gives you the basics -- the intimate details are completely explained by the Palm Developer's documents that you'll find here.
In addition, there are some more advanced things you'll notice about Palm code.
In ordinary code, the software drives the action. The code goes through a sequence of operations, pretty-much in a software-controlled order until the program is done. Sometimes, the code waits for user input but then, it goes back to stepping through its pre-defined list of tasks.
Graphical user interface (GUI) code, however, is a little less ego-centric -- the user drives the action and the software sits around and waits for him. To be more specific, the software sits around and waits for _events_ that are generated by the operating system in response to the user's actions.
This is interesting and all, but how does it affect *your* code? Glad you asked. Your software takes on a different structure than it did before it was a GUI application. Its parts are different, too. It is made up of
So, you've written and compiled your code. It worked the first time because you are, in fact, a brilliant programmer. Some guy decided to run your program on his Palm and he taps a button on the screen with his stylus.
At this point, the operating system generates a button tap event and passes it to your code. Your event loop function is running -- it has called a function that is waiting for the next event and the OS passes it a structure that describes the button tap event. That function returns to your event loop with that structure. Your event loop code then sees that this is a button push event for, say, button number three and calls the event handling code for that button.
There're two parts to a UI object -- the picture on the screen and the code to handle user interaction with that picture. Strictly speaking, you can generate the picture with your code but most programmers choose to use a resource compiler to draw those pictures. The resource compiler generates a file called a resource file and that file is compiled into your code.
The resource editor / compiler that is usually used with OnBoard C is RsrcEdit. It is tied pretty-well to the OnBoard C compiler. You can launch one from the other, OnBoard C generates a default resource file, RsrcEdit can edit that file easily, and those resource files are part of your OnBoard C project by default.
/* * This code is just in there in case you're trying to run this * with the PRC tools. */ #ifdef __GNUC__ # include <PalmOS.h> #endif /* * 'MainForm' is the ID number of the form. It is the ID for the * form in the resource file. Here is where you add the IDs of * other objects you will use in your code. Also, here, are three * prototypes. */ #define MainForm 1000 // *** PUT UI-DEFINITIONS HERE *** // // Prototypes static Boolean appHandleEvent (EventPtr pEvent); static void mainFormInit (FormPtr pForm); static Boolean mainFormEventHandler (EventPtr pEvent); static Boolean doMainMenu (FormPtr pForm, UInt16 command); /* * startApp and stopApp are here for future reference. They clearly * don't do anything for this program, but it's a good idea to do * program clean-up and shutdown in these files. One thing that * typically goes here is database opening and closing. */ static void startApp() {return;} static void stopApp() {return;} /* * A Palm program starts at the PilotMain function -- you can use * this example verbatim for most (maybe all) your Palm applications. * Some other examples might separate the event loop into a separate * function but we've combined the two, here. This function does * the following. * * o calls startApp, * o initiates the first form, * o handles the event loop, * o cleans-up (when it gets the 'leaving now' event), and * o leaves. */ UInt32 PilotMain (UInt16 cmd, void *cmdPBP, UInt16 launchFlags) { EventType event; UInt16 error; if (cmd == sysAppLaunchCmdNormalLaunch) { startApp(); /* * FrmGotForm generates a frmLoadEvent that'll get * handled as soon as we have an event handler that * knows what to do with it. */ FrmGotoForm(MainForm); /* * This loop gets events, handles the events, and * checks to see if we've got a 'done' event. */ do { /* * Wait for an event (we already generated the * first one). */ EvtGetEvent(&event, evtWaitForever); /* * Then, ask the system, the menu system, * and our *OWN* event handlers (one for the * application as a whole and one for the * current form) to deal with the event. */ if (!SysHandleEvent (&event)) if (!MenuHandleEvent (0, &event, &error)) if (!appHandleEvent (&event)) FrmDispatchEvent (&event); } while (event.eType != appStopEvent); /* * When we're done, shut down */ stopApp(); FrmCloseAllForms(); } return 0; } /* * This is the top-level event handler for the entire application. * Here, we handle form load events and our menu events. */ static Boolean appHandleEvent (EventPtr pEvent) { FormPtr pForm; Int16 formId; Boolean handled = false; if (pEvent->eType == frmLoadEvent) { /* * Load the resource for the form */ formId = pEvent->data.frmLoad.formID; pForm = FrmInitForm(formId); FrmSetActiveForm(pForm); /* * install a form-specific event handler */ if (formId == MainForm) FrmSetEventHandler (pForm, mainFormEventHandler); // *** ADD NEW FORM HANDLING HERE *** // handled = true; } /* * If it is a menu item, follow a sub-control path, in this case * doMainMenu although remember that you need a seperate menu handler * function for each menu bar, and then must include the line to call * it in the form handler (i.e. this) for each form that has that menu * bar. */ else if (pEvent->eType == menuEvent) { handled = doMainMenu(pForm, pEvent->data.menu.itemID); } return handled; } /* * This is the event handler for the main form. It handles all of * the user interactions with the user interface objects (e.g., * buttons, lists, text fields, and such) on the main form. */ static Boolean mainFormEventHandler(EventPtr pEvent) { Boolean handled = false; FormPtr pForm = FrmGetActiveForm(); switch (pEvent->eType) { /* * the first event received by a form's event handler is * the frmOpenEvent. */ case frmOpenEvent: FrmDrawForm(pForm); mainFormInit(pForm); handled = true; break; // *** ADD EVENT HANDLING HERE *** // default: break; } return handled; } /* * This is the menu handler for the main form. It handles doing the users selections. It takes a FormPtr for fast access to the form it's 'installed' for, and so that if something changes the active form between now and the end of the function it still works on the form it's designed for, altough that is rare.*/ static Boolean doMainMenu (FormPtr pForm, UInt16 command) { Boolean handled = false; switch(command) { // *** ADD MENU HANDLING HERE *** // } return handled; } /* * This is the startup code for the form. Here, we write our message * to the screen. */ static void mainFormInit (FormPtr pForm) { static Char foo[10] = "Hello GUI"; WinDrawChars (foo,StrLen(foo),20,18); // *** ADD FORM INITIALIZATION HERE *** // }
void * getObjectPtr (FormPtr pForm, Int16 resourceNo) { UInt16 objIndex=FrmGetObjectIndex(pForm,resourceNo); return FrmGetObjectPtr(pForm,objIndex); }
In each section, below, you'll be asked to open the form on which you'll be adding your object. There are several ways to go about this but here is one way to do it.
Starting in OnBoard C, do the following:
At the top of your file, you should add a #define to help your code be
a bit more readable. You should add this at the place marked
'PUT UI-DEFINITIONS HERE':
In RsrcEdit, do the following:
Now, you can go about adding your object's resource to the form.
UI Objects: Buttons
What They Are
Well, they're buttons!
How To Make One -- In The Resource Editor
You need to add the button to the main form in your program and we'll do
that in RsrcEdit. You tie the button in your resource file to your program by
remembering the ID of the button and referring to that number in your code.
We'll start by adding the button to your form.
How To Set One Up -- In The Code
There's nothing that you need to do in the code before the button can be
drawn on the screen. Some things (like lists) require that you fill them
before you can draw them but buttons are simple.
How To Handle One -- In The Code
In your code, you'll want to handle the event caused by someone pressing
that button.
#define MainButton 1001 // that's the button's number you remembered
Now, in your form's event handler (that was mainFormEventHandler in the above
example, at the place marked
'ADD EVENT HANDLING HERE'),
add the following:
case ctlSelectEvent: switch (pEvent->data.ctlSelect.controlID) { case MainButton: { static Char bar[7]="button"; // 1 WinDrawChars(bar, StrLen(bar), 60, 30); // 2 handled = true; break; } // other buttons... } break;You can, of course, replace the lines marked '1' and '2' with your own code -- this was just for illustration.
At the top of your file, you'll want to add a #define to help your code be a bit more readable. You should add this at the place marked 'PUT UI-DEFINITIONS HERE':
#define Menu1MenuItem1 1000; // HERE'S THE NUMBER YOU REMEMBERED //(And yes, you probably want to have better names for your defines in real-life applications)
case Menu1Item1: FrmAlert(Alert1); break;
At the top of your file, you'll want to add a #define to help your code be a bit more readable. You should add this at the place marked 'PUT UI-DEFINITIONS HERE':
#define MainList 1005 // HERE'S THE NUMBER YOU REMEMBEREDOnBoard C uses a header file that includes several, BUT NOT ALL, of the Palm APIs. It turns out that the LstDrawList, LstSetDrawFunction, and WinDrawTruncChars APIs aren't in OnBoard C's header file. We must add the link between our call and the APIs ourselves. We do this by placing the following lines in the section marked 'PUT UI-DEFINITIONS HERE':
void LstDrawList (ListType *pList) SYS_TRAP(sysTrapLstDrawList); void LstSetDrawFunction (ListType *pList, ListDrawDataFuncPtr func) SYS_TRAP(sysTrapLstSetDrawFunction); void WinDrawTruncChars (Char *c, int i, int x, int y, int w) SYS_TRAP(sysTrapWinDrawTruncChars);Next, you'll want to add the functions for setting-up the list. This being 'C', you can either add the functions at the top of the file or you can add prototypes to the top and the functions at the bottom (or in another file). This is all your choice and we're not going to cover it here.
First, we need our function that'll draw the list items. For this function, I'm going to call WinDrawTruncChars, a function that not only writes a string to the screen but also chops it off if the string is too long.
#define listCount 5 static Char *listString[listCount] = {"one", "two", "three", "four", "five"}; void drawList (Int16 i, RectangleType *bounds, Char **items) { WinDrawTruncChars (listString[i], StrLen(listString[i]), bounds->topLeft.x, bounds->topLeft.y, bounds->extent.x); }Next, we'll write the list setup function, setupList, that installs our drawList function. Here's the function definition (below, I'll tell you where to call it).
void setupList(int lIndex) { FormPtr pForm = FrmGetActiveForm(); void *pList = getObjectPtr(pForm, lIndex); LstSetListChoices (pList, 0, listCount); LstSetDrawFunction (pList, (ListDrawDataFuncPtr) drawList); // Since the list is already showing, we have to redraw it LstDrawList (pList); }Now you have to place the call to this function in your code to setup the list when you draw a form. Do that by adding the following function call in your 'mainFormInit' function where it says 'ADD FORM INITIALIZATION HERE':
setupList (MainList);And, voila -- you've built your list.
case lstSelectEvent: { // let's save the list selection -- this is the index of the // thing the user tapped in the array we passed to the list int i = pEvent->data.lstSelect.selection; // this is a check for *WHICH* list -- we'll leave it here // even though we only have one list switch (pEvent->data.lstSelect.listID) { case MainList: // We'll draw the selected string on the screen, but // normally, you'd do something with the index, here WinDrawChars (listString[i], // 1 StrLen(listString[i]), // 2 120, // 3 30); // 4 break; } }The lines marked 1 through 4 comprise the statement that reacts to a user selecting an item in your list. In real code, you'll want to replace these lines with something more sophisticated.
At the top of your file, you'll want to add a couple #define statements to help your code be a bit more readable. You should add this at the place marked 'PUT UI-DEFINITIONS HERE':
#define MyPopup 2006 // HERE'S ONE NUMBER YOU REMEMBERED #define MyList 2005 // HERE'S THE OTHER NUMBER YOU REMEMBEREDOnBoard C uses a header file that includes several, BUT NOT ALL, of the Palm APIs. It turns out that the LstDrawList, LstSetDrawFunction, and WinDrawTruncChars APIs aren't in OnBoard C's header file. We must add the link between our call and the APIs ourselves. We do this by placing the following lines in the section marked 'PUT UI-DEFINITIONS HERE':
void LstDrawList (ListType *pList) SYS_TRAP(sysTrapLstDrawList); void LstSetDrawFunction (ListType *pList, ListDrawDataFuncPtr func) SYS_TRAP(sysTrapLstSetDrawFunction); void WinDrawTruncChars (Char *c, int i, int x, int y, int w) SYS_TRAP(sysTrapWinDrawTruncChars);Next, you'll want to add the functions for setting-up the list. This being 'C', you can either add the functions at the top of the file or you can add prototypes to the top and the functions at the bottom (or in another file). This is all your choice and we're not going to cover it here.
First, we need our function that'll draw the list items. For this function, I'm going to call WinDrawTruncChars, a function that not only writes a string to the screen but also chops it off if the string is too long.
#define listCount 5 static Char *listString[listCount] = {"one", "two", "three", "four", "five"}; void drawList (Int16 i, RectangleType *bounds, Char **items) { WinDrawTruncChars (listString[i], StrLen(listString[i]), bounds->topLeft.x, bounds->topLeft.y, bounds->extent.x); }Next, we'll write the list setup function, setupList, that installs our drawList function. This is just like before, except that you won't draw the list in the form's startup function (the list is hidden -- you wouldn't want to draw a hidden list). Here's the function definition (below, I'll tell you where to call it).
void setupList(int lIndex) { FormPtr pForm = FrmGetActiveForm(); void *pList = getObjectPtr(pForm, lIndex); LstSetListChoices (pList, 0, listCount); LstSetDrawFunction (pList, (ListDrawDataFuncPtr) drawList); // Don't redraw the list }Now you have to place the call to this function in your code to setup the list when you draw a form. Do that by adding the following function call in your 'mainFormInit' function where it says 'ADD FORM INITIALIZATION HERE':
setupList (MyList);And, voila -- you've built your list.
case popSelectEvent: { // let's save the list selection -- this is the index of the // thing the user tapped in the array we passed to the list int i = pEvent->data.popSelect.selection; // this is a check for *WHICH* list -- we'll leave it here // even though we only have one list switch (pEvent->data.popSelect.controlID) { case MyPopup: // We'll draw the selected string on the screen, but // normally, you'd do something with the index, here WinDrawChars (listString[i], // 1 StrLen(listString[i]), // 2 100, // 3 0); // 4 handled = true; break; } }Make sure that you include the 'handled=true;' line whether or not you want to do anything when the user selects the line. Otherwise, the OS tries to change the title of the popup to the selection it has stored (but it doesn't have anything stored -- we're writing the list elements dynamically -- so you get garbage).
The lines marked 1 through 4 comprise the statement that reacts to a user selecting an item in your list. In real code, you'll want to replace these lines with something more sophisticated.
You need to add the buttons to the main form in your program and, as usual, we'll do that in RsrcEdit. This is a bunch like a couple of regular buttons. The differences lie in that the buttons are carefully aligned (as described earlier) and the buttons are all part of the same group. We'll start by adding the button to your form.
At the top of your file, you should add a #define to help your code be a bit more readable. You should add this at the place marked 'PUT UI-DEFINITIONS HERE':
#define UpButton 1020 #define DownButton 1021Now, in your form's event handler (that was mainFormEventHandler in the above example, at the place marked 'ADD EVENT HANDLING HERE'), add the following:
case ctlSelectEvent: switch (pEvent->data.ctlSelect.controlID) { case UpButton: WinDrawChars("Up", 2, 60, 30); handled = true; break; case DownButton: WinDrawChars("Down", 4, 60, 30); handled = true; break; // other buttons... } break;
Before we forget lets put in the define for the resource ID number. At the top of your code, marked 'PUT UI-DEFINITIONS HERE':
#define CHECKBOX 1005With the other controls we have looked at we have referred to the control in the code by it's resource ID (as in the ctlSelectEvent) or with a pointer that we obtained with the getObjectPtr function outline earlier. There is a third way of accessing a control - by its index. For some reason this is the way the PalmOS writers though would be the best way to deal with checkboxes. It is not a problem, we just have to call a function to get the index from a resource id.
You might call a function like this to see if a checkbox was ticked.
static void doCheckBox(void) { FormPtr form; UInt16 ctlIndex; Int16 checked; form = FrmGetActiveForm(); ctlIndex = FrmGetObjectIndex(form, CHECKBOX); checked = FrmGetControlValue(form, cltIndex); if(checked) WinDrawChars("Checked", 7, 5, 120); else WinDrawChars("Not Checked", 11, 5, 120); }Setting the state of a check box is just as simple:
static void setCheckBox(Int16 checked) { FormPtr form; UInt16 ctlIndex; form = FrmGetActiveForm(); ctlIndex = FrmGetObjectIndex(form, CHECKBOX); FrmSetControlValue(form, ctlIndex, checked); }Reacting to the user tapping on the checkbox is exactly the same as if it was a button. In your form's event handler (that was mainFormEventHandler in the above example, at the place marked 'ADD EVENT HANDLING HERE') add the following
case ctlSelectEvent: switch (pEvent->data.ctlSelect.controlID) { case CHECKBOX: { WinDrawChars("Tap", 3, 5, 120); handled = true; break; } //other buttons } break;
MemHandle h = MemHandleNew(100); // 100 bytes CharPtr p = MemHandleLock(h); // turn a handle into a pointer // use 'p' as you would any pointer MemHandleUnlock(h); // go off and do other stuff -- the stuff above and below this line // can be in the same function or in different functions, if you wish. p = MemHandleLock(h); // turn a handle back into a pointer // use 'p' as you would any pointer MemHandleUnlock(h); // and when you're done MemHandleFree(h);
At the top of your file, you'll want to add a #define to help your code be a bit more readable. You should add this at the place marked 'PUT UI-DEFINITIONS HERE':
#define MainField 1010 // HERE'S THE NUMBER YOU REMEMBEREDNext, you'll want to add some functions for dealing with the field. This being 'C', you can either add the functions at the top of the file or you can add prototypes to the top and the functions at the bottom (or in another file). This is all your choice and we're not going to cover it here.
The field setup function, setFieldText, sets the text of the field. Now, the user can add or remove text to his or her little heart's desire so you'll want to be able to get the text from the field. You do that with the 'getFieldText' function.
Here's the function definition (below, I'll tell you where to call them).
// Use a function like this to set a field to a string static void setFieldText (UInt32 fIndex, Char *StrToShow) { FormPtr pForm = FrmGetActiveForm(); void *pField = getObjectPtr (pForm, fIndex); // get the field's old text handle MemHandle oldH = FldGetTextHandle(pField); //Copy our string into a memhandle int len = StrLen(StrToShow); MemHandle mH = MemHandleNew(len+1); Char *pMem = MemHandleLock(mH); StrCopy(pMem, StrToShow); //The memhandle needs to be unlocked to work... MemHandleUnlock(mH); //To establish the field's link to the handle FldSetTextHandle(pField,mH); //To draw the field FldDrawField(pField); // get rid of old handle if (oldH != NULL) MemHandleFree (oldH); }Now you have to place the calls to this function in your code to set the text of the field when you draw a form for the first time. Do that by adding the following function call in your 'mainFormInit' function where it says 'ADD FORM INITIALIZATION HERE':
setFieldText (MainField,"Hello Field");
There's nothing much to handle here, unless you want advanced editing capabilities, which I've never needed. Anybody else?
I'm not sure whether this counts as in the code or as shut down but you'll want to be able to extract the text from a field (by the way, make sure you set the field before you try to get it.) You do that with the following function (again, I'll explain where to call it, below).// Use a function like this to find out what the field's contents // are and to put them into a string: static void getFieldText (UInt32 fIndex, Char *StrToGet) { FormPtr pForm = FrmGetActiveForm(); void *pField = getObjectPtr (pForm, fIndex); MemHandle mH = FldGetTextHandle(pField); Char *pMem=MemHandleLock(mH); StrCopy(StrToGet, pMem); MemHandleUnlock(mH); }To use this, you can replace the button handling code (that goes at the spot marked 'ADD EVENT HANDLING HERE'), described elsewhere in this document, with the following bit of code:
case MainButton: static Char bar[80]; getFieldText (MainField, bar); WinDrawChars (bar, StrLen(bar), 60, 30); handled = true; break;
// To unlink a field from a text handle, use a function like this: static void nullField (UInt32 fIndex) { //We need a pointer to the field FormPtr pForm = FrmGetActiveForm(); void *pField = getObjectPtr (pForm, fIndex); //Unlink the field by setting the handle to 'NULL'. FldSetTextHandle(pField,NULL); }Finally, remember that if you forget to unlink a field that is connected to a database before you close the form, or exit the application, you will get a fatal error! Fix that by adding the following to the application close function (stopApp, in our code) or, if your code has multiple forms, in the close form function:
nullField (MainField);
#define Alert1 1200; // HERE'S THE NUMBER YOU REMEMBERED
1) UInt16 FrmAlert(UInt16 alertID); will pop up an alert with the given ID number. Wherever you want this to happen put:
FrmAlert(Alert1);Ok, so that's great for displaying something, but wouldn't it be great if you could get information from this? Well, FrmAlert returns a value, the ID of the button tapped. Now, this isn't something you've set, it's just that the first button is #0, the second is #1, the third is #2, and the fourth that you should never use is #3.
UInt16 alertSelection = FrmAlert(Alert1);Cool, huh?
2) UInt16 FrmCustomAlert(UInt16 alertID, CharPtr string1, CharPtr string2, CharPtr string3);
is the other function. It is the way for displaying custom text. string1, string2, and string3 and what replace
^1, ^2, and ^3, respectively. You may want to use it in conjunction with StrPrintF to display any kind of data.
One word of warning though: this function should not take null strings (i.e. ""). This is because until Palm OS 3.0 this
caused problems, crashes I believe. If you want backward compatibility but don't want to use more than 1 or 2 strings
make sure to use empty strings (i.e. " ") in the remaining arguments.
FrmCustomAlert(Alert1, "\nMore text","\nThe second text","Hello, World!");Good, good. Now there is an alert saying:
This is some text More text The second text Hello, World!But now what? Well, as you've probably guessed, this also returns the pressed button. Here, I have used StrPrintF to display the choice in a second alert. UInt16 alertSelection = FrmCustomAlert(Alert1, "\nMore text","\nThe second text","Hello, World!"); MemHandle h= MemHandleNew(100); CharPtr s = MemHandleLock(h); StrPrintF(s, "\nThe button tapped was: %d", alertSelection); FrmCustomAlert(alert1, s, " ", " "); MemHandleUnlock(h); MemHandleFree(h);
At the top of your file, you'll want to add a #define to help your code be a bit more readable. You should add this at the place marked 'PUT UI-DEFINITIONS HERE':
#define NewForm 1100 // HERE'S THE NUMBER YOU REMEMBEREDI'm going to repeat: we're using a button to switch to the second form. Make a button just like before, but in your form's event handler (that was mainFormEventHandler in the skeleton code, at the place marked 'ADD EVENT HANDLING HERE'), add the following:
case ctlSelectEvent: switch (pEvent->data.ctlSelect.controlID) { case MainButton: FrmGotoForm (NewForm); handled=true; break; // other buttons... } break;Now, this means that pushing the button will call FrmGotoForm which, in turn, sends the following
We won't worry about handling the frmCloseEvent because we don't have anything we need to do.
We'll handle the frmLoadEvent in the 'appHandleEvent' function. Here, we initialize the form, set it as the active form, and install a new event handler. At the place marked 'ADD NEW FORM HANDLING HERE', add the following code:
else if (formId == NewForm) FrmSetEventHandler (frm, newFormEventHandler);This means that we need a 'newFormEventHandler'. We are going to steal mainFormEvent handler verbatim except for one line. Add the following function to your code:
static Boolean newFormEventHandler (EventPtr pEvent) { Boolean handled = false; FormPtr pForm = FrmGetActiveForm(); switch (pEvent->eType) { /* * the first event received by a form's event handler is * the frmOpenEvent. */ case frmOpenEvent: FrmDrawForm(pForm); newFormInit(pForm); // THIS IS THE DIFFERENT LINE handled = true; break; // Add other event handling here default: break; } return handled; }And this implies that we need a newFormInit function which we'll steal from mainFormInit.
static void mainFormInit (FormPtr pForm) { static Char foo[30] = "Hello Form 2"; WinDrawChars (foo,StrLen(foo),20,18); }
In the first article, here, we'll discuss reading from an existing database -- the memo database.
LocalID dbld; DmOpenRef dbRef; MemHandle record; CharPtr pChar; //... dbld = DmFindDatabase (0, "MemoDB"); if (dbld != 0) dbRef = DmOpenDatabase (0, dbld, dmModeReadWrite);
In this example, I'm going to do more than I absolutely need to in order to read from a database. I'm going to read from every record in the database and print out a few characters from each record.
int i, count; if (dbRef != 0) { // get th number of records in the database count = DmNumRecords (dbRef); // for each record in the database for (i=0; i<count; i++) { // Get the record, make sure it hasn't been deleted, // and lock the handle record = DmGetRecord (dbRef, i); if (record == 0) continue; pChar = MemHandleLock (record); // Now, read from 'pChar' like the pointer it is WinDrawChars (pChar, 20, 2, (10*i)+20); // Now, unlock the handle and release the record MemHandleUnlock (record); DmReleaseRecord (dbRef, i, 0); } }
Here, I'll create a new record in which to write. If you pick a record less than the number of records in the database, the database manager will move the others down. If you pick one that's greater, you'll get additional records (that'll be deleted, I believe, next time you hotsync). If you choose the exact number (remember that the record count, like arrays in C, is zero-based), then you'll just add one more record to the end of the database. That's what we'll do, here.
#define SIZE 100 { int count = 0; MemHandle record = 0; Char *pChar = 0; Char *string = "Just some text"; int length = StrLen (string); // create a new record at the end of a database (that gives // us a handle) and turn it into a pointer. count = DmNumRecords (dbRef); record = DmNewRecord (dbRef, &count, SIZE); pChar = MemHandleLock (record); // Write to the database with DmWrite (since writing through // the pointer won't work). DmWrite (pChar, 0, string, length+1); // Then, unlock the handle and release the record. MemHandleUnlock (record); DmReleaseRecord (dbRef, count, 0); }
if (dbRef != 0) DmCloseDatabase (dbRef);
Start by including the header file and defining a global variable that signifies whether the library is open.
#include <mathlib.h> int MathLibRef = -1;Then, probably in the startApp function at the beginning of execution, do the following:
Err err; // ... err = SysLibFind("MathLib", &MathLibRef); if (err != 0) { // library not loaded already err = SysLibLoad('libr', 'MthL', &MathLibRef); if (err == 0) err = MathLibOpen (MathLibRef, 1); }
FlpCompDouble x; x.d = sqrt(867.5309) * tan(42); FlpFToA(x.fd, buffer);
if (MathLibRef != -1) { Err err; UInt16 usecount; err = MathLibClose (MathLibRef, &usecount); if (usecount == 0) SysLibRemove (MathLibRef); }
The first thing you need to decide is how you're going to use the your preferences during the course of your code. If you need to access them in several forms, you might want to declare the preferences as global. We'll do that in the cookbook. You'd place this global definition at the location marked 'PUT UI-DEFINITIONS HERE' in the skeleton application:
typedef struct { int skeletonData; } Prefs; Prefs prefs;
void startApp() { Int16 prefSize = sizeof(Prefs); if ((PrefGetAppPreferences (AppCreator, 1000, // pref database id &prefs, &prefSize, true) // saved during Hotsync == noPreferenceFound) || (prefSize != sizeof(Prefs))) { // default initialization, since discovered // Prefs was missing or old. prefs.skeletonData=1; } }
void stopApp() { PrefSetAppPreferences (AppCreator, 1000, // pref database id 1, // version of pref database &prefs, sizeof(Prefs), true); // saved during hotsync }