分享
 
 
 

PALM开发教程-第八章 表和滚动条

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

作者:palmheart 来源:palmheart.net 时间:2002年12月9日 13:42 阅读295次

在这一章中,我们将讨论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(),

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

WinDrawChars( string, len, bounds->topLeft.x, bounds->topLeft.y );

// CH.8 We're done

return;

}

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