第十三章 再论用户界面
在这一章和下一章中,我们将创建一个完整的计算器应用程序。从中我们将学到怎样使用公共函数库,特别是如何使用MathlLib,我们还会学到许多编程的细节及如何改善用户界面。
计算器的设计
下面开始设计计算器窗体。它将包括:
1 显示数字的区域
2 代表十个数字的按钮
3 小数点按钮
4 符号按钮
5 指数按扭
6 加、减、乘、除按钮
7 等号按钮
8 清除按钮
9 完成(“Done”)按钮,将计算结果拷贝到剪切板上并关闭计算器
计算器可以对数字进行加、减、乘、除运算,最大可到99,999,999*1099。
在这一章中,我们讨论计算器窗体的可视化设计和用户界面元素的内容,在下一章将讨论计算器的功能设计。
可视化设计
在这一部分中,我们将仔细讲述如何设计用户界面。在这一点上,我想了很多。希望我的想法能给你如何构建用户界面一个大体的概念。这些想法主要建立在第七章及
Palm OS SDK文献的基础上。
在设计用户界面时,我考虑到用户可能习惯了计算器界面上的一些东西。例如,数字的组织形式是特定的。还有,一些实际的需要也是约定成俗的,如显示区域在顶部,以免用户在按下按钮时将显示区域遮住。我们还要遵循Palm Computing的要求机制,把按钮做的足够大,以便用手指就可以操作而不要时时的求助与输入笔。基于这些考虑,我们的计算器应该成为这种模样:在显示区域下面规则的排列几行按钮。
我很高兴遵从这种传统的要求,因为这样没听到人们对此有太多的抱怨,至少在我周围的人是这样。它看起来是个成功的设计。当然,也不要太相信传统的看法,如果这种传统的要求并不能是人们满意的话。例如,当我们设计一个虚拟录像机时,我就怀疑是否将其做的像真的录像机一样,因为很多人对这种设计表示不满。
下一步应该考虑按钮间应按照什么样的顺序来排放。其中重要的一个因素就是考虑到它们的使用次数。我想权威的研究结果已经出来了吧,但我还是用我自己的想法。对于好的用户界面,我可能提出了在现实生活中最糟糕的设计模式,因为像你和我这样的开发者在模型构建上可能都比较平庸。所以在真正的设计中,我们应做一些调查和研究,来确定我们的用户到底是怎么想的。
下面是我对按钮使用次数的一些猜测:
1 数字按钮 在每次的运算过程中平均少于一次;
2 小数点按钮 或许少于1/2次;
3 符号按钮 每次运算大约使用一次;
4 指数按钮 使用次数很少;
5 等号按钮 每次一次;
6 清除按钮 差不多每次运算使用一次;
7 完成按钮 每次运算少于一次;
另一个值得考虑的是误击按钮而造成的后果。如果错误的按下了Clear或Done按钮,将造成很严重的后果,因此必须将它们放在离点击频率较高的按钮较远的地方。在我们的设计中一共有20个按钮,基于以上的考虑,我将其设计为五行四列,如图13-1所示。
7 8 9 /
4 5 6 *
1 2 3 -
+/- . +
Done C Exp =
图13-1 我的按钮设计
下一步是如何将各个按钮放置在窗体上。在放置时最应注意的是在每个按钮的四周都有一个(按钮如果为bold则有两个)象素的边框。因此,最左和最顶部都应有一个象素的空余。
我们需要一个编辑框来显示计算结果。用编辑框要比使用标签好的多。我们把编辑框属性设为无下划线(no-underline)、不能编辑、右对齐。我们还可以添加一个按钮给编辑框产生一个边框,这样它不会光秃秃的挂在屏幕上。我发现编辑框的高度为20象素是个很好的选择。
如果Palm的最大尺寸是160象素的话,有很多方法放置各个按钮。下面是我的想法。我用了高度为20个象素的编辑框,按钮高度为23,两者的宽度都是35象素。我通过计算让所有按钮的高度和宽度加起来正好是屏幕的高度和宽度,由此得到一个按钮的高度和宽度。下面是按钮宽度的计算方法:
1 + 35 + 1 + 4 + 1 + 35 + 1 + 4 + 1 + 35 + 1 + 4 + 1 + 35 + 1
1 36 37 41 42 77 78 82 83 118 119 123 124 159 160
下面是高度的计算方法:
20 + 3 + 1 + 23 + 1 + 3 + 1 + 23 + 1 + 3 + 1 + 23 + 1 + 3 + 1 + 23 + 1 + 3 + 1 + 23 + 1
20 23 24 47 48 51 52 75 76 79 80 103 104 107 108 131 132 135 136 159 160
这就意味我们将如何放置按钮:在1,42,83和124上放列,在24,52,80,108和136上放行。
创建计算器
现在我们为程序添加资源和代码。首先,你应该把我们上一章的程序作一个备份以备以后的使用。
在CD上有四个计算器工程。CH.12是第一个版本,在窗体上只有OK按钮。Calculator 2是第八章的版本,该程序有三个窗体和一个菜单栏。你在本章后看到的这个版本的名字叫Calculator CH.13。
对Calculator.rsrc内容的添加
下面将向我们的工程添加编辑框和按钮,来组成程序的用户界面。我们先添加编辑框,然后从左到右从上到下的添加按钮,步骤如下:
1. 单击Start 按钮,选择Programs | CodeWarreor Lite for Palm OS | Constructor运行构造器;
2. 编辑资源文件。选择File | Open Project File in Constructor将产生一个选择对话框,从Src文件夹中选中Calculator.rsrc;
3. 在Resouse Type and Name列表中打开calc窗体;
4. 选中OK按钮按下DELETE键将其删除。当按钮被选中时,在的边框周围有四个小黑块,并且在Layout Properties面板中显示它的属性;
5. 创建编辑框。选择Windows | Catalog,从Catalog模板中拖一个编辑框到窗体的Layout Appearance面板上;
6. 设置编辑框的属性如下:
Object Identifier=number
Left Origin=5
Top Origin=4
Width=150
Height=18
Editable=no check
Underline=no check
Left Justified=no check
其它的属性采取缺省值。你击中Layout Appearance面板后,编辑框属性在Layout Properties面板中出现,选中各个属性编辑修改;
7. 从Catalog模板中拖动按钮到窗体上;
8. 设置按钮的属性如下:
Object Identifier=fieldborder
Left Origin=1
Top Origin=1
Width=158
Height=18
把Label属性中的OK按钮去掉。其它属性采取缺省值;
9. 从Catalog模板中拖动“7”按钮到窗体上;
10.设置按钮的属性如下:
Object Identifier=seven
Left Origin=1
Top Origin=24
Width=35
Height=23
Font=Large
Label=7
其它属性采取缺省值;
11.创建“8”按钮。选中“7”按钮并按下CTRL-C拷贝。单击窗体选中自己,按下CTRL-V粘贴;
12.设置按钮的属性如下:
Object Identifier=eight
Left Origin=42
Top Origin=24
Label=8
其它的属性和“7”按钮完全一样;
13.创建“9”按钮。单击窗体并按下CTRL-V再粘贴“7”按钮,修改属性如下:
Object Identifier=nine
Left Origin=83
Top Origin=24
Label=9;
14.如创建“9”按钮那样创建其它的按钮。各个按钮属性的改变如下表所示:
Object Identifier Left Origin Top Origin Label
Device 124 24 /
Four 1 52 4
Five 42 52 5
Six 83 52 6
Multiply 124 52 *
One 1 80 1
Two 42 80 2
Three 83 80 3
Substract 124 80 -
Changsign 1 108 +/-
Point 42 108 .
Zero 83 108 0
Add 124 108 +
Done 1 136 Done
Clear 42 136 C
Exponent 83 136 Exp
Equals 124 136 =
15.所建窗体如图13-2所示。
有些人喜欢将0按钮放在2按钮的下面。这是你自己的计算器,所以怎样放置随你的便,只要你向上表中那样定义了它们的名字,那么它就能正常的工作。
我不很喜欢Done按钮这个名字。理想情况是,你的用户明确的知道是否应该将剪贴板的东西替换,或许“Clip ‘n’ Die”能准确的描述这个意思。
当你添加完所有的按钮后,一定记着要保存。
创建一个警告
从资源类型表(resource types list)上选择Alerts,并按下CTRL-K创建一个新的警告信息。将其命名为MathLibError。双击打开,修改属性:Alert Type为Error,Title 属性为Error。在message属性中可写入“This application requires the free shared library MathLib to be installed on your Palm device, please install it.”,设置完成后改警告信息如图13-3所示。
创建应用程序的图标
根据第四章的步骤为计算器创建大小图标,当完成后,保存Calculator.rsrc退出构造器。
Fcalc.c内容的添加
现在我们开始为所有新的按钮添加代码。单击Start并选中Program | Codewarrior Lite运行CodeWarrior IDE。然后选中File | Open Recent | Calculator。从工程列表中选择fcal.c双击打开。
首先将版本号修改为新的版本号。下面我将一行一行的详细的讲解新的代码,指出其中的新内容和修改。
//////////////////////////////////////////////////////////////////////////////
// fcalc.c
// Code for the "calc" form.
// Copyright (c) 1999, Robert Mykland. All rights reserved.
//////////////////////////////////////////////////////////////////////////////
//////////////
// Includes //
//////////////
#include "app.h" // The definitions for this application
#include "calc.h" // Calculator guts
在这里我们看到了一个新的头文件——calc.h。它定义了另一个模块——calc.c,calc.c处理了独立于平台之外的计算器操作。由于此头文件只应用于fcalc.c,所以在此它代替了app.h。这说明了只有fcalc.c使用了calc.c的函数。
///////////////////////
// Global Prototypes //
///////////////////////
void calcInit( void );
void calcStop( void );
Boolean calcFormEventHandler( EventPtr spEvent );
void calcDisplay( char* cpNumber );
void calcSignalError( void );
我们又添加了四个新的全局函数。函数calcInit()和函数calcStop()为fcalc.c 新建和删除资源。我们可以从app.h中看到怎样在PilotMain()中的appInit()和appStop()宏中定义的。函数calcDisplay()的作用是更新计算器显示的内容。函数calcSignalError()允许通过界面显示程序出现的错误。注意到虽然用户界面被完全封装但可以通过上面两个函数进行访问。
/////////////////////
// Local Variables //
/////////////////////
static char cComma; // The comma symbol we're using
static char cPoint; // The decimal point we're using
static Handle hNumber; // Handle to the number in our edit field
上面是模块中,定义了函数使用的几个变量。注意到它们被定义为static是因为这样模块外边的代码就不能访问它们。这就大大的减少了由于不小心将这些变量修改的次数。字符型变量cComma和cPoint分别代表千位符和小数点。用户可以在Palm 装置的系统参考中的Formats窗体中调整这些设置。下面我们将看到怎样读取这些参数和如何在用户界面中修改它们。
另一个变量是用来处理计算器显示数字内存的。在以后我们会看到它,如在FieldToy中处理hText。
函数calcInit()
//////////////////////
// Global Functions //
//////////////////////
//----------------------------------------------------------------------------
void calcInit(
//----------------------------------------------------------------------------
// Initializes this module.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
DWord dChoice;
char* cpNumber;
这是模块中第一个函数calcInit()的起始部分。它没有任何参数也没有返回值。变量dChoice用来储存选择千位符和小数点的次数,变量cpNumber用来初始化载入字段的内存。
查找和加载一个动态库
// Find MathLib
if( (SysLibFind( MathLibName, &MathLibRef ) == 0) ||
SysLibLoad( LibType, MathLibCreator, &MathLibRef ) ||
MathLibOpen( MathLibRef, MathLibVersion ) )
{
FrmAlert( MathLibErrorAlert );
hardStop( 0 );
}
计算器的核心是使用公用库MathLib来进行浮点计算。下面我们将详细讨论MathLib。首先使用SysLibFind()来查找任何一个已经安装的公用函数库,一般来说,MathLib库是很常用的函数库之一。下一步使用函数SysLibLoad()创建到库的连接。函数MathLibOpen()是由MathLib调用来进行本身的初始化。SysLibFind()和SysLibLoad()是Palm OS的自带函数,我们可以从Palm OS 参考(Reference.pdf)的System Manager中查到它们的定义。
如果MathLib由于某些原因没有找到、加载或者打开,那么我们将调用函数FrmAlert()来弹出MathLibError警告信息。然后通过调用hardStop()退出应用程序到PilotMain()。函数FrmAlert()在Form Manager(Ref1.pdf)中有定义。
初始化内存块
// Allocate our field chunk
hNumber = MemHandleNew( MAX_NUMBER_SIZE );
// Lock the memory, get the pointer
cpNumber = MemHandleLock( hNumber );
// Initialize it
StrCopy( cpNumber, "0" );
// Unlock the field's memory
MemHandleUnlock( hNumber );
这四个函数和FieldToy中的函数十分相似。我们用了三个Memory Manager函数(Ref3.pdf)来分配和初始化计算器显示字段的内存。函数MemHandleNew()分配内存,函数MemHandleLock()防止在我们使用时重新分配内存,函数StrCopy()(定义于String Manager)用来把显示内存块初始化零。最后我们使用函数MemHandleUnlock()对内存块解锁。
获取和使用系统优先权
// Handle the number format
switch( dChoice )
{
default:
cComma = ',';
cPoint = '.';
break;
case nfPeriodComma:
cComma = '.';
cPoint = ',';
break;
case nfSpaceComma:
cComma = ' ';
cPoint = ',';
break;
case nfApostrophePeriod:
cComma = '\'';
cPoint = '.';
break;
case nfApostropheComma:
cComma = '\'';
cPoint = ',';
break;
}
// We're done
return;
}
在初始化程序的最后是有关读取系统优先权的代码,它用来描述数字是怎样显示的。我们先调用了函数PreGetPreference()来获取系统的优先权,然后使用了switch语句来赋值静态变量cComma和cPoint。PreGetPreference()是系统优先函数中的一个。
函数Function calcStop():结束库MathLib
//----------------------------------------------------------------------------
void calcStop(
//----------------------------------------------------------------------------
// Cleans up stuff from this module.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
UInt uiUseCount;
// Close MathLib
MathLibClose( MathLibRef, &uiUseCount );
if( uiUseCount == 0 )
SysLibRemove( MathLibRef );
// Free the number field chunk
MemHandleFree( hNumber );
// We're done
return;
}
上面是与函数calcInit()相对应的函数calcStop()。首先,我们关闭与MathLib的连接。MathLib可以跟踪到底有几个程序在使用它,我们使用它返回的值来确定是否应该移除这个公用库。由于Palm OS是一个单任务的操作系统,所以一般情况下,调用这个函数是不会错的。函数SysLibRemove()是一个保留函数,但MathLib的创建者建议在这种情况下使用这个函数。
最后,我们使用函数MemHandleFree()(定义于么Memory Manager)来释放显示所使用的内存。
CalcFormEventHandler的修改
//----------------------------------------------------------------------------
Boolean calcFormEventHandler(
//----------------------------------------------------------------------------
// Handles events for this form.
// Returns true if it fully handled the event.
//----------------------------------------------------------------------------
EventPtr spEvent )
//----------------------------------------------------------------------------
{
// Handle the event
switch( spEvent->eType )
{
这是一个“老式”的calcFormEventHandler()函数,它和我们上一章的函数没有任何区别。我们需要完全重写了这个函数。
初始化字段
// The form is opened
case frmOpenEvent:
{
FormPtr spForm;
Word wIndex;
FieldPtr spField;
ControlPtr spButton;
char caPoint[2];
// Get the field
spForm = FrmGetActiveForm();
wIndex = FrmGetObjectIndex( spForm, calcNumberField );
spField = FrmGetObjectPtr( spForm, wIndex );
// Draw the field
FldSetTextHandle( spField, hNumber );
FldDrawField( spField );
在窗体被加载后,事件frmOpenEvent就立即发生。当我们获知窗体被加载后就使用这个事件来初始化窗体。首先,我们使用三个函数FrmGetActiveForm()、FrmGetObjectIndex()、FrmGetObjectPtr()来获取字段的指针。然后我们使用Field Manager中的函数FldSetTextHandle()和FldDrawField()来设置文本句柄(text handle)并将其显示在编辑框里。注意,即使我们在使用同一个内存块,每一次显示一个字段也必须调用FldSetTextHandle(),这是因为在这个函数中Palm OS都要重新计算大小和设置字段文本。
例如,如果我们输入了123,但是中间没有调用函数FldSetTextHandle(),在初始值0的地方只会出现数字1。这是因为如果不调用FldSetTextHandle()或类似的函数来改变其字段长度,它就会沿袭初始字段长度只有一。
改变按钮文本
// Get the decimal point button
wIndex = FrmGetObjectIndex( spForm, calcPointButton );
spButton = FrmGetObjectPtr( spForm, wIndex );
// Draw the decimal point button
caPoint[0] = cPoint;
caPoint[1] = '\0';
CtlSetLabel( spButton, caPoint );
}
break;
由于Palm OS允许用户改变数字的外形,在理想的情况下,我们必须强制界面来反映这种变化。我们使用一个其它的扩展字符来代表小数点按钮上的符号。
首先我们调用函数FrmGetObjectIndex()和函数FrmGetObjectPtr()(定义于Form Manager,Ref1.pdf)获得按钮结构的指针。为小数点创建一个字符,然后调用函数CtlSetLabel()来改变标签内容并重绘按钮(Control Manager,Ref1.pdf)。
处理窗体按钮事件
// A control was selected
case ctlSelectEvent:
{
// Handle the different buttons
switch( spEvent->data.ctlSelect.controlID )
{
case calcAddButton:
calcAdd();
break;
case calcChangesignButton:
calcChangeSign();
break;
case calcClearButton:
calcClear();
break;
case calcDivideButton:
calcDivide();
break;
case calcDoneButton:
{
FormPtr spForm;
Word wIndex;
FieldPtr spField;
// Get the field
spForm = FrmGetActiveForm();
wIndex = FrmGetObjectIndex( spForm, calcNumberField );
spField = FrmGetObjectPtr( spForm, wIndex );
// Select all
FldSetSelection( spField, 0,
FldGetTextLength( spField ) );
// Copy to clipboard
FldCopy( spField );
// Exit the application
ErrThrow( 0 );
}
break;
case calcEightButton:
calcAppend( 8 );
break;
case calcEqualsButton:
calcEquals();
break;
case calcExponentButton:
calcExponent();
break;
case calcFieldborderButton:
// Ignore this button
return( true );
case calcFiveButton:
calcAppend( 5 );
break;
case calcFourButton:
calcAppend( 4 );
break;
case calcMultiplyButton:
calcMultiply();
break;
case calcNineButton:
calcAppend( 9 );
break;
case calcOneButton:
calcAppend( 1 );
break;
case calcPointButton:
calcPoint();
break;
case calcSevenButton:
calcAppend( 7 );
break;
case calcSixButton:
calcAppend( 6 );
break;
case calcSubtractButton:
calcSubtract();
break;
case calcThreeButton:
calcAppend( 3 );
break;
case calcTwoButton:
calcAppend( 2 );
break;
case calcZeroButton:
calcAppend( 0 );
break;
我们可以通过事件结构(event structure)的变量controlID来获知到底按下了哪一个按钮。事件结构定义在头文件Event.h中。在Calculator_res.h中,构造器中定义了ID号。为响应各个按钮事件,我们调用了一系列函数。这些函数定义在模块calc.c中。按钮calcFieldborderButton事件中没有任何函数,因为我们有意的忽略它。通过这种方法,系统只会发出beep声却不会闪烁。如果想消除beep声,可以向main.c中添加代码,在调用FrmDispatchEvent()之前将其筛除。
将结果保留在剪贴板上
case calcDoneButton:
{
FormPtr spForm;
Word wIndex;
FieldPtr spField;
// Get the field
spForm = FrmGetActiveForm();
wIndex = FrmGetObjectIndex( spForm, calcNumberField );
spField = FrmGetObjectPtr( spForm, wIndex );
// Select all
FldSetSelection( spField, 0,
FldGetTextLength( spField ) );
// Copy to clipboard
FldCopy( spField );
// Exit the application
SoftStop( 0 );
}
break;
当按下Done按钮时,程序退出并将结果保存在剪贴板上。首先象以前一样获取字段的指针,调用函数FldSetSelection()和函数FldGetTextLength()获取字段的全部内容,然后调用另一个Field Manager 函数FldCopy()将内容拷贝到剪贴板上。最后我们用函数softStop()结束事件队列并退出程序。
// A menu item was selected
case menuEvent:
// Handle the menu event
calcFormMenuEventHandler( spEvent );
return( true );
}
// We're done
return( false );
}
Menu事件的处理和以前一样。这样就完成了calFormEventHandle()的新版本。
函数calDisplay()
//----------------------------------------------------------------------------
void calcDisplay(
//----------------------------------------------------------------------------
// Displays the number in the field.
// IMPORTANT: A leading + or - sign is assumed for all numbers passed to
// this function!
//----------------------------------------------------------------------------
char* cpNumber )
//----------------------------------------------------------------------------
{
char* cpSrc;
int iIntSize;
char* cpDest;
char caNumber[40];
char* cpText;
FormPtr spForm;
Word wIndex;
FieldPtr spField;
这是calcDisplay()的开始部分。函数可以和传统C一样显示诸如+123456.78e-4之类的浮点数,并且能将千位符和小数点放在相应的位置上。在Palm OS String Manager函数中,有一个函数StrLocalizeNumber()可以排列千位符和小数点的位置,但是它需要数字中已经有了千位符在里面。既然还需要输入千位符,不如我们重新做一下让显示功能更加强大。
函数calcDisplay()有两部分。第一部分是重新排列我们想显示的数字;第二部分是真正的将数字显示在屏幕上。
向浮点数中添加千位符
函数calcDisplay()的第一部分是重新调整我们需要的数据,添加千位符和修改其他的一些细节。
// Find the end of the number and determine exponent and decimal
wIntSize = -1; // To account for leading sign
for( cpSrc = cpNumber; *cpSrc != '\0'; cpSrc++ )
{
if( (*cpSrc == '.') || (*cpSrc == 'e') )
break;
wIntSize++;
}
第一个循环计算有多少个数字,以便确定到底要添加多少个千位符、添加到什么地方。注意,在这个函数中,假定每个数据都有+和-符号。
// Start source and destination
cpSrc = cpNumber;
cpDest = caNumber;
// Handle the leading sign
if( *cpSrc++ == '-' )
*cpDest++ = '-';
这里我们定义了源指针和目标指针,将拷贝旧的字符串到新的里面。下一步判断符号如果为负,我们就进行拷贝。
// Copy the integer part
while( iIntSize )
{
do
{
*cpDest++ = *cpSrc++;
iIntSize--;
} while( (iIntSize % 3) != 0 );
if( iIntSize == 0 )
break;
*cpDest++ = cComma;
}
在这一段代码中,我们对数字个数减一,在每一组三个数字的前面加一个千位符。如果数字个数减到零,那么我们在添加下一个千位符之前跳出循环。
// Do the decimal point
if( *cpSrc == '.' )
{
cpSrc++;
*cpDest++ = cPoint;
}
// Copy the decimal part
while( *cpSrc && (*cpSrc != 'e') )
*cpDest++ = *cpSrc++;
这一部分的作用是先将小数点拷贝到“.”的位置上。然后,将剩下的数字直接拷到新的字符串里面。
// Build up the exponent if any
if( *cpSrc == 'e' )
{
*cpDest++ = ' ';
while( *cpSrc )
*cpDest++ = *cpSrc++;
}
// Zero delimit the string
*cpDest = '\0';
下面,在添加一个空格后将指数符拷入。 最后将字符串调整定界。这就完成了函数calcDisplay()第一部分的编写。
显示结果
// Change the string in the handle
// Lock the memory, get the pointer
cpText = MemHandleLock( hNumber );
// Initialize it
StrCopy( cpText, caNumber );
// Unlock the field's memory
MemHandleUnlock( hNumber );
// Get the field pointer
spForm = FrmGetActiveForm();
wIndex = FrmGetObjectIndex( spForm, calcNumberField );
spField = FrmGetObjectPtr( spForm, wIndex );
// Refresh the field
FldSetTextHandle( spField, hNumber );
FldDrawField( spField );
// We're done
return;
}
这里将新字段拷入正在使用字段的内存块中。然后重新获得字段指针并刷新,这很象我们在frmOpenEvent中做的那样。
函数calcSignalError()
//----------------------------------------------------------------------------
void calcSignalError(
//----------------------------------------------------------------------------
// Signals an error.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
SndPlaySystemSound( sndError );
return;
}
为显示错误信息,我们调用了函数SndPlaySystemSound()来发出警告声音。此函数在Ref2.pdf中有定义。
Fcalc.h文件内容的添加
由于我们向fcalc.c文件添加了全局函数,为使它们可以被通用,必须在头文件fcalc.h中声明。下面是修改后的fcalc.h文件的内容:
#ifndef FCALC_H
#define FCALC_H
//////////////////////////////////////////////////////////////////////////////
// fcalc.h
// Definitions for the "calc" form.
// Copyright (c) 1999, Robert Mykland. All rights reserved.
//////////////////////////////////////////////////////////////////////////////
///////////////////////
// Global Prototypes //
///////////////////////
void calcInit( void );
void calcStop( void );
Boolean calcFormEventHandler( EventPtr spEvent );
void calcDisplay( char* cpNumber );
void calcSignalError( void );
#endif // FCALC_H
和通常一样,这个文件包含了fcalc.c中全局函数的原型。
Calc.h模块
在下一章中我们将添加计算器运行的一般代码。在这里,创建一个头文件来定义这些函数。在这个头文件中,即使在程序不完整的情况下,也可以进行编译连接和调试。它使用了宏来代替一般意义上的函数。定义这些函数的另一个方法是建立一个新模块和函数,但在其中只添加一些测试代码(就象对calcDisplay()的调用一样)。
#ifndef CALC_H
#define CALC_H
//////////////////////////////////////////////////////////////////////////////
// calc.h
// Definitions for the generic calculation routines.
// Copyright (c) 1999, Robert Mykland. All rights reserved.
//////////////////////////////////////////////////////////////////////////////
///////////////////////
// Global Prototypes //
///////////////////////
void calcAdd( void ); // Queue an add operation
void calcAppend( int ); // Append a digit
void calcChangeSign( void ); // Change the sign of the entry
void calcClear( void ); // Clear/reset the calculator
void calcDivide( void ); // Queue a divide operation
void calcEquals( void ); // Finish the current operation
void calcExponent( void ); // Start gathering the exponent
void calcMultiply( void ); // Queue a multiply operation
void calcPoint( void ); // Start gathering the fraction
void calcSubtract( void ); // Queue a subtraction operation
//////////////////////
// Global Constants //
//////////////////////
#define MAX_NUMBER_SIZE 40
#endif // CALC_H
calc.c的函数在这里都给出了定义,它包括了为测试calcDisplay()需要的一系列函数定义。
app.h内容的添加
//////////////
// Includes //
//////////////
#include <Pilot.h> // All the Palm includes
#include "MathLib.h" // Definitions for MathLib
#include "Calculator_res.h" // Resource definitions
#include "fabout.h" // Definitions for the "about" form
#include "fcalc.h" // Definitions for the "calc" form
#include "fprefs.h" // Definitions for the "prefs" form
#include "main.h" // Definitions for the main entry point
#include "moptions.h" // Definitions for the "options" menu
这里我们又添加了库函数MathLib.h,它包含了MathLib函数的定义。
// This defines the macro that initializes the app
#define appInit() \
{\
calcInit();\
}
// This defines the macro that cleans up the app
#define appStop() \
{\
calcStop();\
}
这里我们向appInit()和appStop()以前的空定义中添加了两个宏。如果模块需要初始化或扫尾都应向这些宏中添加函数调用。
安装MathLib
Mathlib是CD上一个自解压的可执行文件(MathLib.exe)。双击图标将其解压到一个目录下,并按下面步骤进行操作:
在解压的文件夹中,我们会发现我们使用MathLib所需的任何东西,包括HTML文档。将MathLib.c和MathLib.h拷到Calculatot工程的Src文件夹中。添加MathLib.c到工程里使之与库在一起,并确定它是一个基本的输入库。
我们还要为Palm装置安装MathLib.prc文件。用Palm Install Tool和HotSync可以完成,打开App | Info观察条目列表以确定传送是否成功。
当第一次测试程序时,为测试MathLibError警告信息是否工作,可以先不向Palm安装MathLib。
调试
下载并运行程序。按下不同的按钮测试函数calDisplay(),检查Done按钮能否顺利退出程序并且将当前的数字保存在剪贴板上。通过system preference改变千位符和小数点的值看它们是否可以从界面上反映出来。最后,看能否产生about窗体和prefs窗体。
下一步做什么
下一步我们将继续计算器程序的其它部分,学习怎样向Palm中添加一般的C代码。
程序列表
下面是fcalc.c的完整代码:
//////////////////////////////////////////////////////////////////////////////
// fcalc.c
// Code for the "calc" form.
// Copyright (c) 1999, Robert Mykland. All rights reserved.
//////////////////////////////////////////////////////////////////////////////
//////////////
// Includes //
//////////////
#include "app.h" // The definitions for this application
#include "calc.h" // Calculator guts
///////////////////////
// Global Prototypes //
///////////////////////
void calcInit( void );
void calcStop( void );
Boolean calcFormEventHandler( EventPtr spEvent );
void calcDisplay( char* cpNumber );
void calcSignalError( void );
/////////////////////
// Local Variables //
/////////////////////
static char cComma; // The comma symbol we're using
static char cPoint; // The decimal point we're using
static Handle hNumber; // Handle to the number in our edit field
//////////////////////
// Global Functions //
//////////////////////
//----------------------------------------------------------------------------
void calcInit(
//----------------------------------------------------------------------------
// Initializes this module.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
DWord dChoice;
char* cpNumber;
// Find MathLib
if( (SysLibFind( MathLibName, &MathLibRef ) == 0) ||
SysLibLoad( LibType, MathLibCreator, &MathLibRef ) ||
MathLibOpen( MathLibRef, MathLibVersion ) )
{
FrmAlert( MathLibErrorAlert );
sendStopEvent();
}
// Allocate our field chunk
hNumber = MemHandleNew( MAX_NUMBER_SIZE );
// Lock the memory, get the pointer
cpNumber = MemHandleLock( hNumber );
// Initialize it
StrCopy( cpNumber, "0" );
// Unlock the field's memory
MemHandleUnlock( hNumber );
// Get the number format
dChoice = PrefGetPreference( prefNumberFormat );
// Handle the number format
switch( dChoice )
{
default:
cComma = ',';
cPoint = '.';
break;
case nfPeriodComma:
cComma = '.';
cPoint = ',';
break;
case nfSpaceComma:
cComma = ' ';
cPoint = ',';
break;
case nfApostrophePeriod:
cComma = '\'';
cPoint = '.';
break;
case nfApostropheComma:
cComma = '\'';
cPoint = ',';
break;
}
// We're done
return;
}
//----------------------------------------------------------------------------
void calcStop(
//----------------------------------------------------------------------------
// Cleans up stuff from this module.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
UInt uiUseCount;
// Close MathLib
MathLibClose( MathLibRef, &uiUseCount );
if( uiUseCount == 0 )
SysLibRemove( MathLibRef );
// Free the number field chunk
MemHandleFree( hNumber );
// We're done
return;
}
//----------------------------------------------------------------------------
Boolean calcFormEventHandler(
//----------------------------------------------------------------------------
// Handles events for this form.
// Returns true if it fully handled the event.
//----------------------------------------------------------------------------
EventPtr spEvent )
//----------------------------------------------------------------------------
{
// Handle the event
switch( spEvent->eType )
{
// The form is opened
case frmOpenEvent:
{
FormPtr spForm;
Word wIndex;
FieldPtr spField;
ControlPtr spButton;
char caPoint[2];
// Get the field
spForm = FrmGetActiveForm();
wIndex = FrmGetObjectIndex( spForm, calcNumberField );
spField = FrmGetObjectPtr( spForm, wIndex );
// Draw the field
FldSetTextHandle( spField, hNumber );
FldDrawField( spField );
// Get the decimal point button
wIndex = FrmGetObjectIndex( spForm, calcPointButton );
spButton = FrmGetObjectPtr( spForm, wIndex );
// Draw the decimal point button
caPoint[0] = cPoint;
caPoint[1] = '\0';
CtlSetLabel( spButton, caPoint );
}
break;
// A control was selected
case ctlSelectEvent:
{
// Handle the different buttons
switch( spEvent->data.ctlSelect.controlID )
{
case calcAddButton:
calcAdd();
break;
case calcChangesignButton:
calcChangeSign();
break;
case calcClearButton:
calcClear();
break;
case calcDivideButton:
calcDivide();
break;
case calcDoneButton:
{
char* cpNumber;
// Lock the memory, get the pointer
cpNumber = MemHandleLock( hNumber );
// Copy to the clipboard
ClipboardAddItem( clipboardText, cpNumber,
StrLen( cpNumber ) );
// Unlock the field's memory
MemHandleUnlock( hNumber );
// Exit the application
sendStopEvent();
}
break;
case calcEightButton:
calcAppend( 8 );
break;
case calcEqualsButton:
calcEquals();
break;
case calcExponentButton:
calcExponent();
break;
case calcFieldborderButton:
// Ignore this button
return( true );
case calcFiveButton:
calcAppend( 5 );
break;
case calcFourButton:
calcAppend( 4 );
break;
case calcMultiplyButton:
calcMultiply();
break;
case calcNineButton:
calcAppend( 9 );
break;
case calcOneButton:
calcAppend( 1 );
break;
case calcPointButton:
calcPoint();
break;
case calcSevenButton:
calcAppend( 7 );
break;
case calcSixButton:
calcAppend( 6 );
break;
case calcSubtractButton:
calcSubtract();
break;
case calcThreeButton:
calcAppend( 3 );
break;
case calcTwoButton:
calcAppend( 2 );
break;
case calcZeroButton:
calcAppend( 0 );
break;
}
}
break;
// A menu item was selected
case menuEvent:
// Handle the menu event
calcFormMenuEventHandler( spEvent );
return( true );
}
// We're done
return( false );
}
//----------------------------------------------------------------------------
void calcDisplay(
//----------------------------------------------------------------------------
// Displays the number in the field.
// IMPORTANT: A leading + or - sign is assumed for all numbers passed to
// this function!
//----------------------------------------------------------------------------
char* cpNumber )
//----------------------------------------------------------------------------
{
char* cpSrc;
int iIntSize;
char* cpDest;
char caNumber[40];
char* cpText;
FormPtr spForm;
Word wIndex;
FieldPtr spField;
// Find the end of the number and determine exponent and decimal
iIntSize = -1; // To account for leading sign
for( cpSrc = cpNumber; *cpSrc != '\0'; cpSrc++ )
{
if( (*cpSrc == '.') || (*cpSrc == 'e') )
break;
iIntSize++;
}
// Start source and destination
cpSrc = cpNumber;
cpDest = caNumber;
// Handle the leading sign
if( *cpSrc++ == '-' )
*cpDest++ = '-';
// Copy the integer part
while( iIntSize )
{
do
{
*cpDest++ = *cpSrc++;
iIntSize--;
} while( (iIntSize % 3) != 0 );
if( iIntSize == 0 )
break;
*cpDest++ = cComma;
}
// Do the decimal point
if( *cpSrc == '.' )
{
cpSrc++;
*cpDest++ = cPoint;
}
// Copy the decimal part
while( *cpSrc && (*cpSrc != 'e') )
*cpDest++ = *cpSrc++;
// Build up the exponent if any
if( *cpSrc == 'e' )
{
*cpDest++ = ' ';
while( *cpSrc )
*cpDest++ = *cpSrc++;
}
// Zero delimit the string
*cpDest = '\0';
// Change the string in the handle
// Lock the memory, get the pointer
cpText = MemHandleLock( hNumber );
// Initialize it
StrCopy( cpText, caNumber );
// Unlock the field's memory
MemHandleUnlock( hNumber );
// Get the field pointer
spForm = FrmGetActiveForm();
wIndex = FrmGetObjectIndex( spForm, calcNumberField );
spField = FrmGetObjectPtr( spForm, wIndex );
// Refresh the field
FldSetTextHandle( spField, hNumber );
FldDrawField( spField );
// We're done
return;
}
//----------------------------------------------------------------------------
void calcSignalError(
//----------------------------------------------------------------------------
// Signals an error.
//----------------------------------------------------------------------------
void )
//----------------------------------------------------------------------------
{
SndPlaySystemSound( sndError );
return;
}
下面是app.h的完整代码:
#ifndef APP_H
#define APP_H
//////////////////////////////////////////////////////////////////////////////
// app.h
// Definitions for the application.
// Copyright (c) 1999, Robert Mykland. All rights reserved.
//////////////////////////////////////////////////////////////////////////////
//////////////
// Includes //
//////////////
#include <Pilot.h> // All the Palm includes
#include "MathLib.h" // Definitions for MathLib
#include "Calculator_res.h" // Resource definitions
#include "fabout.h" // Definitions for the "about" form
#include "fcalc.h" // Definitions for the "calc" form
#include "fprefs.h" // Definitions for the "prefs" form
#include "main.h" // Definitions for the main entry point
#include "moptions.h" // Definitions for the "options" menu
////////////////////////////
// Definitions for calc.c //
////////////////////////////
#define atoi StrAToI
#define itoa StrIToA
#define strcat StrCat
#define strcpy StrCopy
#define strlen StrLen
#define strncpy StrNCopy
//////////////////////////////
// Definitions for fabout.c //
//////////////////////////////
// The menu event handler macro for the "about" form
#define aboutFormMenuEventHandler(spEvent)
/////////////////////////////
// Definitions for fcalc.c //
/////////////////////////////
// The menu event handler macro for the "calc" form
#define calcFormMenuEventHandler(spEvent) \
{\
optionsMenuEventHandler( spEvent );\
}
//////////////////////////////
// Definitions for fprefs.c //
//////////////////////////////
// The menu event handler macro for the "prefs" form
#define prefsFormMenuEventHandler(spEvent)
////////////////////////////
// Definitions for main.c //
////////////////////////////
// The prototype for getEventHandler()
static FormEventHandlerPtr getEventHandler( Word );
// This creates the function getEventHandler,
// which returns the event handler for a given form
// in this application.
#define EVENT_HANDLER_LIST \
static FormEventHandlerPtr getEventHandler( Word wFormID )\
{\
switch( wFormID )\
{\
case AboutForm:\
return( aboutFormEventHandler );\
case CalcForm:\
return( calcFormEventHandler );\
case PrefsForm:\
return( prefsFormEventHandler );\
}\
return( NULL );\
}
// This defines the macro that initializes the app
#define appInit() \
{\
calcInit();\
}
// This defines the macro that cleans up the app
#define appStop() \
{\
calcStop();\
}
// This application works on PalmOS 2.0 and above
#define ROM_VERSION_MIN ROM_VERSION_2
// Define the starting form
#define StartForm CalcForm
////////////////////////////////
// Definitions for moptions.c //
////////////////////////////////
// Menu ID name conversions
#define OptionsAbout OptionsAboutCalculator
#endif // APP_H