The Palm Programmer's Cookbook. v0.7.2


Copyright 2002, Wade Guthrie. Permission is granted to copy and redistribute this document so long as it is unmodified (including the section that describes where to get this document for free) and the copyright remains in-tact. Permission explicitly granted (in fact you're encouraged) to copy any and all code samples from this document into your own code -- and you can do whatever you'd like with the resultant code (sell it, give it away, whatever).

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

v0.6.8 v0.6.7 v0.6.6 v0.6.5 v0.6 v0.5 v0.41 v0.4 v0.3 v0.2


Contents

This document has a weird arrangement. You can read it in order to learn no more than you need to learn to get the job done. To do that, I had to interleave some concept sections with those for the various user-interface objects (buttons, lists, etc.). Because of that, I think it'd be a little tough to scroll through this document to search for subjects you want. To make that easier, I broke the table of contents into those two sections.

Introduction
How Palm Code Differs From "Normal" Software.
Event-Driven Code
Resource Files.
The Skeleton Program
User-Interface objects
Memory Handling
Beginning Database
Buttons
Repeat Buttons
Menus
Lists
Lists with Popup Trigger
Push Buttons
Check Boxes
Sliders
Feedback Sliders
Text Fields
Scroll Bars
Tables
Alerts
Icons
Forms



0. Introduction

The intent of this paper is to provide a C programmer with all the information necessary to jump quickly into writing Palm code. It's aimed at a programmer who's new to the Palm platform and, in fact, GUI programming in general. Though this document covers the material from the perspective of the OnBoard C and RsrcEdit toolset, almost all of it is applicable to other tools.

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.




1. How Palm Code Differs From "Normal" Software.

If you're used to writing standard, inline, code, you'll find that writing Palm code is a little different. For one thing, there's no way to stop the program from inside the code -- your program stops when someone starts another program. Here's some other differences you'll notice:

This stuff is all described in more detail, below.

In addition, there are some more advanced things you'll notice about Palm code.

(Some of this may be addressed in future versions of this document)




Event-Driven Code

This section is an introduction to event-driven code. If you're already familiar with these techniques, please feel free to skip to the next section.

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

The event loop is the place where your code spends most of its waking hours. It's the thing that waits for events from the operating system, acquires those events, and hands them to the specific event handlers you've written.

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.




A Couple Things About GUIs.

UI Objects

The average Palm program has a screen that is littered with buttons, lists, check boxes, and other similar stuff. Here, I'll call these "User-Interface Objects", or UI objects. There's no in Palm programming, no 'printf' or 'getc' -- instead you use Palm's GUI library to create and handle UI objects.

Forms

Now, if UI objects are the paint in your picture, then a 'form' is the canvas. An application can have several forms but it has to have at least one. We'll call that the main form.




Resource Files.

Most non-GUI programs are made up of source files, header files, and maybe a library or two. Most GUI code, however, also has a resource file.

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.




2. Code: Looking at Basic Palm Code

There're lots of examples of simple Palm programs. Sometimes it's a good idea to look at several in case one strikes your understanding better than another or if the combination, together, helps you see what's happening. There are some excellent examples of basic Palm programs here, here, and here.

The Skeleton Program

This section goes over yet another basic Palm skeleton. It's not absolutely necessary that you write your code exactly like this but this is a good starting point. This code is really pretty simple, though the mass of comments make it look huge. It's made up of only six functions (two of which are empty -- they're only placeholders for more complicated code. Those functions are:

Our skeleton code looks like this:

	/*
	 * 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 *** //
		
	}

Other Useful Bits

Here are some other bits of code that you'll want to include in your Palm code.

getObjectPtr

The Palm API has lots of ways to keep track of UI Objects. There's indecis, pointers, and resource numbers. The programmer typically knows his objects by resource number but most of the Palm APIs want a pointer. Unfortunately, there's no function that'll get you directly from one to the other. The getObjectPtr function uses a series of two instructions to do this job.

		void	*
	getObjectPtr (FormPtr pForm, Int16 resourceNo)
	{
		UInt16 objIndex=FrmGetObjectIndex(pForm,resourceNo);
		return FrmGetObjectPtr(pForm,objIndex);
	}




3. User-Interface objects

This section provides a cookbook for many of the user interface objects you'll be interested in adding to your forms. Each section will describe a single UI object, how to generate the UI object with the resource compiler, and what code you'll need to handle the main events for your object. For each case, you'll be able to take your UI object-handling code to the next level by reading the Palm documentation.

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:

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.

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 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.




UI Objects: Repeat Buttons

What They Are

How To Make One -- In The Resource Editor

How To Set One Up -- In The Code

How To Handle One -- In The Code




UI Objects: Menus

What They Are

A menu is a list of choices that pops down from the top of the Palm's screen. You've all used them throughout this tutorial so this should've been obvious :-).

How To Make One -- In The Resource Editor

We'll add a menu to the main form in RsrcEdit.

How To Set One Up -- In The Code

This part is not so hard, however, it can be tedious if you have large menu bars. Lucky we only made one heading and one item :D.

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)

When you add menu items I find it is good if you either switch back and forth between RsrcEdit and SrcEdit and are writing #defines as you go along or decide on a naming convention (e.g. first menu heading is 1000, second is 1010, third is 1020...).

How To Handle One -- In The Code

Again, not difficult (especially if you make use of copy-paste) but possibly tedious. Where it says 'ADD MENU HANDLING HERE' put in this code (note that this requires you to have followed the instructions in the 'Alerts' section):
	case Menu1Item1:
		FrmAlert(Alert1);
		break;



UI Objects: Lists

What They Are

A list is a group of choices, one per line, setup to allow the user to pick one by tapping on it.

How To Make One -- In The Resource Editor

We'll add a list to the main form in RsrcEdit.

How To Set One Up -- In The Code

You'll want to fill the list with choices (note that when you get a 'select' event, it'll include the numerical index of the choice the user selected). There're different ways to fill the list with choices but we'll only go into one, the LstSetDrawFunction function, here. With this method, the Palm OS calls a function we designate each time it wants to draw a list item on the screen. This works great for dynamically changing lists (where, for example, the user can add or remove things from the list) but it's also good because we don't have to allocate space for the list items and we don't have to deal with deallocating that space.

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 REMEMBERED
OnBoard 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.

How To Handle One -- In The Code

The user is probably going to tap on one of your list's choices and you should have some code to deal with that. That code goes in your form's event handler (that was mainFormEventHandler in the above example) at the place marked 'ADD EVENT HANDLING HERE'). Add the following code at that spot:

  	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.




UI Objects: Lists (with Popup Trigger)

What They Are

This is a variation on a list where the list doesn't appear until the user taps a button-like trigger.

How To Make One -- In The Resource Editor

Make a list just like before, only don't make it usable. The idea is that the list doesn't appear (i.e., it's not usable) until the user taps the popup trigger.

Then, make a popup trigger. Overlay it on the top of the list.

Make the popup -- this associates the list with the popup trigger.

Now, close the form.

How To Set One Up -- In The Code

You'll install the list draw function almost exactly the same way as before.

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 REMEMBERED
OnBoard 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.

How To Handle One -- In The Code

The user will select an item off your list in two steps. First, he'll tap on the popup trigger -- the Palm OS will handle this by showing your list. Next, he'll probably to tap on one of your list's choices and you should have some code to deal with that. It's almost exactly like the plain list-handling code except that the user selecting a list item gives you get a popSelectEvent rather than a lstSelectEvent. That code goes in your form's event handler (that was mainFormEventHandler in the above example) at the place marked 'ADD EVENT HANDLING HERE'). Add the following code at that spot:

  	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.




UI Objects: Push Buttons (AKA Radio Buttons)

What They Are

Push Buttons are a group of buttons that work in concert. Only one can be pushed at a time.

How To Make One -- In The Resource Editor

Well, you can't make just one -- it's a group of buttons so you have to make at least two. The user-interface standards are to make them immediately next to each other or immediately over the top of each other. We'll make ours next to each other.

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.

Now create the first button:

Now, make the second button:

And then, finish it off:

How To Set One Up -- In The Code

There's nothing that you need to do in the code before the buttons can be drawn on the screen.

How To Handle One -- In The Code

In your code, you'll want to handle the event caused by someone pressing one of the buttons button.

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	1021
  
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 UpButton:
  			WinDrawChars("Up", 2, 60, 30);
  			handled	= true;
  			break;
  		case DownButton:
  			WinDrawChars("Down", 4, 60, 30);
  			handled	= true;
  			break;
  		// other buttons...
  		}
  		break;
  



UI Objects: Check Boxes

What They Are

Most programmers familiar with GUI programming will know the check box. It is that little box that can have a tick in it. If you have been following this example you have been using them for 'Usable' and 'Enabled' in the Resource Editor.

How To Make One -- In The Resource Editor

How To Set One Up -- In The Code

There's nothing that you need to do in the code before the check box can be drawn on the screen.

How To Handle One -- In The Code

In your code there are three things you might be interested in doing. At any stage in your program you might want to know whether the check box is ticked or not, you might want to set the checkbox to ticked or not, or you might want to take some action when the user ticks or un-ticks the check box.

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 	1005
  
With 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;
  



UI Objects: Sliders

What They Are

How To Make One -- In The Resource Editor

How To Set One Up -- In The Code

How To Handle One -- In The Code




UI Objects: Feedback Sliders

What They Are

How To Make One -- In The Resource Editor

How To Set One Up -- In The Code

How To Handle One -- In The Code




Memory Handling

Memory is handled a bit differently on the Palm than in most programming environments. The Palm has a stack like most machines. The stack is the place where each function's local variables are stored but a variable's space is given away to other variables when the function returns. The heap, on the other hand, is treated differently on the Palm. You can allocate memory normally (using MemPtrNew and MemPtrFree rather than malloc and free) or you can declare something static or global but you really don't want to do that. The problem with that is that there's not a lot of memory and it can easily get fragmented. What you really want is to make your allocations relocatable with MemHandleNew and MemHandleFree. The Palm OS can move this memory around (thus, unfragmenting it and making more of it available) when you're not using it. When you want to use it, get a pointer with MemHandleLock but don't forget to unlock the memory chunk with MemHandleUnlock. An example of using this technique is as follows.

	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);



UI Objects: Text Fields

What They Are

A text field is a line or block of text which can show and edit a text string. This string can live either directly in a database, or be from a string in memory. Editing is handled by the system, so if you're happy with the way you edit text on a Palm, you only need to worry about the setup of the field, and not the event loop.

How To Make One -- In The Resource Editor

You need to add the field to the main form in your program and once again, we'll do that in RsrcEdit. You tie the field in your resource file to your program by remembering the ID of the field and referring to that number in your code. We'll start by adding the field to your form.

How To Set One Up -- In The Code

There are several things you have to do to set up a field in the code, and there are more things you have to do when you have finished with it. To start with, we need to link the text field to a region of memory that holds the text, then we need to tell the system to draw it.

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 REMEMBERED
Next, 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");

How To Handle One -- In The Code

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;

How To Shut One Down -- In The Code

There's one more function that will interest you if the text in your field is actually living in a database. Since a field deallocates its text when your program shuts down, you'll want to remove any text that is still living (i.e., in a database) before you shut down. You do this with the 'nullField' function.

	// 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);




UI Objects: Scroll Bars

What They Are

How To Make One -- In The Resource Editor

How To Set One Up -- In The Code

How To Handle One -- In The Code




UI Objects: Tables

What They Are

How To Make One -- In The Resource Editor

How To Set One Up -- In The Code

How To Handle One -- In The Code




UI Objects: Alerts

What They Are

An alert is a little pop up with some text and a/some button(s). Also, it is possible to use a nifty little trick that puts text on an alert recource during execution (without using dynamic resource creation).

How To Make One -- In The Resource Editor

This is where you will do most of the work to get an alert. One small explanation first. There is a function (described in the 'How to Handle...' section that will replace text in an alert, specifically the text ^1, ^2, and ^3 with text you provide it. If you want to use this feature put ^1 and/or ^2 and/or ^3 in the message that your alert displays. Many people have special 'Debug' alerts made up of only the text "^1^2^3" that they can then use to get info out of their programs.

How To Set One Up -- In The Code

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 Alert1 1200; 	// HERE'S THE NUMBER YOU REMEMBERED

How To Handle One -- In The Code

There are two ways to do this. Both of them consist of calling functions.

WARNING

Be very careful here! You can lock your palm into an infinite loop if you put this somewhere that it will be called on every iteration of the event loop. The event loop of the active program handles all UI interaction, that is, it recieves every event. Now, a button tap is an event, a hard key (including the power button) press is an event, everything. This means that when someone taps a button on an alert an event occurs. If there is an alert happening every time round each alert spawns another alert!

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);


UI Objects: Icons

What They Are

Icons are the pretty pictures that show up in the app launcher. There are two kinds, the large icons, and the list-view icons. Large icons

How To Make One -- In The Resource Editor

This is the only place you need to do any work with icons.

How To Set One Up -- In The Code

There is nothing to do in the code.

How To Handle One -- In The Code

Ditto, nothing to do.


UI Objects: Forms

What They Are

As described earlier, a Form is a canvas on which you can put other UI objects. Every application has at least one but many have more than one. Every alert box is a form (though the guts of handling it is in the Palm OS rather than in your code).

How To Make One -- In The Resource Editor

Starting in OnBoard C, do the following:

In RsrcEdit, do the following:

Now, we have to add a title to make it a proper form:

Finally, we finish up:

How To Set One Up -- In The Code

We need a few things, now. We could use a way to get to the form, a startup function for the form, and an event handler for the form. We'll use the button that we generated earlier to go to our new form but how you get there depends on why you want the form in your code.

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 REMEMBERED
I'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 (actually, these all get sent to the application -- I was just explaining where we'll handle them).

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);
	}
  

How To Handle One -- In The Code

Nothing to do but sit back and enjoy your new form. Typically, you'd have a way to get back to your main form but that depends on the application.


Beginning Database

There're two different ways to have permanent storage on a Palm. While there's off-line memory cards on some Palms, even the oldest Palm devices have non-volatile storage in which you can store databases. We'll look at databases, here.

In the first article, here, we'll discuss reading from an existing database -- the memo database.

Open the Database.

First, we open the database.


	LocalID		dbld;
	DmOpenRef	dbRef;
	MemHandle	record;
	CharPtr		pChar;

	//...

	dbld = DmFindDatabase (0, "MemoDB");
	if (dbld != 0)
		dbRef = DmOpenDatabase (0, dbld, dmModeReadWrite);

Read the Database.

Now, we can navigate through the database. When you get a record from a database, you get a handle. Remember that you have to lock a handle to make a pointer that you can use.

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);
		}
	}

Write the Database.

Writing to a database is just the tiniest bit more complicated than reading. The thing is, the Palm folks didn't want anyone with a stray pointer to be able to trash a database. For that reason, they protected database memory and made you use a function to write it.

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);
	}

Close the Database.

And, finally, you have to close the database when you're done.
	if (dbRef != 0)
		DmCloseDatabase (dbRef);



Using Shared Libraries

There are libraries that can augment your Palm code. The most common library of this sort is the math library. In order to use a library, do the following.

Open the Library.

First, we open the library. First, you have to find the library (with SysLibFind). Next, you load the library (with SysLibLoad). Finally, you need to open the library using the 'open' function defined in the library header file.

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);
	}

Use the Library.

Call the functions that are defined in the library header -- I'm not going too far into this since this isn't really about the use of any particular library.
	FlpCompDouble x;
	x.d = sqrt(867.5309) * tan(42);
	FlpFToA(x.fd, buffer);

Close the Libary.

And, finally, you have to close the library when you're done. A good place to do this is in the stopApp function at the end of the execution.
	if (MathLibRef != -1) 
	{
		Err err;
		UInt16 usecount;
		err = MathLibClose (MathLibRef, &usecount);
		if (usecount == 0)
			SysLibRemove (MathLibRef);
	}



Preferences

An application can have a database that it accesses every time it is run (in addition to the separate databases it can access). This is often used to house the applications options or preferences. This information is often stored in a struct.

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;

Open Preferences.

A good place to open the preference database is the startApp function. One thing you may want to do is to provide for your application changing its preferences over different versions. A simplistic guard for that is to check the size of the preference database with the size you think it should be. Another is to check the value returned by PrefGetAppPreferences. We use the size in the following code.

	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;
		}
	}

Use the Preferences.

Now, you can use the preferences any way you want. It's a global variable, so set the values, read the values, whatever.

Write Preferences.

And, finally, you have to write the preferences back to the preference database. A great place to do that is the stopApp function.
	void stopApp() 
	{
		PrefSetAppPreferences	(AppCreator, 
					 1000,	// pref database id
					 1, 	// version of pref database
					 &prefs, 
					 sizeof(Prefs), 
					 true);	// saved during hotsync
	}