分享
 
 
 

PalmOS开发教程-9

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

第九章 分类和查找

在这一章中,我们在Contacts程序中添加分类。分类允许把Contacts分成组,如Business和Personal。你可以分别或统一查看这些组。你还可以为Contacts应用程序添加、删除、或者改变分类名称。你可以把每一条记录分配到一个组。

我们也将在Contacts中添加代码使用Palm OS系统查找(Find)功能在Contacts数据库中查找相关的内容。你可以输入短日期或者人的全名,在存储有不同的字段和格式的数据库中找到匹配的记录。你也可以选中任何一个找到的条目,让其在Contact Detail窗体中显示。

保存工程

在做下一步之前,还是要提醒你这一点。操作步骤如下:

1.运行Windows浏览器;

2.找到工程存放的文件夹;

3.选中文件夹,按CTRL+C来将其复制;

4.选择一个文件夹用来保存副本;

5.按CTRL+V将副本粘贴到备份文件夹中;

6.把项目名重命名为容易记的名字,我把它命名为Contacts CH.8。

分类

如果你想把应用程序放在一个组中,分类正好可以实现。可以给一个应用程序定义15个组,这样多的组对于一般的应用程序已足够了。在我的Palm上,任何一个应用程序的组都不超过6个。

Palm OS做了大量分类的工作。一旦把应用程序信息创建完成,分类管理器(Categories Manager)就会将其保持在那个地方。并且创建出下拉框来管理,允许对分类的创建、

修改和删除。

我们的主要工作是把分类中的记录分离出来并浏览。在这之前,处理记录的方法和以前完全相同。在显示记录之前,检查这条检查是否属于当前的分类。这就会引发滚动条的一些问题。

Contacts.rsrc的内容添加

在Constructor中添加三个分类:

◆在Contact List窗体添加弹出列表框,用来过滤contacts窗体列表。它必须与分类管理器(Category Manager)的要求一致;

◆在Contact Detail窗体中添加弹出列表框,用来选择当前记录所在的分类;

◆创建一个App Info String List资源,定义应用程序中所在的初始分类;

Contact List Form的内容添加

现在到了向Contact List窗体中加入资源时候了,让人们来看看我们具体的分类。

1.运行Metrowerks构造器;

2.打开资源文件Contacts.rsrc,它位于项目文件夹中的Src文件夹中;

3.双击打开Contact List窗体;

4.从菜单中选择Window | Catalog,打开Catalog;

5.拖动一个列表框(list)到窗体中;

6.设置列表框属性:Object Identifier=CategoryList,Left Origin=86,Top Origin=1,Width=72,不复选Usable项,因为我们不希望窗体显示的时候把列表也把窗体显示出来,Visible Items=0。作为分类服务的一部分,Palm OS将动态地建立列表(根据我们的要求)。

7.拖动一个弹出触发按纽(Pop-up trigger)到窗体上;

8.将触发器命名为CategoryPopup。Left Origin=160,Top Origin=0,Width=0。不复选Anchor Left,这样使触发器标签文本(从左端的160象素处)和屏幕右侧右对齐。我们将在程序中快速的删除或添加标签中的文字。

9.完成后,Contact List窗体看起来如图9-1。

注意:

你不需要和一般的弹出触发器一样来设置列表的ID,尽管做了也不会有什么不当。你将看到,我们不是调用触发器的ctlSelectEvent事件,而是调用了具体的分类函数。

Contact Detail Form的内容添加

现在我们修改Contact Detail Form,允许人们在分类具体的条目。

10.在Resource Type and Name列表中双击打开Contact Detail List窗体;

11.拖动一个标签到窗体中。设置标签属性:Text为Category,选择粗体。Top Origin=90,选中所有的(按住SHIFT点击)的标签,再选择Arrange | Align Right Edges,使它与其它右对齐的标签对齐;

12.拖动一个列表框控件到窗体中;

13.设置列表框属性:Object Identifier= CategoryList。Left Origin=80,Top Origin=90,Width=80。不复选Usable,因为我们不希望窗体显示的时候列表框就显示。设Visible Items为0。和其它的分类列表框一样,Palm OS将动态建立这个列表;

14.拖动弹出触发器到窗体中;

15.和Contact List窗体类似,把触发器命名为CategoryPopup。Left Origin=80,Top Origin=89,Width=80。和以前一样,你可以保留或者删除标签中的文字;

16.完成后,Contact Detail窗体看起来如图9-2;

17.最后,需要新建一个App Info String List资源来初始化分类名称。在Constructor的Resource Type and Name列表中点击App Info String Lists,并按CTRL-K 新建一个。注意不要创建一个旧的String List;

18.把App Info String List命名为Category Labels,双击打开;

19.在列表的前三个条目中输入:Unfiled,Business和Personal。

20.按12次ENTER键创建16个条目。这将初始化其它分类为空白而不是垃圾。完成后,列表看起来如图9-3。我重申一下:你必须初始化所有16个条目;否则,一些令人费解的事情就会出现。

这就把为支持分类对资源文件所需做的修改完成了。关闭Constuctor并及时保存资源文件。

Contacts.c的修改

为支持分类,在Contacts.c中需要添加四个任务:

◆创建数据结构,以符合Category Manager的要求;

◆支持Contact Detail窗体中分类弹出列表;

◆支持Contact List窗体中的分类弹出列表;

◆Contact List窗体在不同分类时,处理滚动条事件;

初始化分类

分类信息一般要保存在应用程序主数据库的信息模块上。为此,需要在PilotMain()中创建数据库后,新添加一些代码。在函数的顶部先添加一些新的变量:

LocalID dbID; // CH.9 Local ID of the database

UInt cardNum; // CH.9 Card number

LocalID appInfoID; // CH.9 Local ID of the app info block

VoidHand hAppInfo; // CH.9 Handle to the app info block

AppInfoPtr pAppInfo; // CH.9 Points to the app info block

然后,看一下应用程序的信息块:

// CH.9 Get the ID and card number

DmOpenDatabaseInfo( contactsDB, &dbID, NULL, NULL, &cardNum, NULL);

// CH.9 Get the app info pointer if any

DmDatabaseInfo( cardNum, dbID, NULL, NULL, NULL, NULL, NULL, NULL,

NULL, &appInfoID, NULL, NULL, NULL );

深入:

程序信息模块(App Info Block)

每个Palm OS数据库都有一个叫app info block特殊区域。你可以在这个区域中保存任何东西。如果已经使用它保存了分类,还可以在分类结构的末尾处插入具体的数据,Palm OS也会对此处理。我经常使用这个空间保存优先权(preferences)或数据库中全局变量。例如,可以使用它来存储数据库结构,由此可以使用代码处理不同类型的数据库。

小技巧

注意我们在这里细致定义了卡号,而不是只指定卡号为0。尽管卡号为0的卡在Palm Compting的设备上一般都能正常工作,但对一些第三方的硬件,特别是Handspring Visor和TRGpro,他们有一块以上的内存卡,为了支持更宽的Palm内存单元,正确的处理卡号在程序中也就变得很重要了。

如果我们没有找到已创建分类的应用程序信息块,就回到开头新建一个。

// CH.9 If there is no application info block, create one

if( appInfoID == 0 )

{

// CH.9 Allocate an application info block

if( (hAppInfo = DmNewHandle( contactsDB,

sizeof( AppInfoType ) )) == NULL )

errorExit( MemoryErrorAlert );

// CH.9 Translate the handle to a local ID

appInfoID = MemHandleToLocalID( hAppInfo );

// CH.9 Set the application info block

DmSetDatabaseInfo( cardNum, dbID, NULL, NULL, NULL, NULL, NULL,

NULL, NULL, &appInfoID, NULL, NULL, NULL );

// CH.9 Translate the local ID to a pointer

pAppInfo = MemLocalIDToLockedPtr( appInfoID, cardNum );

// CH.9 Clear it

DmSet( pAppInfo, 0, sizeof( AppInfoType ), 0 );

// CH.9 Initialize the categories

CategoryInitialize( pAppInfo, CategoryLabelsAppInfoStr );

// CH.9 Unlock the application info block

MemPtrUnlock( pAppInfo );

}

当调用Palm OS的函数CategoryInitialize()时,Palm OS自动地初始化分类。你需将在Constructor中创建的App Info String List ID号传给此函数。保证所使用的字符串就是上一部分中所定义的。如果不是,上面的分类就不会显示或者显示一些垃圾,但CategoryInitialize()的也不会返回错误。

小技巧

你可以在应用程序信息模块中保存任何东西。如果已保存了分类,你只需在应用程序信息模块扩展分类结构,然后添加你想保存的数据就可以了。不过应用程序信息模块中添加的数据必须符合分类结构形式。

对Contact Detail Form的支持

当我们浏览记录时,Contact Detail窗体应正确的显示每一条记录所在的分类,当我们翻动记录时。它也应该允许我们把分类设置为一个记录。

不像常规的弹出触发按纽(Pop-up Trigger),为了使用分类管理器(Category Manager)来处理事件,必须捕捉分类弹出触发按纽的初始值来触发ctlSelectEvent事件,然后使用Palm OS的函数CategorySelect()完成其他所有的工作。我们让事件返回true,来防止Palm OS在一般情况下对一个弹出触发按纽所做的操作(试图弹出一个列表框)。在这里,列表框的创建、弹出和关闭都是通过调用CategorySelect()来完成。

在程序的开始处,我们定义三个变量。变量detailCat代表在Contact Detail窗体中的显示当前记录的分类。ListCat代表Contact List窗体当前的分类。第三个变量talbeIndex,是为浏览特定分类中的记录而定义的,在下一部分中将做详细的解释。

// CH.9 Category variables

static Word listCat = dmAllCategories; // CH.9 The current category ID

static Word detailCat; // CH.9 Category ID for details

static UInt tableIndex[TABLE_NUM_ROWS]; // CH.9 Record indexes for rows

在contactDetailHandleEvent()事件的顶部,需要分配内存来保存分类的名称:

Char catName[dmCategoryLength]; // CH.9 Category name

下面是支持分类弹出触发按纽和列表框的代码,将之添加到ctlSelectEvent()事件中:

// CH.9 Catch a tap on the category trigger

case ContactDetailCategoryPopupPopTrigger:

{

UInt recAttrs; // CH.9 The record attribs

// CH.9 Palm OS will present the popup list for us.

CategorySelect( contactsDB, form,

ContactDetailCategoryPopupPopTrigger,

ContactDetailCategoryListList,

false, &detailCat, catName, 1, 0 );

// CH.9 Get the record attributes

DmRecordInfo( contactsDB, cursor, &recAttrs,

NULL, NULL );

// CH.9 Put in the category bits

recAttrs &= ~dmRecAttrCategoryMask;

recAttrs |= detailCat;

// CH.9 Set the record attributes

DmSetRecordInfo( contactsDB, cursor, &recAttrs,

NULL );

}

// CH.9 Set fields and return true in this case

setFields();

return( true );

}

在这里,CategorySelect()几乎完成了所有的困难工作,包括创建和管理列表框和可能要编辑的列表框条目。注意CategorySelect()中的第五个参数为False,这个参数提示CategorySelect()我们是使用列表框选择分类而不是对列表框的条目进行排序。这使CategorySelect()去掉了列表框中的All选项。

CategorySelect()中的第八个参数值(catName后面的一个)是1,提示CategorySelect()防止第一个分类Unfiled被修改。使用这种方式,就可以使很多分类不被修改。

你可以使用最后一个参数指定一个字符串作为分类编辑对话框的标题,来替代原来的Edit Categories。在所有例子中,迄今为止,我认为这个缺省的标题是最好的,所以我总是把它设为0。

注意:

Palm OS1.0有它自己版本的CategorySelect()—— CategorySelectV10()。如果你对支持原来的Pilot1000到5000单元有兴趣,就需要使用这个命令。

在调用CategorySelect()后,我们在当前记录的属性位上设置分类的值。首先DmRecordInfo()获取属性位,在做一些位运算后,通过DmRecordInfo()为属性设置新值。在事件处理过程中要做的就是这些。

在newRecord()函数中,我们为创建的每一个记录初始化其分

类。在函数的顶部,需要定义一个变量临时保存记录属性。

UInt recAttrs; // CH.9 The record's attributes

在函数的末尾,和在事件处理函数中一样,我们获得并设置记录的属性。如果在Contact List中我们选择的分类是All,我们就不知道将这个记录到底该放在哪一个分类中,这时我们就把分类设置为Unfiled。如果Contact List上面有一个具体的分类,那么就把记录放在这个分类中。

// Ch.9 Get the record attribute bits

DmRecordInfo( contactsDB, cursor, &recAttrs, NULL, NULL );

// CH.9 Clear the category bits

recAttrs &= ~dmRecAttrCategoryMask;

// CH.9 Set the category to the appropriate category

if( listCat == dmAllCategories )

recAttrs |= dmUnfiledCategory;

else

recAttrs |= listCat;

// CH.9 Set the record attributes

DmSetRecordInfo( contactsDB, cursor, &recAttrs,

NULL );

在attachFields()中,我们需要设置弹出触发按纽的标签文本。首先,在函数的顶部再定义一些新的变量。

UInt recAttrs; // CH.9 The record attribute bits

Char catName[dmCategoryLength]; // CH.6 The category name

在函数的末尾,我们使用一些专门的分类管理函数设置并重新得到标签文本。

// CH.9 Get the record attributes

DmRecordInfo( contactsDB, cursor, &recAttrs, NULL, NULL );

// CH.9 Get the category

detailCat = recAttrs & dmRecAttrCategoryMask;

// CH.9 Set the category popup trigger label

CategoryGetName( contactsDB, detailCat, catName );

CategorySetTriggerLabel( getObject( form,

ContactDetailCategoryPopupPopTrigger ), catName );

这样,使分类能在Contact List窗体上正常工作的代码修改工作就完成了,其中包括将具体的记录写入分类中。

对Contact List Form的支持

对Contact List窗体的修改要更多一些,因为我们需要将记录仅仅显示在一个给出的分类或者All分类中。问题是先前的代码很大程度上依赖于数据库中的每一个记录,例如,我们可以通过Cursor+1实现显示下一条记录。但是在分类中,我们就不能这样做了,因为里面或许只有一些甚至没有分类中所要显示的记录。

要知道当前分类到底有没有这条记录的一种方法是将记录用函数包装起来,这样我们就能象以前那样来浏览记录了。在程序的开头,先声明三个新函数的原型:

static void initIndexes( void );

static void scrollIndexes( Int amount );

static UInt findIndex( UInt scrollValue );

函数initIndexes()从当前分类中获得与之相关联的表的索引tableIndex。这样,我们就可以在指定的表中从头到尾的浏览,从而取代盲目地在所有记录间浏览。函数scrollIndexes()允许我们把具有特殊分类的窗口上移或下移。函数findIndex()返回给我们一个记录在分类中的游标位置。在以后的部分中将看到这些函数是如何工作的,现在我们只是在Contact List 窗体的事件处理函数及其它函数中调用它们来实现浏览的功能。

在Contact List窗体事件处理函数

contactListHandleEvent()应该怎样做呢?首先,在函数的开头保存分类的名称。

Char catName[dmCategoryLength]; // CH.9 Category name

修改窗体打开事件,设置弹出触发按纽的标签文本,并调用initIndexes()来初始化列表框。

// CH.7 Form open event

case frmOpenEvent:

{

// CH.7 Draw the form

FrmDrawForm( form );

// CH.9 Set the category popup trigger label

CategoryGetName( contactsDB, listCat, catName );

CategorySetTriggerLabel( getObject( form,

ContactListCategoryPopupPopTrigger ),

catName );

// CH.8 The cursor starts at the beginning

cursor = 0;

// CH.9 Initialize the table indexes

initIndexes();

// CH.8 Populate and draw the table

drawTable();

}

break;

当选中一个记录切换到Contact Detail窗体时,我们在tableIndex数组中查找所选中记录的游标位置。

// CH.7 Respond to a list selection

case tblSelectEvent:

{

// CH.7 Set the database cursor to the selected contact

cursor = tableIndex[event->data.tblSelect.row];

// CH.7 Go to contact details

FrmGotoForm( ContactDetailForm );

}

break;

在popSelectEvent事件中,由于把记录依据不同的标准进行了重新排序,所以必须根据排序的结果重新初始化tableIndex。

// CH.7 Sort the contact database by the new criteria

DmQuickSort( contactsDB, (DmComparF*)sortFunc, sortBy );

// CH.8 Cursor starts at zero

cursor = 0;

// CH.9 Initialize the table indexes

initIndexes();

// CH.8 Rebuild the table

drawTable();

我们调用scrollIndexes()代替向上、向下箭头重复按钮所做的数学运算。

// CH.8 Up arrow

case ContactListRecordUpRepeating:

scrollIndexes( -1 );

break;

// CH.8 Down arrow

case ContactListRecordDownRepeating:

scrollIndexes( 1 );

break;

同样的,我们通过调用scrollIndexes()代替用按键事件的游标运算。

// CH.8 Up arrow hard key

case pageUpChr:

scrollIndexes( -(TABLE_NUM_ROWS - 1) );

break;

// CH.8 Down arrow hard key

case pageDownChr:

scrollIndexes( TABLE_NUM_ROWS - 1 );

break;

为了处理滚动条,我们需要做两件事。首先,由于刷新基于滚动条的记录的时间变长了,那么我们通过响应sclExitEvent时间来代替响应sclRepeatEvent。第二,使用findIndex()在分类记录的子集中找到任意记录的游标值。我们必须仅仅处理基于当前分类的记录的滚动条。如果把表滚动到底的话,返回的数字是处在0和表顶端的记录之间。我们用findIndex()把这个数字转化为一个绝对的游标值。一旦知道表顶部记录的实际位置,我们调用initIndexes()把余下的值赋给tableIndex数组。

// CH.8 Respond to scrollbar events

case sclExitEvent:

{

//CH.9 Find the record in our category

cursor = findIndex( event->data.sclExit.newValue );

// CH.9 Initialize our index list

initIndexes();

// CH.8 Draw the table

drawTable();

}

break;

下面是处理分类弹出触发按纽的代码。和Contact Detail窗体相比,我们为触发器捕捉ctlSelectEvent事件,并把控件传递给CategorySelect(),为了防止采用弹出触发器的缺省操作,函数向Palm OS返回True。请注意我们在CategorySelect()中使用了True参数,它将该在分类列表框中添加All选项。一旦新的分类被选中,我们就需要重新做索引并重新绘制表。

// CH.9 Catch a tap on the category trigger

case ctlSelectEvent:

{

// CH.9 Palm OS will present the popup list for us.

CategorySelect( contactsDB, form,

ContactListCategoryPopupPopTrigger,

ContactListCategoryListList,

true, &listCat, catName, 1, 0 );

// CH.9 Cursor starts at zero

cursor = 0;

// CH.9 Initialize the indexes

initIndexes();

// CH.9 Draw the table

drawTable();

}

// CH.9 Don't let the OS generate other events from this

return( true );

drawTable()的变化包括修改了一行代码和截去了一整块代码。在initIndexes()和scrollIndex()中可以更方便地管理的重复按钮和滚动条,并且更容易知道下一信息是什么。同样的,我们也可以使用这两个函数处理表和记录的何时到达结尾的问题。

和第八章不同,记录到达最后一条后就使所有行变为不可用,检查talbeIndex数组,看看是否有其它记录与这一行相联系。

// CH.8 Initialize the table styles

for( count = 0; count < TABLE_NUM_ROWS; count++ )

{

// CH.9 If there is data

if( tableIndex[count] != 0xffff )

{

// CH.8 Show the row

TblSetRowUsable( table, count, true );

截去处理向上向下箭头按钮和滚动条的代码,而把它放在程序的底部。这些完成后,在调用TblDrawTable()后,drawTable()函数就会立即结束。

// CH.8 Draw the table

TblDrawTable( table );

// CH.8 We're done

return;

}

在drawCell()中,我们只修改一行代码,这一行在函数的顶部,决定到底使用哪一条记录。

// CH.9 Calculate our record

record = tableIndex[row];

initIndexes()函数

函数initIndexes()创建了在表中所要显示的记录。它创建了数组tableIndex,代表在当前分类中的记录。我们在函数的顶部定义了一些变量并获得当前窗体的指针:

static void initIndexes( void )

{

FormPtr form;

Int count;

UInt index = cursor;

ControlPtr downArrow;

ControlPtr upArrow;

UInt numRecsInCategory;

// CH.9 Get the current form

form = FrmGetActiveForm();

根据行建立循环,在当前分类中查找下一条记录并把它放入行中。如果我们到达了最后一条记录,就把数字0xffff(65,535)赋给数组表示没有所查记录。最后,把游标指向一个已知的有效记录,代替原来或许是在分类中根本就不存在的记录。因为如果分类中没有所查记录,就把游标设为了0xffff。由于表中所有的行已被关掉,这样做就可以了。

// CH.9 For each table row

for( count = 0; count < TABLE_NUM_ROWS; count++ )

{

// CH.9 Find the next matching record

if( DmSeekRecordInCategory( contactsDB, &index, 0,

dmSeekForward, listCat ) )

{

// CH.9 No more records. Fill the rest of the array with

// 0xffff

for( ; count < TABLE_NUM_ROWS; count++ )

tableIndex[count] = 0xffff;

break;

}

// CH.9 Put the index number in the array

tableIndex[count] = index;

index++;

}

// CH.9 Set the cursor to a known category record

cursor = tableIndex[0];

下面是箭头按扭和滚动条处理的代码。它与原来drawTable()函数相比,没有太大的改变。这次它使用DmNumRecordsInCategory()和DmPositionInCategory()来实现对去按钮和滚动条的操作。

// CH.8 Get pointers to the arrow buttons

upArrow = getObject( form, ContactListRecordUpRepeating );

downArrow = getObject( form, ContactListRecordDownRepeating );

// CH.8 Update the arrow buttons and scrollbars

numRecsInCategory = DmNumRecordsInCategory( contactsDB, listCat );

if( numRecsInCategory > TABLE_NUM_ROWS )

{

UInt position = DmPositionInCategory( contactsDB, cursor,

listCat );

// CH.8 Show the up arrow

if( position > 0 )

{

CtlSetLabel( upArrow, BLACK_UP_ARROW );

CtlSetEnabled( upArrow, true );

}

else

{

CtlSetLabel( upArrow, GRAY_UP_ARROW );

CtlSetEnabled( upArrow, false );

}

CtlShowControl( upArrow );

// CH.8 Show the down arrow

if( position >= numRecsInCategory - TABLE_NUM_ROWS )

{

CtlSetLabel( downArrow, GRAY_DOWN_ARROW );

CtlSetEnabled( downArrow, false );

}

else

{

CtlSetLabel( downArrow, BLACK_DOWN_ARROW );

CtlSetEnabled( downArrow, true );

}

CtlShowControl( downArrow );

// CH.9 Show the scrollbar

SclSetScrollBar( getObject( form,

ContactListScrollbarScrollBar ), position, 0,

numRecsInCategory - TABLE_NUM_ROWS, TABLE_NUM_ROWS );

}

else

{

// CH.8 Hide the arrows

CtlHideControl( upArrow );

CtlHideControl( downArrow );

// CH.8 Hide the scrollbar

SclSetScrollBar( getObject( form,

ContactListScrollbarScrollBar ), 0, 0, 0, 0 );

}

// CH.9 We're done

return;

}

scrollIndexes()函数

函数scrollIndexes()修改了tableIndex()数组,要知道我们或许只浏览一小部分内容,因此表中的大部分值仍是好用的。我们先定义一些变量并获得当前窗体指针。我们使用窗体指针来获得向上向下重复按钮的指针。

static void scrollIndexes( Int amount )

{

FormPtr form;

UInt count;

UInt index;

ControlPtr downArrow;

ControlPtr upArrow;

UInt numRecsInCategory;

// CH.9 Get the current form

form = FrmGetActiveForm();

// CH.9 Get pointers to the arrow buttons

upArrow = getObject( form, ContactListRecordUpRepeating );

downArrow = getObject( form, ContactListRecordDownRepeating );

余下的函数分成两部分,一个是向上浏览,一个是向下浏览。首先是向下浏览,我们一直循环直到达到我们的要求。

// CH.9 If we're scrolling down

if( amount > 0 )

{

// CH.9 While there is still an amount to scroll

while( amount-- )

{

在循环中,每次移动一条记录。如果我们一条记录也没有找到,使向下箭头呈灰晕。我们将列表中的索引加一,并且在程序的底部,得到最新的索引。

// CH.9 Get a new index after the last one

index = tableIndex[TABLE_NUM_ROWS - 1];

if( DmSeekRecordInCategory( contactsDB, &index, 1,

dmSeekForward, listCat ) )

{

// CH.9 No more records. We're done scrolling

CtlSetLabel( downArrow, GRAY_DOWN_ARROW );

CtlSetEnabled( downArrow, false );

return;

}

// CH.9 Move current indexes up one

for( count = 0; count < TABLE_NUM_ROWS - 1; count++ )

tableIndex[count] = tableIndex[count + 1];

// CH.9 Put the index number in the array

tableIndex[count] = index;

}

为了确定我们是否使向下箭头有效,需进一步地去查找下一条记录。如果的确还有一条记录,就应该仍然使向下箭头有效。同样,对于向上箭头也是这样。

// CH.9 Disable the down arrow if needed

if( DmSeekRecordInCategory( contactsDB, &index, 1,

dmSeekForward, listCat ) )

{

CtlSetLabel( downArrow, GRAY_DOWN_ARROW );

CtlSetEnabled( downArrow, false );

}

// CH.9 Enable the up arrow

CtlSetLabel( upArrow, BLACK_UP_ARROW );

CtlSetEnabled( upArrow, true );

}

向上箭头的代码也是一样,只是箭头方向不同而已。

else

// CH.9 If we're scrolling up

if( amount < 0 )

{

// CH.9 While there is still an amount to scroll

while( amount++ )

{

// CH.9 Get a new index before the first one

index = tableIndex[0];

if( DmSeekRecordInCategory( contactsDB, &index, 1,

dmSeekBackward, listCat ) )

{

// CH.9 No more records. We're done scrolling

CtlSetLabel( upArrow, GRAY_UP_ARROW );

CtlSetEnabled( upArrow, false );

return;

}

// CH.9 Move current indexes down one

for( count = TABLE_NUM_ROWS - 1; count > 0; count-- )

tableIndex[count] = tableIndex[count - 1];

// CH.9 Put the index number in the array

tableIndex[count] = index;

}

// CH.9 Disable the up arrow if needed

if( DmSeekRecordInCategory( contactsDB, &index, 1,

dmSeekBackward, listCat ) )

{

CtlSetLabel( upArrow, GRAY_UP_ARROW );

CtlSetEnabled( upArrow, false );

}

// CH.9 Enable the down arrow

CtlSetLabel( downArrow, BLACK_DOWN_ARROW );

CtlSetEnabled( downArrow, true );

}

在两个方向的浏览处理好后,我们把游标指向索引的顶部并更新滚动条。

// CH.9 Set the cursor

cursor = tableIndex[0];

// CH.9 Set the scrollbar

numRecsInCategory = DmNumRecordsInCategory( contactsDB, listCat );

SclSetScrollBar( getObject( form,

ContactListScrollbarScrollBar ), DmPositionInCategory(

contactsDB, cursor, listCat ), 0,

numRecords - TABLE_NUM_ROWS, TABLE_NUM_ROWS );

// CH.9 We're done

return;

}

findIndex()函数

函数findIndex()相当地简单,这应该归功于Palm OS的DmSeekRecordInCategory()函数。设置好参数后,这个函数与DmPositionInCategory()基本上是相对应的。

// CH.9 Find a particular index

static UInt findIndex( UInt scrollValue )

{

UInt index = 0;

// CH.9 Seek from zero to the scrollvalue

DmSeekRecordInCategory( contactsDB, &index, scrollValue,

dmSeekForward, listCat );

// We're done

return( index );

}

调试

主要应该记住的事就是检查分类管理器(Category Manager)函数的参数设置是否正确。这类函数大多不会返回错误信息,所以唯一能找出哪里有错的方法就是检查函数的输入和输出是否正确。相信这些函数在接到信息后可以工作,然后根据所发送东西查找错误。我经常犯的错误是没有为CategoryInitialize()正确的定义App Info String List资源,这会引起很多怪问题。如果你用String List代替App Info String List给CategoryInitialize(),它会可以不用做任何工作了。同样地,记住在这个列表中定义16个字符串,虽然它们中的大部分可能是空的。否则,你会得到许多“垃圾”分类名。如果在传递给CategoryInitialize()之前清除应用程序信息模块失败,请添加监视。

图9-4和9-5是Contacts应用程序的运行示意图。图9-4是Contact Detail窗体中打开分类列表的示意图。图9-5是Contact List窗体中的弹出触发按纽的外观示意图。

保密记录

保密记录的处理与分类类似。在每一条记录的属性中有一位可以定义它是否为保密。我们可以在Contact Detail窗体中加入一个复选框,来设置或清除这个位。然后,如果你从来不是使用数学方法来浏览记录,而是使用DmSeekRecordInCategory()或者类似的函数,保密记录就不会从里面出现。

你所需做的变动主要在Contacts窗体里面。所以必须在窗体加入选择框,并编写相应的代码设置记录中的保密位。然后使用在Contact List窗体实现浏览类似的函数来替换Contact Detail窗体中的所有的基于数学方法的浏览。否则,保密记录在使用Contact Detail窗体中的浏览按钮就会被看到。

查找

如果在你的应用程序中有很多文本,我高度推荐应支持查找(Find)。不管使用查找有多大意义,所有规范的Palm OS应用程序都应支持查找,。

在Contacts中,我们将整个记录合成为字符串形式,这样可以在数据库中更容易的查找日期,时间,或全名信息。

对Contacts.c的修改

为了支持Find,我们将加入一个简单的函数。在程序顶部声明其函数原型。

static UInt findIndex( UInt scrollValue );

这个函数没有使用全局变量,就可以在我们的数据库中进行查找。

运行(Lunch)代码

以前,我们只关心应用程序被启动后的位置,因为我们就从这里开始。而现在我们的应用程序在调用查找时也被触发。同样的,如果有人选中了一个已建立的记录,我们就让窗体跳到那个记录上。这个可以通过调用运行代码(lunch Code)来实现。

对这些新代码,我们需要查看PilotMain()传递过来更多的信息。因此,定义变量params。

深入

未用的参数

我们仍然忽略了最后的一个参数类型Word。如果你定义一个函数参数但不使用它,一些编译器将会给出一个警告信息。它使我们知道当不使用这些参数将能做什么。

DWord PilotMain( Word cmd, Ptr params, Word )

{

我们响应运行的第一种类型是系统的缺省查找:

switch( cmd )

{

// CH.2 Normal launch

case sysAppLaunchCmdNormalLaunch:

break;

如果我们调用系统的查找,那么另一个应用程序就会占用应用程序区域。所以,全局变量对我们来说是无用的。那就只能在堆栈中保存或分配内存,这两种方法都不要使用的太多。因此,最好在查找的函数调用中封装查找操作。

// CH.9 System find

case sysAppLaunchCmdFind:

find( params );

return( 0 );

如果用户选择了一个查找结果的条目,我们会收到运行标识。在这种情况下,应用程序一直被激活,这时可以使用全局变量。

// CH.9 Go to item from find

case sysAppLaunchCmdGoTo:

break;

如果我们不去处理查找的运行代码,返回就行了。

// CH.2 We don't handle what's being asked for

default:

return( 0 );

}

深入

除了我们处理的之外,还有一些运行代码,但是上面几个是最重要的。其它的运行代码和在The Palm OS SDK Reference中有详细的描述,包含在本书后面的CD中的CodeWarrior Lite版本中。

在PilotMain中,我们还需要考虑,当我们正在运行程序时产生查找和相关事件时,系统Find会重新使用以前的堆栈。如果它不能找到,就会在复制堆栈的末尾来调用系统查找。为了防止这一点,我们需要添加一个全局变量来确定当前的状态。在初始化查找时,我们无法访问全局变量,但是下面的Goto lunch语句中,就可以访问它了。

// CH.9 Goto variable

static Boolean upStack;

首先检查我们的数据库是不是已打开,如果数据库已打开,最好不要再打开。此时,把变量upStack设置为true,来标识程序已开始运行。

// CH.9 Open the database if it isn't already open

if( contactsDB == NULL )

{

contactsDB = DmOpenDatabaseByTypeCreator( 'ctct', 'PPGU',

dmModeReadWrite );

}

else

upStack = true;

当在查找结果中选中了一个具体的条目,将其切换到Contact Detail窗体中显示。为做到这一点,在窗体初始化的逻辑中可以再添加一种情况。如果我们已经得到运行的结果,就移到数据库中相应的记录上,并在Contact Detail窗体中将其显示。其中最重要的是要关掉所有已打开的窗体,以免在运行系统查找时应用程序是激活的。不然,当试图打开一个已经打开的窗体时,我们的应用程序就会死掉。同样的,如果我们刚开始运行程序,在设置游标和切换到Detail窗体后,需要返回原始的情况。这个代码写在两个FrmGotoForm()命令之间。

else

// CH.9 We are going to a particular record

if( cmd == sysAppLaunchCmdGoTo )

{

// CH.9 In case our app was running before the find

FrmCloseAllForms();

// CH.9 Point the cursor to the found item

cursor = ((GoToParamsPtr)params)->recordNum;

// CH.9 Go to the details page

FrmGotoForm( ContactDetailForm );

// CH.9 If we are running on top of ourselves,

// return to the original event loop

if( upStack )

{

upStack = false;

return( 0 );

}

}

find()函数

在函数的顶部,find()有它自己单独的变量列表:

static void find( Ptr params )

{

FindParamsPtr findParams = (FindParamsPtr)params; // CH.9 Params

DmOpenRef contactsDB; // CH.9 Our local database ptr

UInt numRecords; // CH.9 Number of records in the db

LocalID dbID; // CH.9 Local ID of the database

UInt cardNum; // CH.9 Card number

UInt cursor; // CH.9 The current record

VoidHand hrecord; // CH.9 Handle to the record

CharPtr precord; // CH.9 Pointer to the record

DateTimeType dateTime; // CH.9 Date and time in this record

Char textRecord[dateStringLength + 1 + // CH.9 We

timeStringLength + 1 + // build

DB_FIRST_NAME_SIZE + // text

DB_LAST_NAME_SIZE + // record here

DB_PHONE_NUMBER_SIZE];

Char lcText[dateStringLength + 1 + // CH.9 Copy

timeStringLength + 1 + // lower

DB_FIRST_NAME_SIZE + // case

DB_LAST_NAME_SIZE + // text here

DB_PHONE_NUMBER_SIZE];

Word offset; // CH.9 Offset of the match

RectangleType bounds; // CH.9 Bounding rect for text

SWord width; // CH.9 Width of the bounds rect

SWord len; // CH.9 Text length

Boolean noFit; // CH.9 Does it fit

为和其它的应用程序相区分,首先为Find添加一个标题。

// CH.9 Draw a title for our find items

// CH.9 If there's no more room, return

if( (FindDrawHeader( findParams, "Contacts" )) == true )

return;

接着我们打开数据库,获取一些稍后会用到有关查找的信息。由于在查找操作中我们不想改变数据库中的信息,所以最好以只读的模式打开数据库。

// CH.9 Open the database for reading

if( (contactsDB = DmOpenDatabaseByTypeCreator( 'ctct', 'PPGU',

dmModeReadOnly )) == NULL )

return;

我们通过循环查找匹配的记录。注意到我们不一定从记录0开始。开始处依赖于Find Manager是第一次查找还是在查找结果中再次查找。查找完成后,一屏只能显示一次查找的信息。

深入

如果支持了记录的保密,在查找时,就要保证保密记录不会被找到。在这个情况下,我们就不能一个个的查找记录,而应使用一个叫DmSeekRecordInCategory()的函数。这个函数会自动地把保密记录排除在外。

因为记录中的信息类型是多种多样的,这就需要把数据记录类型转化为与查找相匹配的类型。举个例子,我们是把姓和名字段合为一个字符串中去查找,如果用户输入John Smith作为查找字符串,就会在数据库中找出John Smith。如果我们没有把记录结合在一个大的字符串,而是一个记录一个记录地查找,即使数据库中有John Smith这个记录,我们也不会找到。因此就需要把日期,时间等等转换成文本,并把它们放在一个很大的字符串中。由于内嵌的应用程序是基于字段的,所以它不支持直接查找。

// CH.9 For each record

for( cursor = findParams->recordNum; cursor < numRecords; cursor++ )

{

// CH.9 Get the record

hrecord = DmQueryRecord( contactsDB, cursor );

precord = MemHandleLock( hrecord );

// CH.9 Get the date and time

MemMove( &dateTime, precord + DB_DATE_TIME_START,

sizeof( dateTime ) );

// CH.9 Start over

*textRecord = '\0';

// CH.9 Add the date string if any

if( dateTime.year != NO_DATE )

{

DateToAscii( dateTime.month, dateTime.day,

dateTime.year,

(DateFormatType)PrefGetPreference(

prefDateFormat ), textRecord );

StrCat( textRecord, " " );

}

// CH.9 Add the time string if any

if( dateTime.hour != NO_TIME )

{

TimeToAscii( dateTime.hour, dateTime.minute,

(TimeFormatType)PrefGetPreference(

prefTimeFormat ), textRecord +

StrLen( textRecord ) );

StrCat( textRecord, " " );

}

// CH.9 Append the first name

StrCat( textRecord, precord + DB_FIRST_NAME_START );

StrCat( textRecord, " " );

// CH.9 Append the last name

StrCat( textRecord, precord + DB_LAST_NAME_START );

StrCat( textRecord, " " );

// CH.9 Append the phone number

StrCat( textRecord, precord + DB_PHONE_NUMBER_START );

// CH.9 Unlock the record

MemHandleUnlock( hrecord );

深入

取消查找

这个例子中,在查找过程中不能被取消,但添加这个功能也十分简单。可以调用函数EvtSysEventAvail()来实现,将其放在for()循环顶部。

在Find窗体中,我们需要指定字符串的格式,这里使用小写。然后使用函数FindStrInStr()来查找匹配的记录。从它的参数中可以看到,strToFind并不是一个小写的字符串。如果你想建立自己的函数来查找,也需要规定自己的字符串格式。

// CH.9 Copy and convert to lower case

StrToLower( lcText, textRecord );

// CH.9 If there's no match, move on

if( (FindStrInStr( lcText, findParams->strToFind,

&offset )) == false )

continue;

如果找到了一条匹配记录并且屏幕上有空间,那么就在屏幕上显示,然后继续查找下一条记录。

// CH.9 Send it to find

// CH.9 If there's no more room, return

if( (FindSaveMatch( findParams, cursor, offset, 0,

NULL, cardNum, dbID )) == true )

break;

// CH.9 Get the rectangle for our line of text

FindGetLineBounds( findParams, &bounds );

// CH.9 Truncate the string if necessary

width = bounds.extent.x;

len = StrLen( textRecord );

noFit = false;

FntCharsInWidth( textRecord, &width, &len, &noFit );

// CH.9 Draw the text

WinEraseRectangle( &bounds, 0 );

WinDrawChars( textRecord, len, bounds.topLeft.x,

bounds.topLeft.y );

// We used a line in the find dialog

(findParams->lineNumber)++;

}

// CH.9 Close the database

DmCloseDatabase( contactsDB );

// CH.9 We're done

return;

}

当查找完所有的记录后,关闭数据库。

调试

我主要调试了查找函数中我所传递的参数,并看看能不能将其修改。例如,不要相信strToFind就是你要找的字符串。另外,注意Palm OS不允许将lineNumber值增加。图9-6所示是查找函数运行后的结果。

下一步做什么

这是介绍Palm OS基础知识的最后一章,在下面的章节中将介绍一些有关软件设计的内容。第十章介绍了如何设计Palm OS的用户操作界面,第十一章介绍了建立Palm OS应用程序所使用的一些工具,第十二章介绍了如何组织和修改代码来加强它的可重用性。

程序列表

下面是最新版本的Contacts.c,包括了我们在这一章中所有的修改。

// CH.2 The super-include for the Palm OS

#include <Pilot.h>

// CH.5 Added for the call to GrfSetState()

#include <Graffiti.h>

// CH.3 Our resource file

#include "Contacts_res.h"

// CH.4 Prototypes for our event handler functions

static Boolean contactDetailHandleEvent( EventPtr event );

static Boolean aboutHandleEvent( EventPtr event );

static Boolean enterTimeHandleEvent( EventPtr event );

static Boolean contactListHandleEvent( EventPtr event );

static Boolean menuEventHandler( EventPtr event );

// CH.4 Constants for ROM revision

#define ROM_VERSION_2 0x02003000

#define ROM_VERSION_MIN ROM_VERSION_2

// CH.5 Prototypes for utility functions

static void newRecord( void );

static VoidPtr getObject( FormPtr, Word );

static void setFields( void );

static void getFields( void );

static void setText( FieldPtr, CharPtr );

static void getText( FieldPtr, VoidPtr, Word );

static void setDateTrigger( void );

static void setTimeTrigger( void );

static void setTimeControls( void );

static Int sortFunc( CharPtr, CharPtr, Int );

static void drawTable( void );

static void drawCell( VoidPtr table, Word row, Word column,

RectanglePtr bounds );

static void initIndexes( void );

static void scrollIndexes( Int amount );

static UInt findIndex( UInt scrollValue );

static void find( Ptr params );

// CH.5 Our open database reference

static DmOpenRef contactsDB;

static ULong numRecords;

static UInt cursor;

static Boolean isDirty;

static VoidHand hrecord;

// CH.5 Constants that define the database record

#define DB_ID_START 0

#define DB_ID_SIZE (sizeof( ULong ))

#define DB_DATE_TIME_START (DB_ID_START +\

DB_ID_SIZE)

#define DB_DATE_TIME_SIZE (sizeof( DateTimeType ))

#define DB_FIRST_NAME_START (DB_DATE_TIME_START +\

DB_DATE_TIME_SIZE)

#define DB_FIRST_NAME_SIZE 16

#define DB_LAST_NAME_START (DB_FIRST_NAME_START +\

DB_FIRST_NAME_SIZE)

#define DB_LAST_NAME_SIZE 16

#define DB_PHONE_NUMBER_START (DB_LAST_NAME_START +\

DB_LAST_NAME_SIZE)

#define DB_PHONE_NUMBER_SIZE 16

#define DB_RECORD_SIZE (DB_PHONE_NUMBER_START +\

DB_PHONE_NUMBER_SIZE)

// CH.6 Storage for the record's date and time in expanded form

static DateTimeType dateTime;

static Word timeSelect;

#define NO_DATE 0

#define NO_TIME 0x7fff

// CH.7 The error exit macro

#define errorExit(alert) { ErrThrow( alert ); }

// CH.7 The sort order variable and constants

static Int sortBy;

// CH.7 NOTE: These items match the popup list entries!

#define SORTBY_DATE_TIME 0

#define SORTBY_FIRST_NAME 1

#define SORTBY_LAST_NAME 2

// CH.8 Table constants

#define TABLE_NUM_COLUMNS 3

#define TABLE_NUM_ROWS 11

#define TABLE_COLUMN_DATE 0

#define TABLE_COLUMN_TIME 1

#define TABLE_COLUMN_NAME 2

#define BLACK_UP_ARROW "\x01"

#define BLACK_DOWN_ARROW "\x02"

#define GRAY_UP_ARROW "\x03"

#define GRAY_DOWN_ARROW "\x04"

// CH.9 Category variables

static Word listCat = dmAllCategories; // CH.9 The current category ID

static Word detailCat; // CH.9 Category ID for details

static UInt tableIndex[TABLE_NUM_ROWS]; // CH.9 Record indexes for rows

// CH.9 Goto variable

static Boolean upStack;

// CH.2 The main entry point

DWord PilotMain( Word cmd, Ptr params, Word )

{

DWord romVersion; // CH.4 ROM version

LocalID dbID; // CH.9 Local ID of the database

UInt cardNum; // CH.9 Card number

LocalID appInfoID; // CH.9 Local ID of the app info block

VoidHand hAppInfo; // CH.9 Handle to the app info block

AppInfoPtr pAppInfo; // CH.9 Points to the app info block

FormPtr form; // CH.2 A pointer to our form structure

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.9 Respond to launches

switch( cmd )

{

// CH.2 Normal launch

case sysAppLaunchCmdNormalLaunch:

break;

// CH.9 System find

case sysAppLaunchCmdFind:

find( params );

return( 0 );

// CH.9 Go to item from find

case sysAppLaunchCmdGoTo:

break;

// CH.2 We don't handle what's being asked for

default:

return( 0 );

}

// CH.5 Create a new database in case there isn't one

if( ((error = DmCreateDatabase( 0, "ContactsDB-PPGU", 'PPGU', 'ctct',

false )) != dmErrAlreadyExists) && (error != 0) )

{

// CH.5 Handle db creation error

FrmAlert( DBCreationErrorAlert );

return( 0 );

}

// CH.9 Open the database if it isn't already open

if( contactsDB == NULL )

{

contactsDB = DmOpenDatabaseByTypeCreator( 'ctct', 'PPGU',

dmModeReadWrite );

}

else

upStack = true;

// CH.9 Get the ID and card number

DmOpenDatabaseInfo( contactsDB, &dbID, NULL, NULL, &cardNum, NULL);

// CH.9 Get the app info pointer if any

DmDatabaseInfo( cardNum, dbID, NULL, NULL, NULL, NULL, NULL, NULL,

NULL, &appInfoID, NULL, NULL, NULL );

// CH.5 Get the number of records in the database

numRecords = DmNumRecords( contactsDB );

// CH.5 Initialize the record number

cursor = 0;

// CH.7 Choose our starting page

// CH.5 If there are no records, create one

if( numRecords == 0 )

{

newRecord();

FrmGotoForm( ContactDetailForm );

}

else

// CH.9 We are going to a particular record

if( cmd == sysAppLaunchCmdGoTo )

{

// CH.9 In case our app was running before the find

FrmCloseAllForms();

// CH.9 Point the cursor to the found item

cursor = ((GoToParamsPtr)params)->recordNum;

// CH.9 Go to the details page

FrmGotoForm( ContactDetailForm );

// CH.9 If we are running on top of ourselves,

// return to the original event loop

if( upStack )

{

upStack = false;

return( 0 );

}

}

else

// CH.7 Display the list

FrmGotoForm( ContactListForm );

// CH.7 Begin the try block

ErrTry {

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

// CH.6 Enter Time form

case EnterTimeForm:

form = FrmInitForm( EnterTimeForm );

FrmSetEventHandler( form, enterTimeHandleEvent );

break;

// CH.7 Contact List form

case ContactListForm:

form = FrmInitForm( ContactListForm );

FrmSetEventHandler( form, contactListHandleEvent );

break;

}

FrmSetActiveForm( form );

}

// CH.2 Handle form events

FrmDispatchEvent( &event );

// CH.2 If it's a stop event, exit

} while( event.eType != appStopEvent );

// CH.7 End the try block and do the catch block

}

ErrCatch( errorAlert )

{

// CH.7 Display the appropriate alert

FrmAlert( errorAlert );

} ErrEndCatch

// CH.5 Close all open forms

FrmCloseAllForms();

// CH.5 Close the database

DmCloseDatabase( contactsDB );

// CH.2 We're done

return( 0 );

}

// CH.4 Our Contact Detail form handler function

static Boolean contactDetailHandleEvent( EventPtr event )

{

FormPtr form; // CH.3 A pointer to our form structure

VoidPtr precord; // CH.6 Points to a database record

Char catName[dmCategoryLength]; // CH.9 Category name

// CH.3 Get our form pointer

form = FrmGetActiveForm();

// CH.4 Parse events

switch( event->eType )

{

// CH.4 Form open event

case frmOpenEvent:

{

// CH.2 Draw the form

FrmDrawForm( form );

// CH.5 Draw the database fields

setFields();

}

break;

// CH.5 Form close event

case frmCloseEvent:

{

// CH.5 Store away any modified fields

getFields();

}

break;

// CH.5 Parse the button events

case ctlSelectEvent:

{

// CH.5 Store any field changes

getFields();

switch( event->data.ctlSelect.controlID )

{

// CH.5 First button

case ContactDetailFirstButton:

{

// CH.5 Set the cursor to the first record

if( cursor > 0 )

cursor = 0;

}

break;

// CH.5 Previous button

case ContactDetailPrevButton:

{

// CH.5 Move the cursor back one record

if( cursor > 0 )

cursor--;

}

break;

// CH.5 Next button

case ContactDetailNextButton:

{

// CH.5 Move the cursor up one record

if( cursor < (numRecords - 1) )

cursor++;

}

break;

// CH.5 Last button

case ContactDetailLastButton:

{

// CH.5 Move the cursor to the last record

if( cursor < (numRecords - 1) )

cursor = numRecords - 1;

}

break;

// CH.5 Delete button

case ContactDetailDeleteButton:

{

// CH.5 Remove the record from the database

DmRemoveRecord( contactsDB, cursor );

// CH.5 Decrease the number of records

numRecords--;

// CH.5 Place the cursor at the first record

cursor = 0;

// CH.5 If there are no records left, create one

if( numRecords == 0 )

newRecord();

}

break;

// CH.5 New button

case ContactDetailNewButton:

{

// CH.5 Create a new record

newRecord();

}

break;

// CH.7 Done button

case ContactDetailDoneButton:

{

// CH.7 Load the contact list

FrmGotoForm( ContactListForm );

}

break;

// CH.6 Date selector trigger

case ContactDetailDateSelTrigger:

{

// CH.6 Initialize the date if necessary

if( dateTime.year == NO_DATE )

{

DateTimeType currentDate;

// CH.6 Get the current date

TimSecondsToDateTime( TimGetSeconds(),

¤tDate );

// CH.6 Copy it

dateTime.year = currentDate.year;

dateTime.month = currentDate.month;

dateTime.day = currentDate.day;

}

// CH.6 Pop up the system date selection form

SelectDay( selectDayByDay, &(dateTime.month),

&(dateTime.day), &(dateTime.year),

"Enter Date" );

// CH.6 Get the record

hrecord = DmQueryRecord( contactsDB, cursor );

// CH.6 Lock it down

precord = MemHandleLock( hrecord );

// CH.6 Write the date time field

DmWrite( precord, DB_DATE_TIME_START, &dateTime,

sizeof( DateTimeType ) );

// CH.6 Unlock the record

MemHandleUnlock( hrecord );

// CH.6 Mark the record dirty

isDirty = true;

}

break;

// CH.6 Time selector trigger

case ContactDetailTimeSelTrigger:

{

// CH.6 Pop up our selection form

FrmPopupForm( EnterTimeForm );

}

break;

// CH.5 Sync the current record to the fields

setFields();

}

break;

// CH.5 Respond to field tap

case fldEnterEvent:

isDirty = true;

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.6 Our Enter Time form event handler function

static Boolean enterTimeHandleEvent( EventPtr event )

{

FormPtr form; // CH.6 A form structure pointer

static DateTimeType oldTime; // CH.6 The original time

// CH.6 Get our form pointer

form = FrmGetActiveForm();

// CH.6 Switch on the event

switch( event->eType )

{

// CH.6 Initialize the form

case frmOpenEvent:

{

// CH.6 Store the time value

oldTime = dateTime;

// CH.6 Draw it

FrmDrawForm( form );

// CH.6 Set the time controls

setTimeControls();

}

break;

// CH.6 If a button was repeated

case ctlRepeatEvent:

// CH.6 If a button was pushed

case ctlSelectEvent:

{

Word buttonID; // CH.6 The ID of the button

// CH.6 Set the ID

buttonID = event->data.ctlSelect.controlID;

// CH.6 Switch on button ID

switch( buttonID )

{

// CH.6 Hours button

case EnterTimeHoursPushButton:

// CH.6 Minute Tens button

case EnterTimeMinuteTensPushButton:

// CH.6 Minute Ones button

case EnterTimeMinuteOnesPushButton:

{

// CH.6 If no time was set

if( dateTime.hour == NO_TIME )

{

// CH.6 Set the time to 12 PM

dateTime.hour = 12;

dateTime.minute = 0;

// CH.6 Set the controls

setTimeControls();

}

// CH.6 Clear the old selection if any

if( timeSelect )

CtlSetValue( getObject( form, timeSelect ),

false );

// CH.6 Set the new selection

CtlSetValue( getObject( form, buttonID ), true );

timeSelect = buttonID;

}

break;

// CH.6 Up button

case EnterTimeTimeUpRepeating:

{

// CH.6 If there's no time, do nothing

if( dateTime.hour == NO_TIME )

break;

// CH.6 Based on what push button is selected

switch( timeSelect )

{

// CH.6 Increase hours

case EnterTimeHoursPushButton:

{

// CH.6 Increment hours

dateTime.hour++;

// CH.6 If it was 11 AM, make it 12 AM

if( dateTime.hour == 12 )

dateTime.hour = 0;

// CH.6 If it was 11 PM, make it 12 PM

if( dateTime.hour == 24 )

dateTime.hour = 12;

}

break;

// CH.6 Increase tens of minutes

case EnterTimeMinuteTensPushButton:

{

// CH.6 Increment minutes

dateTime.minute += 10;

// CH.6 If it was 5X, roll over

if( dateTime.minute > 59 )

dateTime.minute -= 60;

}

break;

// CH.6 Increase minutes

case EnterTimeMinuteOnesPushButton:

{

// CH.6 Increment minutes

dateTime.minute++;

// CH.6 If it is zero, subtract ten

if( (dateTime.minute % 10) == 0 )

dateTime.minute -= 10;

}

break;

}

// Revise the controls

setTimeControls();

}

break;

// CH.6 Down button

case EnterTimeTimeDownRepeating:

{

// CH.6 If there's no time, do nothing

if( dateTime.hour == NO_TIME )

break;

// CH.6 Based on what push button is selected

switch( timeSelect )

{

// CH.6 Decrease hours

case EnterTimeHoursPushButton:

{

// CH.6 Decrement hours

dateTime.hour--;

// CH.6 If it was 12 AM, make it 11 AM

if( dateTime.hour == -1 )

dateTime.hour = 11;

// CH.6 If it was 12 PM, make it 11 PM

if( dateTime.hour == 11 )

dateTime.hour = 23;

}

break;

// CH.6 Decrease tens of minutes

case EnterTimeMinuteTensPushButton:

{

// CH.6 Decrement minutes

dateTime.minute -= 10;

// CH.6 If it was 0X, roll over

if( dateTime.minute < 0 )

dateTime.minute += 60;

}

break;

// CH.6 Decrease minutes

case EnterTimeMinuteOnesPushButton:

{

// CH.6 Decrement minutes

dateTime.minute--;

// CH.6 If it is 9, add ten

if( (dateTime.minute % 10) == 9 )

dateTime.minute += 10;

// CH.6 If less than zero, make it 9

if( dateTime.minute < 0 )

dateTime.minute = 9;

}

break;

}

// CH.6 Revise the controls

setTimeControls();

}

break;

// CH.6 AM button

case EnterTimeAMPushButton:

{

// CH.6 If no time was set

if( dateTime.hour == NO_TIME )

{

// CH.6 Set the time to 12 AM

dateTime.hour = 0;

dateTime.minute = 0;

// CH.6 Set the controls

setTimeControls();

}

// CH.6 If it is PM

if( dateTime.hour > 11 )

{

// CH.6 Change to AM

dateTime.hour -= 12;

// CH.6 Set the controls

setTimeControls();

}

}

break;

// CH.6 PM button

case EnterTimePMPushButton:

{

// CH.6 If no time was set

if( dateTime.hour == NO_TIME )

{

// CH.6 Set the time to 12 PM

dateTime.hour = 12;

dateTime.minute = 0;

// CH.6 Set the controls

setTimeControls();

}

// CH.6 If it is AM

if( dateTime.hour < 12 )

{

// CH.6 Change to PM

dateTime.hour += 12;

// CH.6 Set the controls

setTimeControls();

}

}

break;

// CH.6 No Time checkbox

case EnterTimeNoTimeCheckbox:

{

// CH.6 If we are unchecking the box

if( dateTime.hour == NO_TIME )

{

// CH.6 Set the time to 12 PM

dateTime.hour = 12;

dateTime.minute = 0;

// CH.6 Set the controls

setTimeControls();

// CH.6 Set the new selection

timeSelect = EnterTimeHoursPushButton;

CtlSetValue( getObject( form, timeSelect ),

true );

}

else

// CH.6 If we are checking the box

dateTime.hour = NO_TIME;

// CH.6 Set the controls

setTimeControls();

}

break;

// CH.6 Cancel button

case EnterTimeCancelButton:

{

// CH.6 Restore time

dateTime = oldTime;

// CH.6 Return to calling form

FrmReturnToForm( 0 );

}

// CH.6 Always return true

return( true );

// CH.6 OK button

case EnterTimeOKButton:

{

VoidPtr precord; // CH.6 Points to the record

// CH.6 Lock it down

precord = MemHandleLock( hrecord );

// CH.6 Write the date time field

DmWrite( precord, DB_DATE_TIME_START, &dateTime,

sizeof( DateTimeType ) );

// CH.6 Unlock the record

MemHandleUnlock( hrecord );

// CH.6 Mark the record dirty

isDirty = true;

// CH.6 Return to the Contact Details form

FrmReturnToForm( 0 );

// CH.6 Update the field

setTimeTrigger();

}

// CH.6 Always return true

return( true );

}

}

break;

}

// CH.6 We're done

return( false );

}

// CH.7 Our Contact List form event handler function

static Boolean contactListHandleEvent( EventPtr event )

{

FormPtr form; // CH.7 A form structure pointer

Char catName[dmCategoryLength]; // CH.9 Category name

// CH.7 Get our form pointer

form = FrmGetActiveForm();

// CH.7 Parse events

switch( event->eType )

{

// CH.7 Form open event

case frmOpenEvent:

{

// CH.7 Draw the form

FrmDrawForm( form );

// CH.9 Set the category popup trigger label

CategoryGetName( contactsDB, listCat, catName );

CategorySetTriggerLabel( getObject( form,

ContactListCategoryPopupPopTrigger ),

catName );

// CH.8 The cursor starts at the beginning

cursor = 0;

// CH.9 Initialize the table indexes

initIndexes();

// CH.8 Populate and draw the table

drawTable();

}

break;

// CH.7 Respond to a list selection

case tblSelectEvent:

{

// CH.7 Set the database cursor to the selected contact

cursor = tableIndex[event->data.tblSelect.row];

// CH.7 Go to contact details

FrmGotoForm( ContactDetailForm );

}

break;

// CH.7 Respond to a menu event

case menuEvent:

return( menuEventHandler( event ) );

// CH.7 Respond to the popup trigger

case popSelectEvent:

{

// CH.7 If there is no change, we're done

if( sortBy == event->data.popSelect.selection )

return( true );

// CH.7 Modify sort order variable

sortBy = event->data.popSelect.selection;

// CH.7 Sort the contact database by the new criteria

DmQuickSort( contactsDB, (DmComparF*)sortFunc, sortBy );

// CH.8 Cursor starts at zero

cursor = 0;

// CH.9 Initialize the table indexes

initIndexes();

// CH.8 Rebuild the table

drawTable();

}

break;

// CH.8 Respond to arrows

case ctlRepeatEvent:

{

switch( event->data.ctlRepeat.controlID )

{

// CH.8 Up arrow

case ContactListRecordUpRepeating:

scrollIndexes( -1 );

break;

// CH.8 Down arrow

case ContactListRecordDownRepeating:

scrollIndexes( 1 );

break;

}

// CH.8 Now refresh the table

drawTable();

}

break;

// CH.8 Respond to up and down arrow hard keys

case keyDownEvent:

{

switch( event->data.keyDown.chr )

{

// CH.8 Up arrow hard key

case pageUpChr:

scrollIndexes( -(TABLE_NUM_ROWS - 1) );

break;

// CH.8 Down arrow hard key

case pageDownChr:

scrollIndexes( TABLE_NUM_ROWS - 1 );

break;

}

// CH.8 Now refresh the table

drawTable();

}

break;

// CH.8 Respond to scrollbar events

case sclExitEvent:

{

//CH.9 Find the record in our category

cursor = findIndex( event->data.sclExit.newValue );

// CH.9 Initialize our index list

initIndexes();

// CH.8 Draw the table

drawTable();

}

break;

// CH.9 Catch a tap on the category trigger

case ctlSelectEvent:

{

// CH.9 Palm OS will present the popup list for us.

CategorySelect( contactsDB, form,

ContactListCategoryPopupPopTrigger,

ContactListCategoryListList,

true, &listCat, catName, 1, 0 );

// CH.9 Cursor starts at zero

cursor = 0;

// CH.9 Initialize the indexes

initIndexes();

// CH.9 Draw the table

drawTable();

}

// CH.9 Don't let the OS generate other events from this

return( true );

} // CH.7 End of the event switch statement

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

}

// CH.5 This function creates and initializes a new record

static void newRecord( void )

{

VoidPtr precord; // CH.5 Pointer to the record

UInt recAttrs; // CH.9 The record's attributes

// CH.7 Create the database record and get a handle to it

if( (hrecord = DmNewRecord( contactsDB, &cursor,

DB_RECORD_SIZE )) == NULL )

errorExit( MemoryErrorAlert );

// CH.5 Lock down the record to modify it

precord = MemHandleLock( hrecord );

// CH.5 Clear the record

DmSet( precord, 0, DB_RECORD_SIZE, 0 );

// CH.6 Initialize the date and time

MemSet( &dateTime, sizeof( dateTime ), 0 );

dateTime.year = NO_DATE;

dateTime.hour = NO_TIME;

DmWrite( precord, DB_DATE_TIME_START, &dateTime,

sizeof( DateTimeType ) );

// CH.5 Unlock the record

MemHandleUnlock( hrecord );

// CH.5 Clear the busy bit and set the dirty bit

DmReleaseRecord( contactsDB, cursor, true );

// CH.5 Increment the total record count

numRecords++;

// CH.5 Set the dirty bit

isDirty = true;

// Ch.9 Get the record attribute bits

DmRecordInfo( contactsDB, cursor, &recAttrs, NULL, NULL );

// CH.9 Clear the category bits

recAttrs &= ~dmRecAttrCategoryMask;

// CH.9 Set the category to the appropriate category

if( listCat == dmAllCategories )

recAttrs |= dmUnfiledCategory;

else

recAttrs |= listCat;

// CH.9 Set the record attributes

DmSetRecordInfo( contactsDB, cursor, &recAttrs,

NULL );

// CH.5 We're done

return;

}

// CH.5 A time saver: Gets object pointers based on their ID

static VoidPtr getObject( FormPtr form, Word objectID )

{

Word index; // CH.5 The object index

// CH.5 Get the index

index = FrmGetObjectIndex( form, objectID );

// CH.5 Return the pointer

return( FrmGetObjectPtr( form, index ) );

}

// CH.5 Gets the current database record and displays it

// in the detail fields

static void setFields( void )

{

FormPtr form; // CH.5 The contact detail form

CharPtr precord; // CH.6 A record pointer

Word index; // CH.5 The object index

UInt recAttrs; // CH.9 The record attribute bits

Char catName[dmCategoryLength]; // CH.6 The category name

// CH.5 Get the contact detail form pointer

form = FrmGetActiveForm();

// CH.5 Get the current record

hrecord = DmQueryRecord( contactsDB, cursor );

// CH.6 Initialize the date and time variable

precord = MemHandleLock( hrecord );

MemMove( &dateTime, precord + DB_DATE_TIME_START,

sizeof( dateTime ) );

// CH.6 Initialize the date control

setDateTrigger();

// CH.6 Initialize the time control

setTimeTrigger();

// CH.5 Set the text for the First Name field

setText( getObject( form, ContactDetailFirstNameField ),

precord + DB_FIRST_NAME_START );

// CH.5 Set the text for the Last Name field

setText( getObject( form, ContactDetailLastNameField ),

precord + DB_LAST_NAME_START );

// CH.5 Set the text for the Phone Number field

setText( getObject( form, ContactDetailPhoneNumberField ),

precord + DB_PHONE_NUMBER_START );

MemHandleUnlock( hrecord );

// CH.5 If the record is already dirty, it's new, so set focus

if( isDirty )

{

// CH.3 Get the index of our field

index = FrmGetObjectIndex( form, ContactDetailFirstNameField );

// CH.3 Set the focus to the First Name field

FrmSetFocus( form, index );

// CH.5 Set upper shift on

GrfSetState( false, false, true );

}

// CH.9 Get the record attributes

DmRecordInfo( contactsDB, cursor, &recAttrs, NULL, NULL );

// CH.9 Get the category

detailCat = recAttrs & dmRecAttrCategoryMask;

// CH.9 Set the category popup trigger label

CategoryGetName( contactsDB, detailCat, catName );

CategorySetTriggerLabel( getObject( form,

ContactDetailCategoryPopupPopTrigger ), catName );

// CH.5 We're done

return;

}

// CH.5 Puts any field changes in the record

void getFields( void )

{

FormPtr form; // CH.5 The contact detail form

// CH.5 Get the contact detail form pointer

form = FrmGetActiveForm();

// CH.5 Turn off focus

FrmSetFocus( form, -1 );

// CH.5 If the record has been modified

if( isDirty )

{

CharPtr precord; // CH.5 Points to the DB record

// CH.7 Detach the record from the database

DmDetachRecord( contactsDB, cursor, &hrecord );

// CH.5 Lock the record

precord = MemHandleLock( hrecord );

// CH.5 Get the text for the First Name field

getText( getObject( form, ContactDetailFirstNameField ),

precord, DB_FIRST_NAME_START );

// CH.5 Get the text for the Last Name field

getText( getObject( form, ContactDetailLastNameField ),

precord, DB_LAST_NAME_START );

// CH.5 Get the text for the Phone Number field

getText( getObject( form, ContactDetailPhoneNumberField ),

precord, DB_PHONE_NUMBER_START );

// CH.7 Find the proper position

cursor = DmFindSortPosition( contactsDB, pre

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有