第四章 窗体的编程
在本章中,我将通过为上一章的Contacts程序添加一个About对话框,继续向读者展示Palm OS简单有效的程序风格。你将学会如何创建含有位图、文本和按钮的窗体。你可在Contacts程序中加入菜单和代码使你可以访问About对话框并重新回到Contacts主窗体。
在此过程中,我们将先了解应用程序的基本设置,将为Contacts程序创建一个大的和一个小的应用程序图标。
程序设置
我们在这部分中将改变一些对整个程序都有影响的设置。同时还将为Contacts创建图标。
首先,为你的程序做一个备份:
1. 打开Windows文件浏览器;
2. 找到Contacts文件夹;
3. 复制并粘贴Contacts;
4. 命名新文件夹为Contacts CH.3。这就是你的备份文件夹。
创建大小应用程序图标
先查看一下资源构造器的工程设置(Project Settings)。
注意:
Code Warrior6所带的构造器版本有一个bug——不允许创建和编辑多位(multibit)图标。我们找到了一个解决的办法,在本书附带的CD里面找到文件icon.txt,里面写有如何解决此问题的步骤。
1. 运行资源构造器,打开Contacts.rsrc;
2.在Contacts.rsrc窗口的底部找到工程设置图标。如果在窗口的底部只有一个指向右方箭头,单击则工程设置就会弹出。在工程设置中一般要改变的有:应用程序图标的名字、版本字符串和应用程序图标。在上一章节中你已经改变了应用程序图标的名称。表4-1列出了所有设置极其功能:
名称 描述
Generate App Resources 为应用程序产生版本和图标名称的资源,我建议你大多情况下选中次复选框。
Application Icon Name 应用程序的名称
Version String 应用程序的版本号。当对你的应用程序做出有意义修改后,你应该提高版本号表示不同版本。
Application Icon 定义了黑白的程序大图标,这是为Palm OS 2.0和更早版本准备的,在这里不用。
Auto Generate Header File 资源构造器自动生成头文件。选中。
Include Details in Header 资源构造器向头文件中添加
内容。选中。
Keep Ids in Sync 使资源构造器在控件ID改变时自动更改ID。如果你在程序中不大改变ID,你可以不选;但在一般情况下,选中会更好。
表4-1:资源构造器中工程设置的选项。
3. 创建应用程序大图标。从资源列表中选择Multibit Icon并按CTRL-K。一定要保证资源ID为1000。还要注意如果按下了应用程序图标属性边上的创建(Create)按钮,那将生成标准图标而不是多位图标。虽然这样也能产生大图标,但不够理想。
4.双击新图标,弹出一个编辑窗口,你现在就可以在里面画图了。通过选择编辑窗口右端的两个图标,确定颜色为黑白(black and white)还是2位的灰度级。编辑窗口内的编辑控件和Windows下的画图(Paint)及其它作图程序都很相似。当完成后,按窗口顶部右端的X即可关闭窗口。图4-1是本人所画的大图标;
图4-1 一个Contacts程序中的大图标
5. 接下来创建一个应用程序小图标。它只能在Palm OS 3.0或更高版本显示。打开Contactas.rsrc窗口,在Resource Type 和Name面板上选择Multibit Icons;
6. 按下CTRL-K创建一个新图标;
7. 点击ID,将其改为1001,这一步十分重要;
8. 点击新图标。弹出图标编辑窗口,由于Palm OS只显示上部9象素和最左端的15象素,在设计时要注意。本人所画的小图标如图4-2所示;
图4-2 一个Contacts程序中的小图标
CodeWarrior集成开发环境中的工程设置
这部分所讲的是如何在使用应用程序的设置:
1. 行CodeWarrior集成开发环境,打开Contacts工程;
2. 选择Edit|Stater Settings。程序设置还叫Stater Settings的原因是自从我们建立Starter工程后还没有将它改名。在Stater Settings会发现大量的设置选项,你千万不要把它们搞混;
3. Stater Setting 对话栏的左边是设置结构树。选择Target子树下的Target Setting,将Target Name中的名字由Starter改为Contacts;保存,现在CodeWarrior集成开发环境将会调用工程Contacts;
4. 在Target子树下选中68K Target,将文件名从Starter.tmp改为Contacts.tmp;
5. Linker子树下选中PalmRez Post Linker。将Mac Resource Files 设置由Starter.tmp改为Contacts.tmp;将Output Files设置由Starter.prc改为Contacts.prc,将Database Name设置由空白添加Contacts-PPGU;
注意: 如前所述,在Palm设备中所有东西都是以数据库的形式保存的,应用程序也不例外。每个数据库都必须有唯一的名称,否则就会出现问题。Contacts-PPGU就是和它的唯一原始(creator)ID相关联的文件名,这样命名可以保证唯一性。有关更多原始ID的内容,请参看下一章。
在Contact Settings子树下还有很多其他的设置属性,但是这时候还没必要去设置它们。如果你感兴趣的话,可以参考CodeWarrior集成开发环境指南(Guide)的有关内容。
多窗体界面
在这部分里我们将再为Contacts程序添加一个窗体:About窗体。我们也将修改Contacts.c中的代码使窗体之间可相互切换。
Contacts.rsrc文件中内容的添加
我们将为资源文件添加一个About窗体:
1. 打开资源构造器,打开文件Contacts.rsrc;
2. 建About窗体。从资源文件列表中选择Forms并按下CTRL-K;
3. 命名窗体。单击名字Untitled改为About;
4. 击打开About窗体;
5. 找到Form对话面板左边的窗体属性。表4-2列出了窗体的各个属性极其用途;
6. 修改窗体属性。复选Save Behind,写入About Contacts。
表4-2 窗体属性
Left Origin 窗体的左侧位置的象素数,整个屏幕为160象素。
Top Origin 窗体的顶部位置的象素数,整个屏幕从上到下为160象素。
Width 窗体的宽度,窗体不一定占满整个屏幕。
Height 窗体的高度。
Usable 决定窗体是否可见,如果对象没有标识为Usable,则它是不可见的。窗体一般情况下标识为Usable。
Modal 如果窗体为工作窗体且选取Modal,则在窗体外的笔击事件不起任何作用。Modal窗体的名字在窗体顶部的中央。Modal窗体不会被诸如警告之类的系统对话框中断,所以使用时要慎重。
Save Behind 如果选中,窗体关闭后,对窗体操作前的屏幕上窗体后的内容将被保存。
Form ID 窗体的ID号,ID唯一标识窗体。
Help ID 如果窗体为Modal,可填入代表帮助信息资源文件的ID字符串,在窗体的右上角会出现一个“i”图标。如果按下,则帮助信息就会被调出。在Date Book应用程序中的Details窗体有一个这样的例子可供参考。
Menu Bar ID 窗体菜单栏的ID号,在最后一章节中,我们将利用这一属性添加一个菜单拦。
Default Button ID 如果提供了此ID号,当用户切换的其它应用程序时,Palm OS在退出前会自动按下此按钮。这对Modal窗体来说尤其方便,经常使用的缺省按钮为Cancel。
Form Title 如果为窗体提供了标题,Palm OS会创建写有你设定标题的一个标题栏。
添加位图
为About窗体添加一个位图,步骤如下:
1. 选择Resource Type and Name列表下的Bitmaps;
2. 按CTRL-K创建新位图;
3. 在构造器中,选择Options | Set Image Size。可让你调整位图的大小;
4. 调整位图大小为42x42;
5. 绘制位图。我做的图如图4-3所示。用构造器的编辑器做出如此大的图是很困难的,我是先使用了画笔(Paint)绘好后然剪切过来的;
6. 绘完后。单击右上角的X关闭位图编辑器;
7. 将位图添加到Contacts窗体。选中Window | Catalog打开Catalog窗口。从其中拖动一位图到About窗体;
8. 改变位图的属性。位图的属性意义十分直观。请参考表4-3;
9. 将Bitmap Resource ID置为前面部分创建的位图的资源ID号。它的ID号应为1000。此时,你应在位图出现的一般位置看到自己的位图;
10. 设定位图在窗体中的Left Origin和Top Origin。置位图左侧位置为59(让它处于窗体中间)顶部位置为20(让它在标题栏下)。
Object ID 在窗体中标识位图唯一的ID号。此号不能和应用程序中其它任何的ID号相同。
Object Identifier 位图的名称。
Left Origin 位图左边位置的象素数,全屏幕左右长度为160象素。
Top Origin 位图顶部位置的象素数,全屏幕上下长度为160象素。
Bitmap Resource ID 决定位图的资源文件的ID号。
Usable 决定位图的可见与否。只有当标识为Usable时,位图才可见。
添加标签
现在创建一个包含Contacts信息的标签。你可以从Catalog窗口把一个标签拖到窗体上。表4-4列出了标签对象的属性。
Object Identifier 标签名。构造器在Contacts_res.h文件中创建的变量就来源于此名。
Label ID Palm OS用来识别该对象的唯一ID号。
Left Origin 标签左边位置的象素数。全屏幕左右长度为160象素。标签的长度由它包含的文字数决定。
Top Origin 标签顶端位置的象素数。全屏幕上下长度为160象素。标签包含文字的行数和字体的大小将决定标签的高度。
Usable 决定标签是否在窗体上可见。如果未选中的话,标签为不可见。当你想不改变窗体而使控件可见或不可见时,就要用到此设置。
Font 标签内文字字体的大小。你可以通过Windows Key
Caps 实用工具查找你所需字体。
Text 标签内显示的文字。
在标签中的内容一般为应用程序的名字、你或你的公司的名称、版权信息和程序的版本号等。我创建了两个标签是为了使Contacts的字体比剩下的字体大一些。
由于位图和标签在代码中没被提到,因此再没有比资源构造器更能满足你对不同位图和标签的要求了。我通过Windows Key Caps 实用工具从Pilot基本字体中找到了版本符号,并且复制字符到了资源构造器中。
添加按钮
在About窗体的底部创建一个按钮。你可把它命名为OK或任何其它的名字。
当Contacts.c修改后,此按钮的功能是关闭About窗体并返回到Contacts窗体。因为我们除了在初始的例子接触到按钮外,还没有正式介绍过,所以下面详细介绍一下按钮的各个属性。
Object Identifier 按钮的名称。构造器在
Contacts_res.h文件中创建的变量就来源于此名。
Button ID Palm OS用来识别该对象的唯一ID号。
Left Origin 按钮左边位置的象素数。全屏幕左右长度为160象素。由于按钮的左侧边到窗体的边框至少需一个象素,所以按钮的最左端也应置1。对bold按钮来说,要需二个象素。
Width 按钮的宽度。而实际的宽度,对普通按钮来说应加2,对bold按钮来讲应加2。
Height 按钮的高度。而实际的宽度,对普通按钮来说应加2,对bold按钮来讲应加2。
Usable 决定按钮是否在窗体上可见。如果未选中的话,按钮为不可见。当你想不改变窗体而使控件可见或不可见时,就要用到此设置。
Anchor Left 决定当应用程序运行时,在改变标签长度时,按钮的大小怎样作自我调整。当被选中时,则按钮的右端会做相应的伸长或缩短;否则,按钮的左侧作相应的伸长或缩短。
Frame 如果被选中,则按钮有一边框。
Non-Bold Frame 如果选中,则按钮边框占一象素;否则,边框占二象素宽。
Font 按钮上文字字体的大小。你可以通过Windows Key
Caps 实用工具查找你需要的字体。
Label 按钮上显示的文字。
把按钮命名为OK。当设置完所有属性后,你将看到一个图4-4所示的图形界面。
添加菜单
通过对Option菜单条目的选择可以访问About窗体。创建Option菜单并加入Contacts菜单栏。步骤如下:
2. 在Resource Type and Name List中选择Menus,按CTRL-K创建一新菜单;
3. 单击name区,将其命名为Options;
4. 双击Options打开;
5. 在菜单栏中将标题改为Options;
6. 按下CTRL-K创建一个新菜单。命名为About Contacts;
7. 关闭Options菜单;
8. 双击打开Contact Detail菜单栏;
9. 将Options菜单从Resource Type and Name List中拖到Contact Detail菜单栏Edit菜单的后面。当你做完后,你的Contacts菜单应如图4-5所示;
10. 关闭并保存Contacts.rsrc。
图4-4:当前窗体外观
向Contracts.c添加代码
如要使多个窗体运转,我们需要在Contracts.c中添加代码和其他的一些部分。首先添加的是一个Option菜单,同时我们也要为这个菜单添加一些代码。
Form-Loading代码更广的应用
因为我们要加载多个窗体,所以我们现在修改form-loading代码以便更加容易操作。
在文件的头部,将myHandleEvent()函数名改为contactDetailHandleEvent(),因为这个事件仅用于Contact Detail 窗体。修改之后的函数的原型如下:
// Prototypes for our event handler functions
static Boolean contactDetailHandleEvent( EventPtr *event );
myHandleEvent()函数的头部应修改如下:
// Our Contact DETAIL form event handler functions
static Boolean contactDetailHandleEvent( EventPtr event )
{
下一步,我们处理一个名为frmOpenEvent的事件。当从一个窗体切换到另一个窗体时,该事件被发送。在修改事件处理器以切换窗体之后,我要说明怎样修改窗体。
Word index; // CH.3 A general purpose index
FieldPtr field; // CH.3 Used for manipulating fields
// CH.3 Get our form pointer
form = FrmGetActiveForm();
// CH.4 Parse events
switch( event->eType )
{
// CH.4 Form open event
case frmOpenEvent:
{
// CH.3 Get the index of our field
index = FrmGetObjectIndex( form, ContactDetailFirstNameField );
// CH.3 Get the pointer to our field
field = FrmGetObjectPtr( form, index );
// CH.3 Set the editable text
FldSetTextHandle( field, htext );
// CH.2 Draw the form
FrmDrawForm( form );
// CH.3 Set the focus to our field
FrmSetFocus( form, index );
}
break;
frmOpenEvent块中,剪切从PilotMain()至事件循环的代码,把代码放到这里。这样,当我们在窗体之间切换时,每当窗体被显示时这段代码将被执行。在执行应用程序时,这
段代码将被执行若干次。
在我们切换窗体之前,最好先获取源窗体文本框中的内容。这是因为当我们切换到一个新的窗体时,与原窗体相关的所有内存都将被释放。这样一旦我们返回原窗体时就会碰到大麻烦。
我们通过对frmCloseEvent的处理来获取文本句柄。在切换到另一个窗体时,当前窗体被注销前,frmCloseEvent事件被触发。
// CH.4 Form close event
case frmCloseEvent:
{
// CH.3 Get the index of our field
index = FrmGetObjectIndex( form, ContactDetailFirstNameField );
// CH.3 Get the pointer to our field
field = FrmGetObjectPtr( form, index );
// CH.4 Unlink our handle from the field
FldSetTextHandle( field, NULL );
}
break;
我们在这里所做的一切是获取指向域的指针,将它的文本句柄设为NULL。
我们需要用一个将导致frmOpenEvent被发送的调用来替换我们从PilotMain()中剪切下来的代码。如果我们不这样做,什么事情都不会发生。我们将永远盯着一个空白屏幕,因为第一个窗体永远不会被加载。你用来发送frmLoadForm和frmOpenForm事件来切换窗体的函数是FrmGotoForm()。在PilotMain()中FrmGotoForm()之外的代码如下:
//CH.3 Release the string resource
DmReleaseResource(hsrc);
//CH.4 Go to our starting page
FrmGotoForm(ContactDetailTorm);
//CH.2 Our event loop
do
{
用于推广窗体加载的最后一点代码是将事件处理函数从一个窗体切换至另一个窗体。每一个窗体有它自己的事件处理。一个好的方法是应答frmLoadForm事件,即使是在主事件循环中。代码如下:
// CH.3 Handle menu events
if( MenuHandleEvent( NULL, &event, &error ) )
continue;
// CH.4 Handle form load events
if( event.eType == frmLoadEvent )
{
// CH.4 Initialize our form
switch( event.data.frmLoad.formID )
{
// CH.4 Contact Detail form
case ContactDetailForm:
form = FrmInitForm( ContactDetailForm );
FrmSetEventHandler( form, contactDetailHandleEvent );
break;
// CH.4 About form
case AboutForm:
form = FrmInitForm( AboutForm );
FrmSetEventHandler( form, aboutHandleEvent );
break;
}
FrmSetActiveForm( form );
}
// CH.2 Handle form events
FrmDispatchEvent( &event );
对每一个我们要加载的窗体,我们在switch语句中增加case语句来初始化窗体并设置对应的事件处理。
现在是为我们在上一章中分配的内存在做一些工作的时候了。尽管当应用进程从内存卸下时我们的文本句柄将被释放,我们最好还是在PilotMain()末尾显式卸载它,因为关闭窗体不再释放其内的文本句柄。
// CH.4 Deallocate memory
MemHandleFree( htext );
// CH.2 We're done
return( 0 );
程序的整洁性是与应用进程是否崩溃的相关的重要因素。当你的程序变得越来越复杂,你的程序代码到处搬动时,有序地分配和释放内存将为你免去应用程序崩溃之灾,为你节约大量时间。
处理Options菜单
我们将按惯例选择菜单项来获得About窗体。因为所有的菜单ID都是唯一的,你仅需在现有的菜单时间处理中为Contacts添加一些代码:
// CH.3 Erase the menu status from the display
MenuEraseStatus( NULL );
// CH.4 Handle options menu
if( event->data.menu.itemID == OptionsAboutContacts )
{
// CH.4 Pop up the About form as a Dialog
FrmPopupForm( AboutForm );
return( true );
}
// CH.3 Handle graffiti help
if( event->data.menu.itemID == EditGraffitiHelp )
{
// CH.3 Pop up the graffiti reference based on
// the graffiti state
SysGraffitiReferenceDialog( referenceDefault );
return( true );
}
在调用MenuEraseStatus()之后在MenuEventHandler()中增加的代码将正常运转。你检查新选择项的ID,然后调用FrmPopusForm()。FrmPopusForm()与FrmGotoForm()相似,除了旧的窗体从不关闭。这在About窗体中是恰当的,因为我们知道我们将从About窗体中返回到Contact Detail窗体中。除此之外没有别的路径。
这意味着我们不需要作我们在上一章所做的通用的窗体变换工作。尽管如此,我们最好加上窗体变换代码,这样我们不必担心在加入窗体时使Contact Detail窗体的行为变乱。
注意:在这里我们在处理代码中原本为了处理edit菜单的options菜单。这是怎么回事?原来,Contructor(构造器)给所有菜单的所有菜单项赋予唯一的ID号。因此,没必要单独处理某一组菜单项。
为About窗体加入一个事件处理
每一个窗体应有它自己的时间处理,因此我们为新的About窗体添加一个事件处理。首先,为About窗体的事件处理函数添加一个函数原型,称之为aboutHandleEvent()。代码如下:
// CH.3 Prototypes for our event handler functions
static Boolean contactDetailHandleEvent( EventPtr event );
static Boolean aboutHandleEvent( EventPtr event );
static Boolean menuEventHandler( EventPtr event );
然后,在处理frmLoadEvent的事件循环中加入代码。这段代码将对About窗体初始化并以FrmDispatchEvent()为目标加入事件处理函数。
// CH.4 Initialize our form
switch( event.data.frmLoad.formID )
{
// CH.4 Contact Detail form
case ContactDetailForm:
form = FrmInitForm( ContactDetailForm );
FrmSetEventHandler( form, contactDetailHandleEvent );
break;
// CH.4 About form
case AboutForm:
form = FrmInitForm( AboutForm );
FrmSetEventHandler( form, aboutHandleEvent );
break;
}
剩下的所有的事就是加入时间处理函数aboutHandleEvent()。
// CH.4 Our About form event handler function
static Boolean aboutHandleEvent( EventPtr event )
{
FormPtr form; // CH.4 A pointer to our form structure
// CH.4 Get our form pointer
form = FrmGetActiveForm();
// CH.4 Respond to the Open event
if( event->eType == frmOpenEvent )
{
// CH.4 Draw the form
FrmDrawForm( form );
}
// CH.4 Return to the calling form
if( event->eType == ctlSelectEvent )
{
FrmReturnToForm( 0 );
// CH.4 Always return true in this case
return( true );
}
事件处理函数对frmOpenEvent应答以唯一的显示窗体基本调用:FrmDrawForm()。它也像我们的第一个Hello应用程序一样应答ctlSelectEvent,除了在这一次,它不发送告警,而是调用FrmReturnToForm()。FrmReturnToForm()函数使About窗体被消灭,该窗体的调用窗体(在这里是Contact Detail窗体)被重激活。
注意:在返回调用窗体时返回true是十分重要的。原因是否则Palm OS试图全面处理cltSelectEvent事件,处理工作包括处理在调用FrmReturnToForm中被你消灭的结构。为了避免这些内存恶作剧,在FrmReturnToForm之后总是返回true, 这样窗体会立即消失。
调试
又回到调试了。你的Contact窗体看起来和工作起来和以前一样。当你从Options菜单选择对应项时,你的About窗体会弹出。当你按下它的OK按钮时,窗体返回Contact Detail窗体。Contact Detail窗体含有一个包含原有的Edit菜单和新的Options菜单。About窗体没有菜单条因为我们没有定义它。
如同以前一样,程序在PilotMain()开始执行,按照它通常的路径走下去。当frmLoadEvent
事件被处理时,它初始化Contact Detail窗体的事件处理函数。之后,frmOpenEvent事件被发送到Contact Detail窗体的事件处理函数。在那里,事件被处理,窗体被画出,各个域如以前一样被设置。然后程序等在那里等待事件的发生。
Contact Detail 窗体处于等待输入状态
接下来讲什么?
在下一章中,我们将继续考察更多的基于Palm设备的控件。
源代码
下面是新版本的Contacts.c的所有代码。
// CH.2 The super-include for PalmOS
#include
// CH.3 Our resource file
#include "Contacts_res.h"
// CH.3 Prototypes for our event handler functions
static Boolean contactDetailHandleEvent( EventPtr event );
static Boolean aboutHandleEvent( EventPtr event );
static Boolean menuEventHandler( EventPtr event );
// CH.3 Our field memory handle
static Handle htext; // CH.3 Handle to the text in our edit field
#define HTEXT_SIZE 81 // CH.3 Size of our edit field
// CH.4 Constants for ROM revision
#define ROM_VERSION_2 0x02003000
#define ROM_VERSION_MIN ROM_VERSION_2
// CH.2 The main entry point
DWord PilotMain( Word cmd, Ptr, Word )
{
DWord romVersion; // CH.4 ROM version
FormPtr form; // CH.2 A pointer to our form structure
Handle hsrc; // CH.3 Handle to the string resource
CharPtr psrc; // CH.3 Points to the text in the resource
CharPtr ptext; // CH.3 Points to the text in the edit field
EventType event; // CH.2 Our event structure
Word error; // CH.3 Error word
// CH.4 Get the ROM version
romVersion = 0;
FtrGet( sysFtrCreator, sysFtrNumROMVersion, &romVersion );
// CH.4 If we are below our minimum acceptable ROM revision
if( romVersion < ROM_VERSION_MIN )
{
// CH.4 Display the alert
FrmAlert( LowROMVersionErrorAlert );
// CH.4 PalmOS 1.0 will continuously re-launch this app
// unless we switch to another safe one
if( romVersion < ROM_VERSION_2 )
{
AppLaunchWithCommand( sysFileCDefaultApp,
sysAppLaunchCmdNormalLaunch, NULL );
}
return( 0 );
}
// CH.2 If this is not a normal launch, don't launch
if( cmd != sysAppLaunchCmdNormalLaunch )
return( 0 );
// CH.3 Get the initialization string resource handle
hsrc = DmGetResource( strRsc, FieldInitString );
// CH.3 Lock the resource, get the pointer
psrc = MemHandleLock( hsrc );
// CH.3 Allocate our field chunk
htext = MemHandleNew( HTEXT_SIZE );
if( htext == NULL )
return( 0 );
// CH.3 Lock the memory, get the pointer
ptext = MemHandleLock( htext );
// CH.3 Initialize it
StrCopy( ptext, psrc );
// CH.3 Unlock the field's memory
MemHandleUnlock( htext );
// CH.3 Unlock the resource's memory
MemHandleUnlock( hsrc );
// CH.3 Release the string resource
DmReleaseResource( hsrc );
// CH.4 Go to our starting page
FrmGotoForm( ContactDetailForm );
// CH.2 Our event loop
do
{
// CH.2 Get the next event
EvtGetEvent( &event, -1 );
// CH.2 Handle system events
if( SysHandleEvent( &event ) )
continue;
// CH.3 Handle menu events
if( MenuHandleEvent( NULL, &event, &error ) )
continue;
// CH.4 Handle form load events
if( event.eType == frmLoadEvent )
{
// CH.4 Initialize our form
switch( event.data.frmLoad.formID )
{
// CH.4 Contact Detail form
case ContactDetailForm:
form = FrmInitForm( ContactDetailForm );
FrmSetEventHandler( form, contactDetailHandleEvent );
break;
// CH.4 About form
case AboutForm:
form = FrmInitForm( AboutForm );
FrmSetEventHandler( form, aboutHandleEvent );
break;
}
FrmSetActiveForm( form );
}
// CH.2 Handle form events
FrmDispatchEvent( &event );
// CH.2 If it's a stop event, exit
} while( event.eType != appStopEvent );
// CH.4 Deallocate memory
MemHandleFree( htext );
// CH.2 We're done
return( 0 );
}
// CH.4 Our Contacts form handler function
static Boolean contactDetailHandleEvent( EventPtr event )
{
FormPtr form; // CH.3 A pointer to our form structure
Word index; // CH.3 A general purpose index
FieldPtr field; // CH.3 Used for manipulating fields
// CH.3 Get our form pointer
form = FrmGetActiveForm();
// CH.4 Parse events
switch( event->eType )
{
// CH.4 Form open event
case frmOpenEvent:
{
// CH.3 Get the index of our field
index = FrmGetObjectIndex( form, ContactDetailFirstNameField );
// CH.3 Get the pointer to our field
field = FrmGetObjectPtr( form, index );
// CH.3 Set the editable text
FldSetTextHandle( field, htext );
// CH.2 Draw the form
FrmDrawForm( form );
// CH.3 Set the focus to our field
FrmSetFocus( form, index );
}
break;
// CH.4 Form close event
case frmCloseEvent:
{
// CH.3 Get the index of our field
index = FrmGetObjectIndex( form, ContactDetailFirstNameField );
// CH.3 Get the pointer to our field
field = FrmGetObjectPtr( form, index );
// CH.4 Unlink our handle from the field
FldSetTextHandle( field, NULL );
}
break;
// CH.3 Parse menu events
case menuEvent:
return( menuEventHandler( event ) );
break;
}
// CH.2 We're done
return( false );
}
// CH.4 Our About form event handler function
static Boolean aboutHandleEvent( EventPtr event )
{
FormPtr form; // CH.4 A pointer to our form structure
// CH.4 Get our form pointer
form = FrmGetActiveForm();
// CH.4 Respond to the Open event
if( event->eType == frmOpenEvent )
{
// CH.4 Draw the form
FrmDrawForm( form );
}
// CH.4 Return to the calling form
if( event->eType == ctlSelectEvent )
{
FrmReturnToForm( 0 );
// CH.4 Always return true in this case
return( true );
}
// CH.4 We're done
return( false );
}
// CH.3 Handle menu events
Boolean menuEventHandler( EventPtr event )
{
FormPtr form; // CH.3 A pointer to our form structure
Word index; // CH.3 A general purpose control index
FieldPtr field; // CH.3 Used for manipulating fields
// CH.3 Get our form pointer
form = FrmGetActiveForm();
// CH.3 Erase the menu status from the display
MenuEraseStatus( NULL );
// CH.4 Handle options menu
if( event->data.menu.itemID == OptionsAboutContacts )
{
// CH.4 Pop up the About form as a Dialog
FrmPopupForm( AboutForm );
return( true );
}
// CH.3 Handle graffiti help
if( event->data.menu.itemID == EditGraffitiHelp )
{
// CH.3 Pop up the graffiti reference based on
// the graffiti state
SysGraffitiReferenceDialog( referenceDefault );
return( true );
}
// CH.3 Get the index of our field
index = FrmGetFocus( form );
// CH.3 If there is no field selected, we're done
if( index == noFocus )
return( false );
// CH.3 Get the pointer of our field
field = FrmGetObjectPtr( form, index );
// CH.3 Do the edit command
switch( event->data.menu.itemID )
{
// CH.3 Undo
case EditUndo:
FldUndo( field );
break;
// CH.3 Cut
case EditCut:
FldCut( field );
break;
// CH.3 Copy
case EditCopy:
FldCopy( field );
break;
// CH.3 Paste
case EditPaste:
FldPaste( field );
break;
// CH.3 Select All
case EditSelectAll:
{
// CH.3 Get the length of the string in the field
Word length = FldGetTextLength( field );
// CH.3 Sound an error if appropriate
if( length == 0 )
{
SndPlaySystemSound( sndError );
return( false );
}
// CH.3 Select the whole string
FldSetSelection( field, 0, length );
}
break;
// CH.3 Bring up the keyboard tool
case EditKeyboard:
SysKeyboardDialogV10();
break;
}
// CH.3 We're done
return( true );
}