PalmOS开发教程介绍和分析
第八章 表和滚动条 |
在这一章中,我们将讨论Palm OS的两个很重要的用户界面元素:表和滚动条。表能够显示或编辑较大的数据量。在嵌入式应用程序中都它使用的很广泛。滚动条的功能很出色,但由于滚动条不支持1.0版本的Palm OS系统,所以只有在不想支持较早的Pilot 1000和5000时,才可以使用滚动条。我们将同时添加滚动按钮(它可以被所有的Palm设备使用)和滚动条(请不要在一个真正的应用程序中使用!可能会系统崩溃的喔!),然而这些还不够,我们还将论及如何 支持PAGE UP和PAGE DOWN键。 保存工程 现在你已经有了这个习惯了吧,步骤如下: 1.运行Windows浏览器; 2.找到工程存放的文件夹; 3.选中文件夹,按CTRL+C来复制文件夹; 4.选择一个文件夹用来保存副本; 5.按CTRL+V把项目副本粘贴到备份文件夹中; 6.把项目名重命名为你容易记的名字,我把它命名为Contacts CH.7。 删除旧的资源 既然已用表代替了Contact List窗体中的列表框,那么我们需要把列表框删除。 1.运行Metrowerks 构造器; 2.打开资源文件Contacts.rsrc。它位于项目文件夹中的Src文件夹中; 3.双击打开Contact List窗体; 4.点击资源列表中名为List的资源,按DELETE键来删除; 5.Contact List窗体现在看起来如图8-1所示。 删除旧代码 既然已经将列表框删除了,函数buildList()和deleteList()也就不再需要了。找到并删除这两个函数及其有关的内容。你可以将光标放在文件的开始处,在菜单栏中选中Search | Find,输入buildList。在删除了所有与buildList有关内容后,你可以再对deleteList做相似的操作。 另外,删除在Contact List事件处理函数中响应1stSelectEvent事件的代码。这些代码是: // CH.7 Respond to a list selection case lstSelectEvent: { // CH.7 Set the database cursor to the selected contact cursor = event->data.lstSelect.selection; // CH.7 Go to contact details FrmGotoForm( ContactDetailForm ); } break; 表 相对其它的UI(用户界面)元素来说,表是容器。表中的UI元素和表外的Palm OS系统中 UI元素不太一样。在表中的每一个单元(Cell)(行+列)可以有不同的类型,也就是说,它可以支持不同类型UI元素,在接下来的部分中将描述这些类型。 在捕获事件和将单元中的数据传递给UI元素时,表也有所不同。一些普通的函数,对表却可以执行一些特殊的函数。表看起来很像静态的Palm OS,只是要保证将每个可用UI元素的类型区分开来。 不幸地是,许多表中可使用的UI元素,在我的工程中却用不到。本章中,我只是将如何建立了自己的定制单元的技巧做一些论述。 条目类型 表中的每一个单元都有自己的类型。例如,单元可以是一个字编辑框资源或一个复选框资源。表8-1中是这些类型及其操作的纲要。 类型 使用 CheckboxTableItem 除了没有文本和选项框关联外,这种类型的单元操作和一般的选项框一样,可以通过调用TblSetItemInt()将其选中或清除,0表示没有选中,1表示选中。 CustomTableItem 这是一个非常有用的单元类型。你必须为此类型每一列定义一个定制函数,在本章以后的部分中,我们更多讲述了如何处理这个类型的内容。这种类型是可编辑的。 DateTableItem 在此单元中显示的日期已定义为DateType格式,可用TblSetItemPtr()函数将指向DateType的游标传给表。这种格式的缺点是在过去的任何日期后都会写出一个感叹号。有时这或许是件好事,但有时它会强制你使用定制日期显示。这种类型是不可编辑的。 LabelTableItem 它用来显示一个标签。使用TblSetItemPtr()将字符串传递给表。此格式的缺点就是表经常在所传递的字符串后面加上一个冒号(:),并且文本通常是右对齐。这就是为什么在这一章中,我们要使用定制类型地原因。这种类型是不可编辑的。 numericTableItem 显示一个右对齐的数字。这种类型很好,不会加上一些怪异的内容。可调用TblSetItemInt()函数来设置数字。这种类型是不可编辑的。 popupTriggerTableItem 这种类型类似于弹出触发按纽。使用TblSetItemPtr()函数可以指向列表框的游标使列表框显示出来,使用TblSetItemInt()可以设置列表框到底选中哪一个条目。 TextTableItem 这种类型类似于编辑框,它是可编辑的。编辑框的长度可以改变和重叠。使用TblSetLoadDataProcedure()定义一个定制导入函数,将编辑框的句柄传递给表。使用TblSetSaveDataProcedure()定义一个保存函数,可以将数据保存在此句柄的编辑框中。所以你必须写这两个定制函数来支持表中的编辑框操作。 textWithNoteTableItem 这种类型会在一般的文本条目右边加入一个小的提示图标。这提示图标看起来象单独地被选中。当单元被选中后,你须调用TblEditing()看一下编辑框是否为可编辑模式。如果不是,Note图标已经被选中了,你就要切换到你的Note窗体去处理。 narrowTextTableItem 除了可以使用TblSetItemInt()在字段末尾处定义空间的大小,使之符合所填内容外,这个类型和一般的TextTableItem类型相同。例如,在日历窗体中,为了在条目的右边放置小的警告钟图标,Date Book程序就用这种类型来提供空间。 因为所有存在的类型都有其专用性,所以只有自己定制类型才能完成自己想实现的功能。 表的属性 表8-2中是表的属性描述。 和其它资源属性一样,在窗体中选中表资源后,就可以在构造器中进行编辑。 名称 描述 Object Identifier 在资源头文件中,构造器用之代表资源ID Table ID 表的资源ID号。 Left Origin 水平方向上控件的最左端位置 Top Origin 垂直方向上控件的最顶端位置 Width 表的宽度 Height 表的高度。 Editable 定义表中可编辑的数据是否能被用户输入 Rows 表中可见的行数。 Column Widths 每一列的宽度,如果要定义一个新的列,按CTRL-K 添加一个表 现在将表添加到Contact List窗体中: 1.运行Metrowerks 构造器; 2.打开资源文件Contacts.rsrc,它位于工程文件夹中的Src文件夹中; 3.双击打开Contact List窗体。 4.在菜单中选择Window | Catalog来打开Catalog; 5.拖动表资源到窗体中; 6.设置表的属性:Object Identifier=Table,Left Origin=0,Top Origin=15,Width=153,Height=130。这样就有足够的空间放置十行,然后设置Rows为10,这样设置也可以在窗体右边留有足够的空间放置滚动条; 7.定义Column Widths。设Column Width从1到40。选中Column Width 1,按CTRL-K创建一个新的列。设置此列宽度从2到40。选中Column Width 2,按CTRL-K创建第三列。设置Column Width从3到73; 8.Contact List窗体看起来如图8-2所示。 在表中显示记录 我们将添加表的两个基本函数:drawTable()和drawCell()。drawTable()在光标的当前状态绘制表。函数drawCell()是定制的单元输入函数,当Palm OS要向表中输入一个条目时,就会执行这个函数。我们先加入这些函数的原型: static void drawTable( void ); static void drawCell( VoidPtr table, Word row, Word column, RectanglePtr bounds ); drawCell()的函数原型必须和订制字单元输入的回馈函数原型相匹配。在Palm OS文献的TblSetCustomDrawProcedure()中有这个原型的定义。 为了整洁起见,最好在文件的开头定义常量: // 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" 常量TABLE_NUM_COLUMNS和TABLE_NUM_ROWS定义了窗体中表显示的大小,这与以后的很多运算与迭代有关。接下去的三个常量TABLE_COLUMN_DATE、TABLE_COLUMN_TIME和TABLE_COLUMN_NAME定义了每列所填写的信息。最后的四个常量BLACK_UP_ARROW,BLACK_DOWN_ARROW,GRAY_UP_ARROW和GRAY_DOWN_ARROW是Palm Os中Symol 7字体中代表这些图的ASCII值。当滚动条到达顶部或底部,我们使用这些常量给箭头加上灰晕。值得注意的是,在Palm Os中,只有这个控件可以添加灰晕。 函数contactListHandleEvent()的修改 找到Contact List窗体的事件处理函数contactListHandleEvent(),在这里需要添加drawTable()函数调用: // CH.7 Form open event case frmOpenEvent: { // CH.7 Draw the form FrmDrawForm( form ); // CH.8 Populate and draw the table drawTable(); } break; 接着,处理表中记录被选中后的操作,在选中一条记录后应该调用Contact Detail窗体来显示其详细信息。请注意这些代码与处理列表框记录选中后的代码很相似。为使Contact Detail窗体显示相应的记录,我们设置了游标(Cursor)变量。 // CH.7 Respond to a list selection case tblSelectEvent: { // CH.7 Set the database cursor to the selected contact cursor += event->data.tblSelect.row; // CH.7 Go to contact details FrmGotoForm( ContactDetailForm ); } break; 因为数据库要根据了不同的标准排序,所以每次排序后都要重新画表来显示新的记录顺序。为此,在DmQuickSort()后加入drawTable()函数来响应popSelectEvent事件。 // CH.7 Sort the contact database by the new criteria DmQuickSort( contactsDB, (DmComparF*)sortFunc, sortBy ); // CH.8 Rebuild the table drawTable(); } break; 这样对这个函数的修改就完成了。 添加drawTable()函数 下面添加drawTable()函数。先定义一些变量,并获取表的指针。 // CH.8 Draw our list of choices using a table object static void drawTable( void ) { FormPtr form; TablePtr table; Int column; Int count; ControlPtr upArrow; ControlPtr downArrow; // CH.8 Get the form pointer form = FrmGetActiveForm(); // CH.8 Get the table pointer table = getObject( form, ContactListTableTable ); 我们将对表中的列做两件事情。首先,每一列都要有一个定制的规则(Routine)。虽然条目类型是基于单元的,但如果单元是定制的,每一单元在特定的列上都要使用相同的规则。在例子中,我们将创建一个定制规则——drawCell(),在表的每个单元中都将使用这个规则。 另外一个要做的事情是使列为可见。列的缺省值是不可见的,为了显示需要将其设置为可见。 // CH.8 For all columns for( column = 0; column < TABLE_NUM_COLUMNS; column++ ) { // CH.8 Set the draw routine TblSetCustomDrawProcedure( table, column, drawCell ); // CH.8 Make the column visible TblSetColumnUsable( table, column, true ); } 下面,再来讲述表的行。由于表中的每一单元都需要定义一个类型,所以我们对列进行了操作。对于表中的不用(Unused)的行来说,就不需这样做。如果数据库包含的记录少于可见的行数,就需把表中不用的行关闭。这是很重要的,如果不关掉这些不用的行,当写代码时,我们就会试图向行中写不存在的记录,说不定会使系统崩溃的。既然表中的记录数是在变化的,我们就要保证在有记录时,标记行为可用,在没有记录时,标记行为不可用。 // CH.8 Initialize the table styles for( count = 0; count < TABLE_NUM_ROWS; count++ ) { // CH.8 If there is data if( count < numRecords ) { // CH.8 Show the row TblSetRowUsable( table, count, true ); // CH.8 Set the cell styles for( column = 0; column < TABLE_NUM_COLUMNS; column++ ) TblSetItemStyle( table, count, column, customTableItem ); } else // CH.8 Hide unused rows if any TblSetRowUsable( table, count, false ); } // CH.8 Draw the table TblDrawTable( table ); 一旦表的类型确定,通过命令TblDrawTable()将表画出来。 值得注意的是,使用TblSetRowUsable()函数可以在浏览表时,只显示所览数据库的一列,这种方法的缺点是它比我们后面章节使用的方法要耗费更多的内存。 添加drawCell()函数 通过前面的准备,现在终于可以调用我们定制函数drawCell()了,每次它都会在表中绘制一条目(Item)。下面是函数的开始部分: // CH.8 The custom drawing routine for a table cell static void drawCell( VoidPtr table, Word row, Word column, RectanglePtr bounds ) { Int record; CharPtr precord; Char string[DB_FIRST_NAME_SIZE + DB_LAST_NAME_SIZE]; SWord width; SWord len; Boolean noFit; 由于这个函数是通过调用TblSetCustomDrawProcedure()设置的回馈(CallBack)函数,所以它的参数和返回值就由此而决定。我们会从中得到表的指针、每一单元的行和列、每一单元在窗体上的矩形框。 // CH.8 Calculate our record record = cursor + row; // CH.8 Get our record hrecord = DmQueryRecord( contactsDB, record ); precord = MemHandleLock( hrecord ); // CH.8 Get the date and time MemMove( &dateTime, precord + DB_DATE_TIME_START, sizeof( dateTime ) ); 首先,我们得到一条和这一行相关联的记录,然后提取日期和时间,使之更容易被输入。 // CH.8 Switch on the column switch( column ) { // CH.8 Handle dates case TABLE_COLUMN_DATE: { if( dateTime.year != NO_DATE ) { DateToAscii( dateTime.month, dateTime.day, dateTime.year, (DateFormatType)PrefGetPreference( prefDateFormat ), string ); } else StrCopy( string, "-" ); } break; 根据列的类型,我们创建了要显示的字符串。对日期来说,所用的函数和列表框中显示时间的函数相同,在没有日期的地方将以短划线表示。 // CH.8 Handle times case TABLE_COLUMN_TIME: { if( dateTime.hour != NO_TIME ) { TimeToAscii( dateTime.hour, dateTime.minute, (TimeFormatType)PrefGetPreference( prefTimeFormat ), string ); } else StrCopy( string, "-" ); } break; 下一列显示时间。它和列表框中显示时间的函数相同,如果没有日期,我们以短划线来代替。 // CH.8 Handle names case TABLE_COLUMN_NAME: { StrCopy( string, precord + DB_FIRST_NAME_START ); StrCat( string, " " ); StrCat( string, precord + DB_LAST_NAME_START ); } break; 第三列也就是最后一列显示名和姓。我们写入了为单元新建的文本。 // CH.8 Unlock the record MemHandleUnlock( hrecord ); 因为我们已经创建了合适的文本字符串,现在就可以将记录解锁(Unlock)向里面写入了。注意,这种方法没有使用永久(Permanently)内存存储单元数据,因此无论数据库中有多少记录,这个函数都能很好的工作。在表和列表框中都使用订制函数写入数据的好处可见一斑。 // CH.8 Set the text mode WinSetUnderlineMode( noUnderline ); FntSetFont( stdFont ); // CH.8 Truncate the string if necessary width = bounds->extent.x; len = StrLen( string ); noFit = false; FntCharsInWidth( string, &width, &len, &noFit ); 下面,为了使WinDrawChars()能达到我们的要求,必须把文本模式设置好。名字或许不能在屏幕上能显示的空间中完全显示出来,所以需要检查字符串避免不要太长而超出单元显示的范围。如果太长,我们只好去掉多余的部分。事实上,如果你很充分的想象力的话,可以想办法在字符串的末尾添上省略号(……)表示其多余的部分。 // CH.8 Draw the cell WinEraseRectangle( bounds, 0 ); WinDrawChars( string, len, bounds->topLeft.x, bounds->topLeft.y ); // CH.8 We're done return; } 最后,在清除屏幕上以前的内容后,将字符串写入。这样定制函数就完成了。它很容易编写且有极高的灵活性。 调试 当在第一次运行时,最好是单步执行drawTable()和drawCell()函数。如果不关闭表中不用的行,由于drawCell()将试图访问不存在的记录,系统有可能会崩溃。记住在Detail 窗体和表之间不断的切换测试,并且使用下拉框使用不同排序标准进行排序。 Contact List窗体看起来如图8-3所示。 三种滚动条 在Palm OS中普遍使用的有三种滚动条。第一种是滚动按钮,它是一对向上和向下重复按钮,在Enter Time窗体中我们已经使用过,它们可以在所有的Palm OS版本中使用;第二种是滚动条;除了不能在Piolt1000或Piolt5000使用外,在其它Palm OS版本中都可以使用;第三种是PAGE UP和PAGE DOWN键。 下面,我们就将加入资源和代码来支持Contact List窗体中的这三种滚动条。但这样做通常并不是个好主意。 滚动条属性 表8-3是滚动条的属性: 名称 描述 Object Identifier 在资源头文件中,构造器用之代表资源ID Scrollbar ID 滚动条的ID号。 Left Origin 水平方向上控件的最左端位置 Top Origin 垂直方向上控件的最顶端位置 Width 滚动条的宽度值。 Height 滚动条的高度值。 Usable 定义滚动条是否可见。 Value 滚动条的最初值。 Minimum Value 滚动条的最小值。 Maximum Value 滚动条的最小值。 Page Size 滚动条所关联的行或记事行的每一页的大小,这个用来设置滚动条中Box的大校 Orientation 定义滚动条是水平方向还是垂直方向 添加滚动按钮和滚动条资源 添加两个滚动按钮和一个滚动条来支持三种滚动条类型其中的两种。 1.运行Metrowerks 构造器; 2.打开资源文件Contacts.rsrc,它位于你的项目文件夹中的Src文件夹中; 3.双击打开Contact List窗体; 4.在菜单中选择Window | Catalog,打开Catalog。 5.拖动一个滚动条到窗体中; 6.修改滚动条的属性:Object Identifier=Scrollbar,Left Origin=153,Top Origin=15,Width=7,Height=130。这样滚动条正好在表的最右边,紧靠窗体的右边界。 7.添加滚动按钮。你可以从Enter Time窗体中将滚动按钮拷贝过来,打开Enter Time窗体。从Enter Time窗体中把滚动按钮拖到Contact List窗体中。把向上的箭头的Left Origin设为149,Top Origin为145,将Object Identifier改为RecordUp;把向下的箭头Left Origin设为149,Top Origin为152,将Object Identifier改为RecordDown。 8.Contact List窗体看起来如图8-4。 让滚动按钮工作起来 在例子中,所要做的首要工作是要使游标(cursor)变量与表的顶部位置相等。并且使向上箭头在到达记录的顶部时要变灰,向下按钮在到达记录的底部时变灰。首先在contactListHandleEvent()中加入代码: // CH.8 Respond to arrows case ctlRepeatEvent: { switch( event->data.ctlRepeat.controlID ) { // CH.8 Up arrow case ContactListRecordUpRepeating: if( cursor > 0 ) cursor--; break; // CH.8 Down arrow case ContactListRecordDownRepeating: if( (numRecords > TABLE_NUM_ROWS) && (cursor < numRecords - TABLE_NUM_ROWS) ) cursor++; break; } // CH.8 Now refresh the table drawTable(); } return( true ); 这些代码十分简单。注意由于响应重复按钮事件,所以需在ctlRepeatEvent事件中添加代码。对于向上的箭头,每按一次游标中减一;对于向下的箭头,没按一次游标中加一。 为了保证安全,需要检查游标到底能移到什么地方。在绘制表的过程中,我们会重新绘制按纽,或在需要的地方使按钮变得不可用。 为了完成这个操作,在drawTable()的按钮响应事件中添加以下代码: // CH.8 Get pointers to the arrow buttons upArrow = getObject( form, ContactListRecordUpRepeating ); downArrow = getObject( form, ContactListRecordDownRepeating ); // CH.8 Update the arrow buttons and scrollbars if( numRecords > TABLE_NUM_ROWS ) { // CH.8 Show the up arrow if( cursor > 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( cursor >= numRecords - TABLE_NUM_ROWS ) { CtlSetLabel( downArrow, GRAY_DOWN_ARROW ); CtlSetEnabled( downArrow, false ); } else { CtlSetLabel( downArrow, BLACK_DOWN_ARROW ); CtlSetEnabled( downArrow, true ); } CtlShowControl( downArrow ); // CH.8 Show the scrollbar FrmShowObject( form, FrmGetObjectIndex( form, ContactListScrollbarScrollBar ) ); SclSetScrollBar( getObject( form, ContactListScrollbarScrollBar ), cursor, 0, numRecords - TABLE_NUM_ROWS, TABLE_NUM_ROWS ); } else { // CH.8 Hide the arrows CtlHideControl( upArrow ); CtlHideControl( downArrow ); // CH.8 Hide the scrollbar FrmHideObject( form, FrmGetObjectIndex( form, ContactListScrollbarScrollBar ) ); } // CH.8 We're done return; } 如果表的位置在开头或末尾,我们将重复按钮打上灰晕使之为不可用。这样就防止了游标被置到一个不存在值。 这样工作就完成了,重复按纽实现了象表的滚动条箭头一样的功能。 对PAGE UP和PAGE DOWN键的支持 为了捕捉PAGE UP和PAGE DOWN键,首先必须在keyDownEvent里添加代码。数学上的知识可以给我们一些提示。在当向上翻页或向下翻页,最好能在页面上留下一条常识的线。移动记录时不应移动到TABLE_NUM_ROWS,而应移动到TABLE_NUM_ROWS-1。由于不能使上下翻页键为不可用,就必须保证在按下它们时不会超出游标的移出范围。此外,游标和numRecords都是无符号的,所以必须在做数学运算前进行检查,避免它们变为负数而指向了不存在的值。这需要对contactListHandleEvent()作一些修改: // 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: if( cursor > TABLE_NUM_ROWS - 1 ) cursor -= TABLE_NUM_ROWS - 1; else cursor = 0; break; 对向上翻页来说,运算相当简单。如果向上翻页没有使记录游标小于零,向上翻一整页;否则,就翻到零记录为止。 // CH.8 Down arrow hard key case pageDownChr: if( (numRecords > 2 * TABLE_NUM_ROWS - 1) && (cursor < numRecords - 2 * TABLE_NUM_ROWS - 1) ) cursor += TABLE_NUM_ROWS - 1; else cursor = numRecords - TABLE_NUM_ROWS; break; } // CH.8 Now refresh the table drawTable(); } break; 对向下翻页来说,必须注意,当游标是numRecords减去TABLE_NUM_ROWS后(切记游标是基于零的),表是否已经到了最后的一条记录。所以首要的是检查翻页是否超出了最后一个记录。首先,保证表中有足够的记录在从numRecords中减去它后仍是一个正数。然后再检查游标是否到了最后一条记录。如果没有,向下翻一个整页。如果已超过了最后一条记录,翻到最后一条记录为止。 在程序的最后,和滚动按钮程序一样重新绘制表。完成这些后就可以支持翻页键了。 设计滚动条 滚动条需要在事件处理和订制程序中都添加一小段代码,首先来看一下事件处理中的代码: // CH.8 Respond to scrollbar events case sclRepeatEvent: cursor = event->data.sclExit.newValue; drawTable(); break; 使游标和新的滚动条值相等,就可以响应滚动条滚动事件。如果正确地设置了滚动条滚动的范围,就能保证不会使游标得到错误的值。在为游标赋值后,和其它的滚动条类型一样,需要刷新表和滚动条。 下面,看看添加在drawTable()中的代码。代码添加在滚动按钮代码中的if(numRecords>TABLE_NUM_ROWS)声明后面: // CH.8 Show the scrollbar FrmShowObject( form, FrmGetObjectIndex( form, ContactListScrollbarScrollBar ) ); SclSetScrollBar( getObject( form, ContactListScrollbarScrollBar ), cursor, 0, numRecords - TABLE_NUM_ROWS, TABLE_NUM_ROWS ); 在这里显示了滚动条,并将其设置了精确的值。因为我们已知道表中存在的记录比可见的行数多,所以numRecords-TABLE_NUM_ROWS不会产生一个错误的结果。 但如果不是这样,而是存在的记录比可见的行数要少,就要隐藏滚动条: // CH.8 Hide the scrollbar FrmHideObject( form, FrmGetObjectIndex( form, ContactListScrollbarScrollBar ) ); } 支持滚动条的代码修改就完成了。 调试 和以前一样,首先调试刚刚添加的代码。另外,将所有的滚动条值移动到第一个和最后一个记录上,次数不要太少(一些记录不会显示)或太多(系统会崩溃的)。 Contact List窗体看起来如图8-5。 下一步做什么 在下一章中,我们将通过在Contacts中添加其他的一些很出色的函数,如系统查找、分类、保密记录等,来结束本书的基础知识部分。 清单 这是经过这一章修改后的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 ); // 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.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 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.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.5 Open the database contactsDB = DmOpenDatabaseByTypeCreator( 'ctct', 'PPGU', dmModeReadWrite ); // 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 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 // 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 // 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.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 += 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 Rebuild the table drawTable(); } break; // CH.8 Respond to arrows case ctlRepeatEvent: { switch( event->data.ctlRepeat.controlID ) { // CH.8 Up arrow case ContactListRecordUpRepeating: if( cursor > 0 ) cursor--; break; // CH.8 Down arrow case ContactListRecordDownRepeating: if( (numRecords > TABLE_NUM_ROWS) && (cursor < numRecords - TABLE_NUM_ROWS) ) cursor++; break; } // CH.8 Now refresh the table drawTable(); } return( true ); // 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: if( cursor > TABLE_NUM_ROWS - 1 ) cursor -= TABLE_NUM_ROWS - 1; else cursor = 0; break; // CH.8 Down arrow hard key case pageDownChr: if( (numRecords > 2 * TABLE_NUM_ROWS - 1) && (cursor < numRecords - 2 * TABLE_NUM_ROWS - 1) ) cursor += TABLE_NUM_ROWS - 1; else cursor = numRecords - TABLE_NUM_ROWS; break; } // CH.8 Now refresh the table drawTable(); } break; // CH.8 Respond to scrollbar events case sclRepeatEvent: cursor = event->data.sclExit.newValue; drawTable(); break; } // 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 // 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.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.5 A record pointer Word index; // CH.5 The object index // 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.5 We're done return; } // CH.5 Puts any field changes in the record static 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, precord, NULL, (DmComparF*)sortFunc, sortBy ); // CH.5 Unlock the record MemHandleUnlock( hrecord ); // CH.7 Reattach the record DmAttachRecord( contactsDB, &cursor, hrecord, NULL ); } // CH.5 Reset the dirty bit isDirty = false; // CH.5 We're done return; } // CH.5 Set the text in a field static void setText( FieldPtr field, CharPtr text ) { VoidHand hfield; // CH.5 Handle of field text CharPtr pfield; // CH.5 Pointer to field text // CH.5 Get the current field handle hfield = FldGetTextHandle( field ); // CH.5 If we have a handle if( hfield != NULL ) { // CH.5 Resize it if( MemHandleResize( hfield, StrLen( text ) + 1 ) != 0 ) errorExit( MemoryErrorAlert ); } else // CH.5 Allocate a handle for the string { hfield = MemHandleNew( StrLen( text ) + 1 ); if( hfield == NULL ) errorExit( MemoryErrorAlert ); } // CH.5 Lock it pfield = MemHandleLock( hfield ); // CH.5 Copy the string StrCopy( pfield, text ); // CH.5 Unlock it MemHandleUnlock( hfield ); // CH.5 Give it to the field FldSetTextHandle( field, hfield ); // CH.5 Draw the field FldDrawField( field ); // CH.5 We're done return; } // CH.5 Get the text from a field static void getText( FieldPtr field, VoidPtr precord, Word offset ) { CharPtr pfield; // CH.5 Pointer to field text // CH.5 Get the text pointer pfield = FldGetTextPtr( field ); // CH.5 Copy it DmWrite( precord, offset, pfield, StrLen( pfield ) ); // CH.5 We're done return; } // CH.6 Set the Contact Detail date selector trigger static void setDateTrigger( void ) { FormPtr form; // CH.5 The contact detail form // CH.6 Get the contact detail form pointer form = FrmGetActiveForm(); // CH.6 If there is no date if( dateTime.year == NO_DATE ) { CtlSetLabel( getObject( form, ContactDetailDateSelTrigger ), " " ); } else // CH.6 If there is a date { Char dateString[dateStringLength]; // CH.6 Get the date string DateToAscii( dateTime.month, dateTime.day, dateTime.year, (DateFormatType)PrefGetPreference( prefDateFormat ), dateString ); // CH.6 Set the selector trigger label CtlSetLabel( getObject( form, ContactDetailDateSelTrigger ), dateString ); } // CH.6 We're done return; } // CH.6 Set the Contact Detail time selector trigger static void setTimeTrigger( void ) { FormPtr form; // CH.5 The contact detail form // CH.6 Get the contact detail form pointer form = FrmGetActiveForm(); // CH.6 If there's no time if( dateTime.hour == NO_TIME ) { CtlSetLabel( getObject( form, ContactDetailTimeSelTrigger ), " " ); } else // CH.6 If there is a time { Char timeString[timeStringLength]; // CH.6 Get the time string TimeToAscii( dateTime.hour, dateTime.minute, (TimeFormatType)PrefGetPreference( prefTimeFormat ), timeString ); // CH.6 Set the selector trigger label CtlSetLabel( getObject( form, ContactDetailTimeSelTrigger ), timeString ); } // CH.6 We're done return; } // CH.6 Set the controls in the Enter Time form based on dateTime static void setTimeControls( void ) { FormPtr form; ControlPtr hourButton; ControlPtr minuteTensButton; ControlPtr minuteOnesButton; ControlPtr amButton; ControlPtr pmButton; ControlPtr noTimeCheckbox; Char labelString[3]; SWord hour; // CH.6 Get the form form = FrmGetActiveForm(); // CH.6 Get the control pointers hourButton = getObject( form, EnterTimeHoursPushButton ); minuteTensButton = getObject( form, EnterTimeMinuteTensPushButton ); minuteOnesButton = getObject( form, EnterTimeMinuteOnesPushButton ); amButton = getObject( form, EnterTimeAMPushButton ); pmButton = getObject( form, EnterTimePMPushButton ); noTimeCheckbox = getObject( form, EnterTimeNoTimeCheckbox ); // CH.6 If there is a time if( dateTime.hour != NO_TIME ) { // CH.6 Update the hour hour = dateTime.hour % 12; if( hour == 0 ) hour = 12; CtlSetLabel( hourButton, StrIToA( labelString, hour ) ); // CH.6 Update the minute tens CtlSetLabel( minuteTensButton, StrIToA( labelString, dateTime.minute / 10 ) ); // CH.6 Update the minute ones CtlSetLabel( minuteOnesButton, StrIToA( labelString, dateTime.minute % 10 ) ); // CH.6 Update AM CtlSetValue( amButton, (dateTime.hour < 12) ); // CH.6 Update PM CtlSetValue( pmButton, (dateTime.hour > 11) ); // CH.6 Uncheck the no time checkbox CtlSetValue( noTimeCheckbox, false ); } else // If there is no time { // CH.6 Update the hour CtlSetValue( hourButton, false ); CtlSetLabel( hourButton, "" ); // CH.6 Update the minute tens CtlSetValue( minuteTensButton, false ); CtlSetLabel( minuteTensButton, "" ); // CH.6 Update the minute ones CtlSetValue( minuteOnesButton, false ); CtlSetLabel( minuteOnesButton, "" ); // CH.6 Update AM CtlSetValue( amButton, false ); // CH.6 Update PM CtlSetValue( pmButton, false ); // CH.6 Uncheck the no time checkbox CtlSetValue( noTimeCheckbox, true ); } // CH.6 We're done return; } // CH.7 This function is called by Palm OS to sort records static Int sortFunc( CharPtr precord1, CharPtr precord2, Int sortBy ) { Int sortResult; // CH.7 Switch based on sort criteria switch( sortBy ) { // CH.7 Sort by date and time case SORTBY_DATE_TIME: { DateTimePtr pdateTime1; DateTimePtr pdateTime2; Long lDiff; pdateTime1 = (DateTimePtr)(precord1 + DB_DATE_TIME_START); pdateTime2 = (DateTimePtr)(precord2 + DB_DATE_TIME_START); // CH.7 Compare the dates and times lDiff = (Long)(TimDateTimeToSeconds( pdateTime1 ) / 60 ) - (Long)(TimDateTimeToSeconds( pdateTime2 ) / 60 ); // CH.7 Date/time #1 is later if( lDiff > 0 ) sortResult = 1; else // CH.7 Date/time #2 is later if( lDiff < 0 ) sortResult = -1; else // CH.7 They are equal sortResult = 0; } break; // CH.7 Sort by first name case SORTBY_FIRST_NAME: { sortResult = StrCompare( precord1 + DB_FIRST_NAME_START, precord2 + DB_FIRST_NAME_START ); } break; // CH.7 Sort by last name case SORTBY_LAST_NAME: { sortResult = StrCompare( precord1 + DB_LAST_NAME_START, precord2 + DB_LAST_NAME_START ); } break; } // CH.7 We're done return( sortResult ); } // CH.8 Draw our list of choices using a table object static void drawTable( void ) { FormPtr form; TablePtr table; Int column; Int count; ControlPtr upArrow; ControlPtr downArrow; // CH.8 Get the form pointer form = FrmGetActiveForm(); // CH.8 Get the table pointer table = getObject( form, ContactListTableTable ); // CH.8 For all columns for( column = 0; column < TABLE_NUM_COLUMNS; column++ ) { // CH.8 Set the draw routine TblSetCustomDrawProcedure( table, column, drawCell ); // CH.8 Make the column visible TblSetColumnUsable( table, column, true ); } // CH.8 Initialize the table styles for( count = 0; count < TABLE_NUM_ROWS; count++ ) { // CH.8 If there is data if( count < numRecords ) { // CH.8 Show the row TblSetRowUsable( table, count, true ); // CH.8 Set the cell styles for( column = 0; column < TABLE_NUM_COLUMNS; column++ ) TblSetItemStyle( table, count, column, customTableItem ); } else // CH.8 Hide unused rows if any TblSetRowUsable( table, count, false ); } // CH.8 Draw the table TblDrawTable( table ); // CH.8 Get pointers to the arrow buttons upArrow = getObject( form, ContactListRecordUpRepeating ); downArrow = getObject( form, ContactListRecordDownRepeating ); // CH.8 Update the arrow buttons and scrollbars if( numRecords > TABLE_NUM_ROWS ) { // CH.8 Show the up arrow if( cursor > 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( cursor >= numRecords - TABLE_NUM_ROWS ) { CtlSetLabel( downArrow, GRAY_DOWN_ARROW ); CtlSetEnabled( downArrow, false ); } else { CtlSetLabel( downArrow, BLACK_DOWN_ARROW ); CtlSetEnabled( downArrow, true ); } CtlShowControl( downArrow ); // CH.8 Show the scrollbar FrmShowObject( form, FrmGetObjectIndex( form, ContactListScrollbarScrollBar ) ); SclSetScrollBar( getObject( form, ContactListScrollbarScrollBar ), cursor, 0, numRecords - TABLE_NUM_ROWS, TABLE_NUM_ROWS ); } else { // CH.8 Hide the arrows CtlHideControl( upArrow ); CtlHideControl( downArrow ); // CH.8 Hide the scrollbar FrmHideObject( form, FrmGetObjectIndex( form, ContactListScrollbarScrollBar ) ); } // CH.8 We're done return; } // CH.8 The custom drawing routine for a table cell static void drawCell( VoidPtr table, Word row, Word column, RectanglePtr bounds ) { Int record; CharPtr precord; Char string[DB_FIRST_NAME_SIZE + DB_LAST_NAME_SIZE]; SWord width; SWord len; Boolean noFit; // CH.8 Calculate our record record = cursor + row; // CH.8 Get our record hrecord = DmQueryRecord( contactsDB, record ); precord = MemHandleLock( hrecord ); // CH.8 Get the date and time MemMove( &dateTime, precord + DB_DATE_TIME_START, sizeof( dateTime ) ); // CH.8 Switch on the column switch( column ) { // CH.8 Handle dates case TABLE_COLUMN_DATE: { if( dateTime.year != NO_DATE ) { DateToAscii( dateTime.month, dateTime.day, dateTime.year, (DateFormatType)PrefGetPreference( prefDateFormat ), string ); } else StrCopy( string, "-" ); } break; // CH.8 Handle times case TABLE_COLUMN_TIME: { if( dateTime.hour != NO_TIME ) { TimeToAscii( dateTime.hour, dateTime.minute, (TimeFormatType)PrefGetPreference( prefTimeFormat ), string ); } else StrCopy( string, "-" ); } break; // CH.8 Handle names case TABLE_COLUMN_NAME: { StrCopy( string, precord + DB_FIRST_NAME_START ); StrCat( string, " " ); StrCat( string, precord + DB_LAST_NAME_START ); } break; } // CH.8 Unlock the record MemHandleUnlock( hrecord ); // CH.8 Set the text mode WinSetUnderlineMode( noUnderline ); FntSetFont( stdFont ); // CH.8 Truncate the string if necessary width = bounds->extent.x; len = StrLen( string ); noFit = false; FntCharsInWidth( string, &width, &len, &noFit ); // CH.8 Draw the cell WinEraseRectangle( bounds, 0 ); WinD |
本文地址:http://www.45fan.com/a/question/73509.html