开发PalmOS的方法
第六章 控件 |
在这一章中,我们将继续研究Contact Detail 程序,让它具有显示并设置日期时间的功能。我们要添加一个窗体来设置时间。这个时间和日期是可选的以供下次联系使用。 为实现此功能,我们将使用新的资源:选择触发器(selector triggers),开关按钮(push buttons),重复按钮(repeating buttons)。连同前面的按钮,它们都是Palm OS的控件。它们具有类似的属性,并且在触发时发出相同的事件。它们都可拥有自己的标签,它们都是被单击触发的。在单击后它们的形状都有所改变,不过有的只是瞬间改变就恢复了而已。 保存你的工程 当在修改工程之前,最好先制作一个它的副本。这样当出现问题时,你就可以拿出的工程的副本重新开始。步骤如下: 1. 打开Windows 资源管理器; 2. 找到工程所在的文件夹; 3. 选中工程,按下CTRL+C拷贝文件夹; 4. 选择想要保存到的文件夹; 5. 按下CTRL+V保存; 6. 将工程重新命名,以便你能清楚记忆。我将其命名为Contacts CH.5。 对Contatcs.rsrc文件内容的添加 这一部分我们为Contact Detail添加日期和时间的控件。我们还将创建一个用来改变时间的窗体。对改变日期,我们将调用Palm OS的标准对话框。 添加日期时间选择触发控件 向Contact Detail窗体添加两个标签和两个选择触发器(selector triggers)。我们将使用选择触发来显示下一次调用此contact的日期和时间。选择触发控件处理事件和按钮很相似,只是外形有很大不同。它被一个点壮矩形所环绕。和按钮相比,这个矩形在宽度和高度上都占有一个象素的额外空间,这一点在放置此控件时要考虑。有关选择触发器(selector triggers)的属性见表6-1。 Object Identifier 构造器用来代表资源头文件ID的常量 Selector Trigger ID 选择触发控件的资源ID; Left Origin 水平方向上控件的最左端位置; Top Origin 垂直方向上控件的最顶端位置; Width 控件的最大宽度。此属性很少使用,因为控件的右 边界会随着标签文本的长度改变而改变; Height 控件的高度; Usable 决定次控件是否可见能用。如果没有选中,也可在通过函数调用来实现其可见; Anchor Left 决定当文本长度改变时,控件的左侧或右侧是否做相应的伸缩; Font 标签使用的字体; Label 标签的缺省文本; 以下是添加控件的步骤: 1. 打开资源构造器; 2. 打开文件Contacts.rsrc。它在Src文件夹中; 3. 双击Contacts Detail窗体; 4. 选择Window | Catalog,产生控件模板; 5. 将一个标签拖到窗体上。置标签文本为Next Call Date。将它放在Phone Number 标签底下。设置Left Origin为0、Top Origin为60,文本字体为粗体; 6. 将一个选择触发器(selector triggers)拖到窗体上。设置Object Identifier为Date,Left Origin为81,Top Origin 为60,Width为78。向标签输入10个空格,这样可保证在缺省的情况下,当被finger按下时有充足的空间; 7. 向窗体上再拖一个标签。置标签文本为Next Call Time。将它放在Next Call Date 标签底下。设置Left Origin为12、Top Origin为80,文本字体为粗体; 8. 向窗体上再拖一个选择触发器(selector triggers)。设置Object Identifier为Time,Left Origin为81,Top Origin 为80,Width为78。也向标签输入10个空格。 9. 添加控件后的窗体如图6-1所示。按下右上角的X按钮,关闭窗体。 图6-1:Contact Detail 窗体 创建一个新的设置时间窗体 现在创建一个窗体: 1. 点击资源(resource)中的窗体(Forms)选项,按下CTRL-K创建一个新的窗体; 2. 点击name框并重命名为Enter Time; 3. 双击打开窗体进行编辑; 4. 首先设置窗体属性。复选中属性Modal和Save Behind。我们将此窗体作为对话框的形式出现;此窗体是我们接触到的第一个不是全屏显示的窗体;我们修改其宽度(Width)为156,其高度(Height)为53; 5. 为实现modal边框可见,我们需要在窗体和屏幕边界留出2个象素的宽度。所以宽度设为156,而左初始边界应为2,顶端初始边界应为105,这样就保证了两个象素的余度; 6. 修改窗体的名字(Name)属性为Enter Time。 添加开关按钮(push buttons) 开关按钮(push buttons)在表现为按下的状态时,不同于通常淡的底色和黑的文字,而是黑的底色和淡的文字。我们使用开关按钮(push buttons)显示小时、分钟、和上午/下午(AM/PM)。在放置开关按钮(push buttons)时,我们必须考虑它的边界所占用的一个象素的宽度。开关按钮(push buttons)的属性如表6-2所示: Object Identifier 资源构造器用来代表资源头文件ID的常量 Push Button ID 开关按钮(push buttons)的资源ID Left Origin 水平方向上按钮的最左端位置; Top Origin 垂直方向上按钮的最顶端位置; Width 按钮的宽度 Height 按钮的高度 Usable 用来定义控件是否可见及可用,如果没有选中,也可在通过函数调用来实现其可见 Group ID 表示当按钮按下时是否突出 显示。如果此数字为0,则按钮当被按下是将在“按下”和“没有按下”两个状态间切换。当此数字不为0时,按钮被按下是保持原来的状态。在每一组中所用的组(Group)ID应是唯一的,因为在后面的代码中将使用到组ID Font 标签显示文字的字体 Label 标签本身的文字 添加步骤: 1. 将一个开关按钮(push buttons)拖到Enter Time 窗体上; 2. 既然此按钮显示时间,可将Object Identifier设置为Hours。设置属性:Left Origin=5,Top Origin=17,Width为18,Group ID为1。Font属性为Bold,清除标签内的文字; 3. 复制Hours开关按钮(push buttons)。可选中Hours开关按钮(push buttons)后,按下CTRL-D进行复制。修改Object Identifier为MinutesTens。设置属性:Left Origin=34,Top Origin=17,Width为12,因为它只包含一个数字; 4. 复制MinutesTens按钮。可选中MinutesTens按钮后,按下CTRL-D进行复制。修改Object Identifier设置为MinutesOnes。设置属性:Left Origin=50,Top Origin=17; 5. 再拖动一个开关按钮(push buttons)到窗体上。修改Object Identifier设置为AM。设置属性:Left Origin=109,Top Origin=17,Width为20,Group ID为2。设置标签内的文字为AM; 6. 复制AM开关按钮(push buttons)。修改Object Identifier为PM。设置属性:Left Origin=130,Top Origin=17,注意AM 按钮和PM按钮重叠是为了使他们之间只有一个象素的间隔,这就是相关的开关按钮(push buttons)如何归为一组的方法。设置标签内的文字为PM; 名字 描述 Object Identifier 构造器用来代表资源头文件ID的常量。 Push Button ID 开关按钮(push buttons)标志号。 Left Origin 开关按钮(push buttons)的左边缘的水平起始位置。 Top Origin 开关按钮(push buttons)的顶边缘的垂直起始位置。 Width 开关按钮(push buttons)的宽度。 Height 开关按钮(push buttons)的高度。 Usable 该参数定义该控件是否可视和被激活。如该参数未被选择,你可通过一函数调用使该控件可视和被激活。 Group ID 该参数影响开关按钮(push buttons)被选时是否仍保持加亮状态。如该参数为0,开关按钮(push buttons)被按下时,其状态在“on”和“off”之间切换。如果你要在程序中使用组号,则该组号应为唯一。 Font 标签显示文字的字体 Label 标签本身的文字 向设置时间窗体添加重复按钮(repeating buttons) 如果输入笔按在Repeating按钮上的时间超过半秒,该按钮将连续发ctlRepeatEvent事件。头半秒之后,ctlRepeatEvent事件每十分之一秒发一次。我们使用重复按钮(repeating buttons)来构造Up和down箭头以调整时间。如同一般的按钮一样,重复按钮(repeating buttons)可有一边框。在我们的事例中,这些按钮不使用边框,因此,它们像标签和文本框一样排列。 重复按钮(repeating buttons)的属性如下表: 名字 描述 Object Identifier 构造器用来代表资源头文件ID的常量。 Button ID 重复按钮(repeating buttons)标志号。 Left Origin 按钮的左边缘的水平起始位置。 Top Origin 按钮的顶边缘的垂直起始位置。 Width 按钮的宽度。 Height 按钮的高度。 Usable 该参数定义该控件是否可视和被激活。如该参数未被选择,你可通过一函数调用使该控件可视和被激活。 Anchor Left 如该特性被选择,按钮被程序重定大小时向右扩张。 Frame 如该特性被选择,按钮将有一边框。 Non-Bold Frame 如该特性被选择,按钮边框粗细为1像素。 Font 标签显示文字的字体 Label 标签本身的文字 向设置时间窗体添加一个Repeating按钮 1. 将一个Repeating按钮拖放至设置时间窗体。将Object Identifier改为TimeUp。参考位置为Left Origion = 109, Top Origion = 17。参考大小为Width=20,Height =8。不选Frame特性。 2. 选字体为Symble 7,在label上选Hex box。键入01。这样,你在form上看到的是空白标签,而运行代码后将显示一个向上的箭头。不要被hex 21所迷惑。当你运行代码时,hex 21显示为一个复选框。 3. 通过选择Repeating按钮并按CTL-D来复制该按钮。将将Object Identifier改为TimeDown。此按钮的位置应为Left Origion = 69, Top Origion = 25。将label设置为hex 02,则程序运行时显示一个向下的箭头。 向设置时间窗体添加一个复选框 复选框的左端有一个小框可被选择以指示某些事。在设置时间窗体中,该小框可让用户选择不输入时间。复选框没有边框,所以它们的排列如同一个域一样。复选框的属性如下表所示: 名字 描述 Object Identifier 资源构造器用来代表资源头文件ID的常量。 Checkbox ID 复选框标志号。 Left Origin 复选框的左边缘的水平起始位置。 Top Origin 复选框的顶边缘的垂直起始位置。 Width 复选框的宽度。 Height 复选框的高度。 Usable 该参数定义该控件是否可视和被激活。如该参数未被选中,你可通过一函数调用使该控件可视和被激活。 Selected 如该参数被选择,则复选框被画时缺省为被眩 Group ID 该参数影响复选框被选时是否仍保持加亮状态。如该参数为0,复选框被按下时,其状态在被选和未选之间切换。如果你要在程序中使用组号,则该组号应为唯一。 Font 标签显示文字的字体。 Label 标签本身的文字 现在向设置时间窗体添加一个复选框: 1. 从Catalog窗拖放一个复选框到设置时间窗体。 2. 将Object Identifier属性设置为NoTime。参考位置为Left Origion = 53, Top Origion = 37。设Width为50。选Selected。设Group ID为0因为并未成组。设Label为NoTime。 完善设置时间窗体 现在让我们来添加一些熟悉的控件: 1. 小时和分钟之间用冒号隔开会更好看。从Catalog窗体拖放一个label控件到设置时间窗体中。位置为Left Origion = 27, Top Origion = 17。设字体为Bold。加一个冒号。 2. 每一个对话框都需要一个OK按钮。从Catalog窗体拖放一个button控件到设置时间窗体中。位置为Left Origion = 5, Top Origion = 37。注意Left Origion设为5使按钮与其上的push按钮对齐,并在左边缘留出4象素的空间。 3. 有一个Cancel按钮也会很好。拖放一个button控件到窗体中。改设Object Identifier为Cancel。其位置为Left Origion = 115, Top Origion = 37。改设Label为Cancel。 4. 使Cancel按钮为此窗体的缺省按钮。记下Cancel按钮的Button ID。点击窗体背景的任意处以显示设置时间窗体的属性表。将窗体缺省按钮的ID设为Cancel按钮的Button ID。 你已经完成了设置时间窗体的构建。你的窗体应该如下图所示。点击右上角的X来关闭窗口。选File | Save来保存你的改变。 向Contacts.c添加代码 现在,为了在数据库、控件以及我们才添加的新窗体中支持日期和时间,我们向Contacts.c添加代码。 在数据库中初始化和保存日期和时间 为了在内部和数据库中保存和定义日期和时间,你需要一些变量和常量的定义。 // 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 变量dateTime保存目前正被处理的记录的日期和时间。设置时间窗体使用timeSelect变量完成同样功能。常量NO_TIME和NO_DATAE同样使用dataTime来表示没有日期或没有时间或二者兼备。 在newRecord()功能中,加入代码设date和time的初始状态为没有日期和没有时间。 // 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 ) ); 注意我们使用MemSet()将整个记录清零。如果我们不这样做,记录中的域将有垃圾并且域功能将崩溃,因为我们并没有如域功能希望的那样发送以零定界的字符串。然后,我们把变量dateTime作为临时变量来初始化该记录。 图6-2:EnterTime 窗体 在setFields()功能中,从记录中载入dateTime变量的值。 // CH.6 Initialize the date and time variable precord = MemHandleLock( hrecord ); MemMove( &dateTime, precord + DB_DATE_TIME_START, sizeof( dateTime ) ); 并且,设置日期和时间(selector triggers)的外观。我们将在讨论选择触发器时详细研究这些功能。 // CH.6 Initialize the date control setDateTrigger(); // CH.6 Initialize the time control setTimeTrigger(); 支持日期和时间选择按钮 加入的第一行代码应是Contact Detail窗体事件处理句柄,对日期选择按钮来说,然后再添加向处理普通按钮那样事件处理过程。 // CH.6 Date selector trigger case ContactDetailDateSelTrigger: { // CH.6 Initialize the date if necessary if( dateTime.year == NO_DATE ) { DateTimeType currentDate; // CH.6 Get the current date TimSecondsToDateTime( TimGetSeconds(), ¤tDate ); // CH.6 Copy it dateTime.year = currentDate.year; dateTime.month = currentDate.month; dateTime.day = currentDate.day; } // CH.6 Pop up the system date selection form SelectDay( selectDayByDay, &(dateTime.month), &(dateTime.day), &(dateTime.year), "Enter Date" ); // CH.6 Get the record hrecord = DmQueryRecord( contactsDB, cursor ); // CH.6 Lock it down precord = MemHandleLock( hrecord ); // CH.6 Write the date time field DmWrite( precord, DB_DATE_TIME_START, &dateTime, sizeof( DateTimeType ) ); // CH.6 Unlock the record MemHandleUnlock( hrecord ); // CH.6 Mark the record dirty isDirty = true; } break; 如果以前没有填入时间,把时间设为当前时间。获取当前时间是通过调用函数TimGetSeconds()并通过函数 TimSeconsToDateTime()将其输出转换为日期。这些函数是时间处理器的一部分,在Palm OS的Reference.pdf文件中有详细的描述。 时间初始化完成后,我们调用函数SelectDay()以产生Palm OS内置控件来选择日期。当选定时间后,我们锁定数据库当前记录,将新的时间值写入。 选择时间有些不一样。因为Palm OS中没有可以直接选择时间的控件,那么我们弹出Enter Time 窗体来和用户交互。 // CH.6 Time selector trigger case ContactDetailTimeSelTrigger: { // CH.6 Pop up our selection form FrmPopupForm( EnterTimeForm ); } break; 在Contact Detail事件处理过程中我们所做的就是调出Enter Time 窗体。修改数据库的工作就由后者来处理。 函数setDateTrigger()用来更新日期选择按钮的外观,代码如下: // 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; } 如果没有时间,我们控件中写入10个空格。由于选择触发按钮能够根据标签自动调整大小,这样就可保证触发按钮足够大可用手指来选择。记住一定要保证控件标签(包括触发按钮)中的文本大小不要超过最初在构造器中定义的数量。 如果已有时间,那我们返回时间。系统内的优先权将决定我们使用什么格式来显示时间。我们调用带参数preDateFormat的函数PrefGetPreference()来获取日期的优先权。我们选择类型DateFormat的原因是preGetPreference()能够返回许多不同的优先权,我们选最普遍的一种。 函数DateToAscii()用来将日期变量转换为我们定义的短时间格式,这是由我们从系统中获得的。当我们获得时间后,我们就将其写入触发按钮标签内。 函数SetTimeTrigger()用来设置时间触发按钮。它和setDateTrigger()很相似,除了用它自己相应的函数外。 支持开关按钮(push buttons) 像在Contact Details所做的那样,我们也将建立switch语句来处理ctlSelectEvent。Case语句建立在不同button ID值上,它可用真正的控件ID来表示。 首先来处理小时和分钟开关按钮(push buttons): // 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; 按钮将在它们的标签上显示各自的值。哪一个值和上或下箭头相关联是根据哪一个被选中决定的。如果当按钮被选中后但没有时间显示,12PM将被设置并显示。如果先前已选择了一个按钮,那么我们先清除这个选择,然后选中我们刚刚点击的那个按钮。 在函数setTimeControl()中,我们看它是如何设置标签文本的。实际上,它和其他任何控件处理标签一样。 // 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 ) ); 时间是以24小时格式显示,我们把它转换为12小时格式并在按钮上显示。 在函数enterTimeHandleEvent()中也有两个Push按钮,用来进行AM/PM设置。它们的代码如下: // 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; 在AM case语句中,如果没有时间,那么我们设置时间为12 AM;如果时间是PM,那么我们从24小时格式中减去12而变为AM。 PM按钮处理和AM差不多。 在setTimeControls()中显示AM和PM,代码如下: // CH.6 Update AM CtlSetValue( amButton, (dateTime.hour < 12) ); // CH.6 Update PM CtlSetValue( pmButton, (dateTime.hour > 11) ); 在需要布尔值的地方根据其逻辑变换布尔值是个很有意思的事。 支持重复按钮(repeating buttons) Repeating按钮允许时间被增加或减少。为了使其有效,我们必须像处理ctlSelectEvent那样处理ctlRepeatEvent。在函数enterTimeHandleEvent()我们必须也要从事件句柄那里返回false,否则就不能产生repeating 事件。 // 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; 如果没有时间显示,上下箭头不做任何事情。有时间显示时,根据所选择的开关按钮(push buttons),重复按钮(repeating buttons)将增加小时、分钟的十位、分钟的个位。其中也处理了必要的循环情况。 减少按钮和增加按钮基本相似。 支持复选框 复选框的处理和其它按钮一样。当被触发时,产生一个ctlSelectEvent。 // 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; 为方便起见,如果没选复选框,我们将选中小时Push按钮,然后其它的事由函数setTimeControl()来处理。当选中复选框时将清空所有的控件内容。 结束Enter Time窗体 还有一些其它的窗口处理事件需要讨论。在enterTimeHandleEvent()中处理frmOpenEvent。 // 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; 当打开窗体时,我们用函数setTimeControls()初始化窗体。我们也保存了当前时间以便按下了cancel能够恢复。 // 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 ); Cancel按钮事件储存了老的时间,并重新返回给Contact Detail窗体. Ok按钮有些复杂。我们必须根据新时间相应的更新数据库和Contact Detail窗体。 在这里有几个重点。这个代码块表明了为什么变量hrecord在函数中是公用的。在这里由于hrecord是有效的,所有我们就用它来将新的时间值写入数据库中。 注意到在函数FrmReturnToForm()后调用的setTimeTrigger(),它用来触发Contact Details窗体的时间选择触发按钮。能够实现触发的原因是因为当执行FrmReturnToForm()后,活动窗体就变为了Contact Details窗体。这就使弹出窗体的数据能够顺利的传递到调用窗体上。 通常情况下,在调用FrmReturnToForm()后返回true,这是因为调用后老的窗体结构已经消失。然而如果返回了false,Palm OS就试图访问按钮结构以做更多的素材。由于窗口已经不在了,这样就会使程序崩溃。 调试 首先,你应该保证数据库记录能被正确的创建和修改。如果顺利的话,你所加的显示函数能够正确的显示所得结果。你的已有记录表现为no date,在界面上会显示12 AM,但不会造成危险。 下一步操作Contact Detail窗体上的日期触发按钮,看是否能按设计程序正常工作。对日期内置控件来说,这应比较容易。 对于时间控制,你需要调试Enter Time窗体。仔细的调试各个控件直到都能够可靠的工作。当窗体能够顺利运行后,你可验证数据库记录和时间选择触发按钮是否被正确的修改。 下一步 下一步,我们将向Contacts应用程序添加一个列表框窗体。然后修改代码使我们能够根据first name、last name、date 和time进行排序。 程序清单 下面是完整的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 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 ); // 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.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.5 If there are no records, create one if( numRecords == 0 ) newRecord(); // CH.4 Go to our starting page FrmGotoForm( ContactDetailForm ); // CH.2 Our event loop do { // CH.2 Get the next event EvtGetEvent( &event, -1 ); // CH.2 Handle system events if( SysHandleEvent( &event ) ) continue; // CH.3 Handle menu events if( MenuHandleEvent( NULL, &event, &error ) ) continue; // CH.4 Handle form load events if( event.eType == frmLoadEvent ) { // CH.4 Initialize our form switch( event.data.frmLoad.formID ) { // CH.4 Contact Detail form case ContactDetailForm: form = FrmInitForm( ContactDetailForm ); FrmSetEventHandler( form, contactDetailHandleEvent ); break; // CH.4 About form case AboutForm: form = FrmInitForm( AboutForm ); FrmSetEventHandler( form, aboutHandleEvent ); break; // CH.6 Enter Time form case EnterTimeForm: form = FrmInitForm( EnterTimeForm ); FrmSetEventHandler( form, enterTimeHandleEvent ); break; } FrmSetActiveForm( form ); } // CH.2 Handle form events FrmDispatchEvent( &event ); // CH.2 If it's a stop event, exit } while( event.eType != appStopEvent ); // 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.6 Date selector trigger case ContactDetailDateSelTrigger: { // CH.6 Initialize the date if necessary if( dateTime.year == NO_DATE ) { DateTimeType currentDate; // CH.6 Get the current date TimSecondsToDateTime( TimGetSeconds(), ¤tDate ); // CH.6 Copy it dateTime.year = currentDate.year; dateTime.month = currentDate.month; dateTime.day = currentDate.day; } // CH.6 Pop up the system date selection form SelectDay( selectDayByDay, &(dateTime.month), &(dateTime.day), &(dateTime.year), "Enter Date" ); // CH.6 Get the record hrecord = DmQueryRecord( contactsDB, cursor ); // CH.6 Lock it down precord = MemHandleLock( hrecord ); // CH.6 Write the date time field DmWrite( precord, DB_DATE_TIME_START, &dateTime, sizeof( DateTimeType ) ); // CH.6 Unlock the record MemHandleUnlock( hrecord ); // CH.6 Mark the record dirty isDirty = true; } break; // CH.6 Time selector trigger case ContactDetailTimeSelTrigger: { // CH.6 Pop up our selection form FrmPopupForm( EnterTimeForm ); } break; } // CH.5 Sync the current record to the fields setFields(); } break; // CH.5 Respond to field tap case fldEnterEvent: isDirty = true; break; // CH.3 Parse menu events case menuEvent: return( menuEventHandler( event ) ); break; } // CH.2 We're done return( false ); } // CH.4 Our About form event handler function static Boolean aboutHandleEvent( EventPtr event ) { FormPtr form; // CH.4 A pointer to our form structure // CH.4 Get our form pointer form = FrmGetActiveForm(); // CH.4 Respond to the Open event if( event->eType == frmOpenEvent ) { // CH.4 Draw the form FrmDrawForm( form ); } // CH.4 Return to the calling form if( event->eType == ctlSelectEvent ) { FrmReturnToForm( 0 ); // CH.4 Always return true in this case return( true ); } // CH.4 We're done return( false ); } // CH.6 Our Enter Time form event handler function static Boolean enterTimeHandleEvent( EventPtr event ) { FormPtr form; // CH.6 A form structure pointer static DateTimeType oldTime; // CH.6 The original time // CH.6 Get our form pointer form = FrmGetActiveForm(); // CH.6 Switch on the event switch( event->eType ) { // CH.6 Initialize the form case frmOpenEvent: { // CH.6 Store the time value oldTime = dateTime; // CH.6 Draw it FrmDrawForm( form ); // CH.6 Set the time controls setTimeControls(); } break; // CH.6 If a button was repeated case ctlRepeatEvent: // CH.6 If a button was pushed case ctlSelectEvent: { Word buttonID; // CH.6 The ID of the button // CH.6 Set the ID buttonID = event->data.ctlSelect.controlID; // CH.6 Switch on button ID switch( buttonID ) { // CH.6 Hours button case EnterTimeHoursPushButton: // CH.6 Minute Tens button case EnterTimeMinuteTensPushButton: // CH.6 Minute Ones button case EnterTimeMinuteOnesPushButton: { // CH.6 If no time was set if( dateTime.hour == NO_TIME ) { // CH.6 Set the time to 12 PM dateTime.hour = 12; dateTime.minute = 0; // CH.6 Set the controls setTimeControls(); } // CH.6 Clear the old selection if any if( timeSelect ) CtlSetValue( getObject( form, timeSelect ), false ); // CH.6 Set the new selection CtlSetValue( getObject( form, buttonID ), true ); timeSelect = buttonID; } break; // CH.6 Up button case EnterTimeTimeUpRepeating: { // CH.6 If there's no time, do nothing if( dateTime.hour == NO_TIME ) break; // CH.6 Based on what push button is selected switch( timeSelect ) { // CH.6 Increase hours case EnterTimeHoursPushButton: { // CH.6 Increment hours dateTime.hour++; // CH.6 If it was 11 AM, make it 12 AM if( dateTime.hour == 12 ) dateTime.hour = 0; // CH.6 If it was 11 PM, make it 12 PM if( dateTime.hour == 24 ) dateTime.hour = 12; } break; // CH.6 Increase tens of minutes case EnterTimeMinuteTensPushButton: { // CH.6 Increment minutes dateTime.minute += 10; // CH.6 If it was 5X, roll over if( dateTime.minute > 59 ) dateTime.minute -= 60; } break; // CH.6 Increase minutes case EnterTimeMinuteOnesPushButton: { // CH.6 Increment minutes dateTime.minute++; // CH.6 If it is zero, subtract ten if( (dateTime.minute % 10) == 0 ) dateTime.minute -= 10; } break; } // Revise the controls setTimeControls(); } break; // CH.6 Down button case EnterTimeTimeDownRepeating: { // CH.6 If there's no time, do nothing if( dateTime.hour == NO_TIME ) break; // CH.6 Based on what push button is selected switch( timeSelect ) { // CH.6 Decrease hours case EnterTimeHoursPushButton: { // CH.6 Decrement hours dateTime.hour--; // CH.6 If it was 12 AM, make it 11 AM if( dateTime.hour == -1 ) dateTime.hour = 11; // CH.6 If it was 12 PM, make it 11 PM if( dateTime.hour == 11 ) dateTime.hour = 23; } break; // CH.6 Decrease tens of minutes case EnterTimeMinuteTensPushButton: { // CH.6 Decrement minutes dateTime.minute -= 10; // CH.6 If it was 0X, roll over if( dateTime.minute < 0 ) dateTime.minute += 60; } break; // CH.6 Decrease minutes case EnterTimeMinuteOnesPushButton: { // CH.6 Decrement minutes dateTime.minute--; // CH.6 If it is 9, add ten if( (dateTime.minute % 10) == 9 ) dateTime.minute += 10; // CH.6 If less than zero, make it 9 if( dateTime.minute < 0 ) dateTime.minute = 9; } break; } // CH.6 Revise the controls setTimeControls(); } break; // CH.6 AM button case EnterTimeAMPushButton: { // CH.6 If no time was set if( dateTime.hour == NO_TIME ) { // CH.6 Set the time to 12 AM dateTime.hour = 0; dateTime.minute = 0; // CH.6 Set the controls setTimeControls(); } // CH.6 If it is PM if( dateTime.hour > 11 ) { // CH.6 Change to AM dateTime.hour -= 12; // CH.6 Set the controls setTimeControls(); } } break; // CH.6 PM button case EnterTimePMPushButton: { // CH.6 If no time was set if( dateTime.hour == NO_TIME ) { // CH.6 Set the time to 12 PM dateTime.hour = 12; dateTime.minute = 0; // CH.6 Set the controls setTimeControls(); } // CH.6 If it is AM if( dateTime.hour < 12 ) { // CH.6 Change to PM dateTime.hour += 12; // CH.6 Set the controls setTimeControls(); } } break; // CH.6 No Time checkbox case EnterTimeNoTimeCheckbox: { // CH.6 If we are unchecking the box if( dateTime.hour == NO_TIME ) { // CH.6 Set the time to 12 PM dateTime.hour = 12; dateTime.minute = 0; // CH.6 Set the controls setTimeControls(); // CH.6 Set the new selection timeSelect = EnterTimeHoursPushButton; CtlSetValue( getObject( form, timeSelect ), true ); } else // CH.6 If we are checking the box dateTime.hour = NO_TIME; // CH.6 Set the controls setTimeControls(); } break; // CH.6 Cancel button case EnterTimeCancelButton: { // CH.6 Restore time dateTime = oldTime; // CH.6 Return to calling form FrmReturnToForm( 0 ); } // CH.6 Always return true return( true ); // CH.6 OK button case EnterTimeOKButton: { VoidPtr precord; // CH.6 Points to the record // CH.6 Lock it down precord = MemHandleLock( hrecord ); // CH.6 Write the date time field DmWrite( precord, DB_DATE_TIME_START, &dateTime, sizeof( DateTimeType ) ); // CH.6 Unlock the record MemHandleUnlock( hrecord ); // CH.6 Mark the record dirty isDirty = true; // CH.6 Return to the Contact Details form FrmReturnToForm( 0 ); // CH.6 Update the field setTimeTrigger(); } // CH.6 Always return true return( true ); } } break; } // CH.6 We're done return( false ); } // CH.3 Handle menu events static 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.5 Create the database record and get a handle to it hrecord = DmNewRecord( contactsDB, &cursor, DB_RECORD_SIZE ); // 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 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.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.5 Unlock the record MemHandleUnlock( hrecord ); } // 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 MemHandleResize( hfield, StrLen( text ) + 1 ); } else // CH.5 Allocate a handle for the string hfield = MemHandleNew( StrLen( text ) + 1 ); // 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; } |
本文地址:http://www.45fan.com/dnjc/73856.html