敬业的IT人 >> 编程开发 >> 其他语言 >> Palm OS应用程序设计指南之七

Palm OS应用程序设计指南之七

敬业的IT人 互联网 佚名 2008-1-4 14:07:21

  在这一节中,我们将讨论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;进入讨论组讨论。
    ·应用MFC开发高级应用程序
    ·在VC++应用程序中读取文本数据
    ·使用GWT开发AJAX应用程序
    ·在Ajax应用程序中实现数据交换
    ·DirectSound应用程序开发快速入门
    ·用Indy组件开发Socket应用程序
    ·MFC应用程序框架入门
    ·VFP基础教程 第十一章 应用
    ·用VC++建立Service服务应用程序
    ·用VC++编制FTP客户端应用程序

  

  相对其它的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程序就用这种类型来提供空间。
  因为所有存在的类型都有其专用性,所以只有自己定制类型才能完成自己想实现的功能。

  表的属性

  下表是表的属性描述。

  和其它资源属性一样,在窗体中选中表资源后,就可以在构造器中进行编辑。

名称 描述Object Identifier在资源头文件中,构造器用之代表资源IDTable 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;进入讨论组讨论。
    ·应用MFC开发高级应用程序
    ·在VC++应用程序中读取文本数据
    ·使用GWT开发AJAX应用程序
    ·在Ajax应用程序中实现数据交换
    ·DirectSound应用程序开发快速入门
    ·用Indy组件开发Socket应用程序
    ·MFC应用程序框架入门
    ·VFP基础教程 第十一章 应用
    ·用VC++建立Service服务应用程序
    ·用VC++编制FTP客户端应用程序
在表中显示记录

  我们将添加表的两个基本函数:drawTable()和drawCell()。drawTable()在光标的当前状态绘制表。函数drawCell()是定制的单元输入函数,当Palm OS要向表中输入一个条目时,就会执行这个函数。我们先加入这些函数的原型:

static void drawTable( void );
static void drawCell( VoidPtr table, Wordrow, 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;
  这样对这个函数的修改就完成了。进入讨论组讨论。
    ·应用MFC开发高级应用程序
    ·在VC++应用程序中读取文本数据
    ·使用GWT开发AJAX应用程序
    ·在Ajax应用程序中实现数据交换
    ·DirectSound应用程序开发快速入门
    ·用Indy组件开发Socket应用程序
    ·MFC应用程序框架入门
    ·VFP基础教程 第十一章 应用
    ·用VC++建立Service服务应用程序
    ·用VC++编制FTP客户端应用程序
添加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, Wordrow, 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 o-n 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 窗体和表之间不断的切换测试,并且使用下拉框使用不同排序标准进行排序。进入讨论组讨论。
    ·应用MFC开发高级应用程序
    ·在VC++应用程序中读取文本数据
    ·使用GWT开发AJAX应用程序
    ·在Ajax应用程序中实现数据交换
    ·DirectSound应用程序开发快速入门
    ·用Indy组件开发Socket应用程序
    ·MFC应用程序框架入门
    ·VFP基础教程 第十一章 应用
    ·用VC++建立Service服务应用程序
    ·用VC++编制FTP客户端应用程序
三种滚动条

  在Palm OS中普遍使用的有三种滚动条。第一种是滚动按钮,它是一对向上和向下重复按钮,在Enter Time窗体中我们已经使用过,它们可以在所有的Palm OS版本中使用;第二种是滚动条;除了不能在Piolt1000或Piolt5000使用外,在其它Palm OS版本中都可以使用;第三种是PAGE UP和PAGE DOWN键。

  下面,我们就将加入资源和代码来支持Contact List窗体中的这三种滚动条。但这样做通常并不是个好主意。

  滚动条属性

  表8-3是滚动条的属性:

名称描述Object Identifier在资源头文件中,构造器用之代表资源IDScrollbar 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。

  让滚动按钮工作起来

  在例子中,所要做的首要工作是要使游标(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;
}
  如果表的位置在开头或末尾,我们将重复按钮打上灰晕使之为不可用。这样就防止了游标被置到一个不存在值。

  这样工作就完成了,重复按纽实现了象表的滚动条箭头一样的功能。进入讨论组讨论。
    ·应用MFC开发高级应用程序
    ·在VC++应用程序中读取文本数据
    ·使用GWT开发AJAX应用程序
    ·在Ajax应用程序中实现数据交换
    ·DirectSound应用程序开发快速入门
    ·用Indy组件开发Socket应用程序
    ·MFC应用程序框架入门
    ·VFP基础教程 第十一章 应用
    ·用VC++建立Service服务应用程序
    ·用VC++编制FTP客户端应用程序
对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 ) );
}
  支持滚动条的代码修改就完成了。

  调试

  和以前一样,首先调试刚刚添加的代码。另外,将所有的滚动条值移动到第一个和最后一个记录上,次数不要太少(一些记录不会显示)或太多(系统会崩溃的)。

  下一步做什么

  在下一节中,我们将通过在Contacts中添加其他的一些很出色的函数,如系统查找、分类、保密记录等,来结束本书的基础知识部分。进入讨论组讨论。
    ·应用MFC开发高级应用程序
    ·在VC++应用程序中读取文本数据
    ·使用GWT开发AJAX应用程序
    ·在Ajax应用程序中实现数据交换
    ·DirectSound应用程序开发快速入门
    ·用Indy组件开发Socket应用程序
    ·MFC应用程序框架入门
    ·VFP基础教程 第十一章 应用
    ·用VC++建立Service服务应用程序
    ·用VC++编制FTP客户端应用程序
清单

  这是经过这一章修改后的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 o-ne
  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 o-ne
 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 o-ne
 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 o-ne record
      if( cursor > 0 )
       cursor--;
     }
     break;

     // CH.5 Next button
     case ContactDetailNextButton:
     {
      // CH.5 Move the cursor up o-ne 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 o-ne
      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(),&currentDate );

       // 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 o-n 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 o-n button ID
    switch( buttonID )
    {
     // CH.6 Hours button
     case EnterTimeHoursPushButton:
      // CH.6 Minute Tens button
     case EnterTimeMinuteTensPushButton:
      // CH.6 Minute o-nes 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 o-n 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 o-n 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 o-n
// 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 o-n 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 o-n
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 o-n 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 o-nes
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 o-nes
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 o-n 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 o-n 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 );
WinDrawChars( string, len, bounds->topLeft.x, bounds->topLeft.y );

// CH.8 We're done
return;
}
进入讨论组讨论。
粤ICP备06119539号
Copyright CiscoSky.Org,Some Rights Reserved.
Email:me1228#tom.com