您的位置: 嵌入式在线 > 技术中心 > Palm OS > PalmOS开发教程-5.2

PalmOS开发教程-5.2

2008-01-03      嵌入式在线      收藏 | 打印

       添加数据库

       现在开始添加一个数据库。首先,利用资源构造器向Contact Detail窗体添加一些按钮用来浏览数据库记录;再添加一个帮助信息和新的警告;然后添加可创建和修改数据库的程序代码。

       数据库技术和术语

       数据库有很多种类型。也就存在与它们相关联的容易混淆的术语。我将在这一部分探讨一下基本术语并解释我们将要接触的其它术语。

       数据库中最基本的单元叫记录,有的地方也叫“行”(raw)。一个记录通常有一些数据组成:例如,一个人的姓名、地址和电话号码等。每个数据可叫字段,也被称为“表元”或“列”。通常“列”是指在所有记录中提供相似信息的数据,例如:数据库中所有的“姓”。

       你可以把数据库看成是由行和列组成的信息表。每一行代表一个单独的条目。每一行代表和所有条目相关联的一种特殊类型的数据集合。例如:你可以用数据库的每一行代表一个人。在这种情况下,列可能为所有人的姓和名。如果行代表约会,那么列可代表约会时间、约会时间等。

       一般情况下,你一次只能查看数据库的一条记录。正在被你查看的行一般叫“游标”(cursor)。Palm OS称之为“索引”(index)。在数据库中一行行的移动被叫做“浏览”(navigation)。

       数据库一般分为平面(flat-file)数据库和关系数据库两种。平面数据库是由一个单独的表组成。Palm OS就使用这种简单的数据库类型。关系数据库是由许多不同的表组成,并且它们之间可通过不同的方式相联系。现在绝大部分的数据库为关系数据库。在一些Palm OS程序中,你可以通过建立一些平面数据库,让它们像关系数据库一样的工作。

       数据库可根据你提供的限制查询语句为你提供它的一个子集。这就像你问数据库一个问题然后数据库给你想要的答案一样。这种方式在数据库中叫做“查询”(query)。通过查询得到的行叫结果集(result set)或答案集(solution set)。

       Palm OS中的数据库要比一般的平面数据库要灵活的多,因为它的记录就是内存中的存储块。你可根据自己的需要任意解释它们。结果,你就有了这样一个数据库:它里面的记录有着不同的格式和长度。

       Contacts.rsrc文件内容的添加

       现在我们就通过编制程序来看看数据库如何工作。首先,为Contact Detail窗体创建添加(add)和删除(delete)记录以及浏览(navigate)数据库的按钮:

       1. 打开资源构造器; 
       2. 打开Contacts工程中src文件夹中的Contacts.rsrc文件; 
       3. 选中Contact Detail窗体双击打开; 
       4. 选中Window | Catalog生成Catalog窗口; 
       5. 拖动三个标签到Contact Detail窗体上。根据下表设置它们的属性:

       Left Origin Top Origin Text 
       20 15 First Name 
       21 30 Last Name 
       2 45 Phone Number

       注意:我产生Left Origin Numbers的方法是:首先按下shift同时单击左键,然后从构造器的菜单上再选择Arrange | Align Right Edge。

       6.拖动至少两个以上的输入框到Contact Detail窗体上。根据下表设置它们的属性:

       Object Identifier LeftOrigin TopOrigin Width MaxCharacters Auto Shift
FirstName 80 15 79 15 Yes
LastName 80 30 79 15 Yes
PhoneNumer 80 45 79 15 Yes

       7.拖动六个按钮到Contact Detail窗体上。根据下表设置它们的属性:
ObjectIdentifier LeftOrigin TopOrigin Width MaxCharacters
First 1 130 28 First
Prev 45 130 28 Prev
Next 88 130 28 Next
Last 131 130 28 Last
Delete 103 146 36 Delete
New 53 146 36 New

       注意:我利用了这样的小技巧使按钮水平对齐:按下shift,同时选中First 、Prev、Next和Last按钮,然后在菜单上选择Arrange | Spread Horizontally。

       当你按上述步骤做完后,得到的Contact Detail窗体应如图5-1所示。
图5-1:添加了所有元素后的Contact Detail 窗体

       创建警告信息
当你的Palm OS版本低于2.0(Pilot 1000,Pilot 5000)时,就需要添加警告信息。步骤如下: 
       1. 选中Alert资源,并按下CTRL-K创建一个新的警告信息; 
       2. 单击其名称,将其改为LowROMVersionError; 
       3. 双击打开新的警告信息; 
       4. 将属性Alert Type置为Error; 
       5. 将属性Title置为Fatal Error; 
       6. 将属性Message置为“The version of Palm device you have can’t run this software. Please upgrade you Palm device.”你的警告信息应如图5-2所示。

       当新的数据库不能创建时,你也应添加一个警告信息来处理。步骤如下: 
       1. 选中Alert资源,并按下CTRL-K创建一个新的警告信息; 
       2. 单击其名称,将其改为DBCreationError; 
       3. 双击打开新的警告信息; 
       4. 将属性Alert Type置为Error; 
       5. 将属性Title置为Fatal Error; 
       6. 将属性Message置为“The Contacts database could not be created. Please free up some memory.”你的警告信息应如图5-2所示;
图5-2:”低版本的ROM错误”警告框 
       7. 关闭并保存Contacts.rsrc文件。 
       Contacts. c文件内容的添加
我将逐步的详细讲解Contacts.c文件增加的内容和改变的内容,在这一部分的最后将有程序的一个详细列表。
首先我们应对在PilotMain()中使用的ROM版本号进行定义。加入的新代码将保证Palm OS运行的更加稳定。

// CH.4 Constants for ROM revision
#define ROM_VERSION_2 0x02003000
#define ROM_VERSION_MIN ROM_VERSION_2

       注意: 有关ROM_VERSION_2使用的数字是从Palm OS SDK Reference(CodeWarrior Docoumentation文件夹里的Reference.pdf)中找到的,此信息在1012页。本书的碰到的所有问题几乎都可以拿这些文档来做参考。

       在文件的头部定义了六个新的实用函数原型。NewRecord()函数为数据库产生一个新的记录;getRecord()函数连接函数FrmGetObjectIndex()和函数FrmGetObjectPtr();函数 setFields()将数据库的内容复制到上三个文本框中去。函数getFields()将窗体上文本框的内容填充到数据库中去。函数setText()设置文本框内容;函数文本框从文本框中获取文本并写入一被锁定的(locked)数据库中。我们将先浏览一下这些函数,然后把它们完全掌握。

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

       数据库中将使用五个新的变量。当数据库一旦打开,变量contactsDB允许我们可对其进行操作;变量numRecords定义了数据库中当前记录数;变量cursor代表Contact Detail窗体上显示的记录;变量isDirty定义当前记录是否已被修改,这就允许我们不需标识没有改变的记录而保持同步;变量hrecord表示当前记录的句柄。

// CH.5 Our open database reference
static DmOpenRef contactsDB;
static ULong numRecords;
static UInt cursor;
static Boolean isDirty;
static VoidHand hrecord;

       我们的数据库的记录长度是固定的(但是在Palm OS中并不一定非要这样)。为使记录能准确的保存,每条记录的开始点都应被仔细的定义。容易起见,大量的常量被定义:

// 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)

       如上所示,数据库中每一条记录的开始点和长度都已定义。顺便说一下,记录长度是为整条记录而定义的。

       在以前的章节中我们在PilotMain()的头部定义了内存块,而现在我们创建并初始化了一个数据库。在编写正式代码前,首先让我们检查Palm装置的操作系统版本。

       由于一些数据库函数特别是排序(sorting)函数自从Palm OS 1.0后有了改变,所以我们需要先加入一些代码以保证程序工作在Palm OS2.0或更高版本上。

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

       深入DmCreateDatabase()函数所带的两个参数具有其特殊的意义。它们把你的应用程序的数据库同其它应用程序区分开来,以防止你对它们进行意外的访问或修改。主ID号是第一个‘RPGU’,称为Creator ID。它表示创建数据库的一个或多个应用程序。第二个ID号你可以任意选取。我选取了‘ctct’代表‘Contacts’。请注意常量两边的“单引号”。这对于Mac程序员应该不会陌生,但对Windows编程人员来说不是很熟悉。Mac资源一般采用四个字符的常量,Palm OS继承了这一点。这四个字符将由编译器转替换成一个32比特的数字。为了保证你所用的Creator ID的唯一性,你必须到Palming Computing的网站http://www.palm.com/devzone/crid/cridsub.html去注册Creator ID。我已经为本书所用的例子注册了Creator ID‘PPGU’,你可以放心的使用它。

       这个函数将调用刚创建的或已存在的数据库。由于已经保证了数据库的存在,所以程序定能正常运行。为在以后调用此数据库,我们把数据库保存在contactsDB中。

// 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();

       如果访问不存在的记录将导致Palm OS的崩溃,因此你必须清楚各条记录并保证在程序中访问的必须是合法的记录。你可以按上述代码去做。首先变量numRecords被初始化。它给出了你可以编址记录的范围。一般情况下,数据库的第一条记录号是零,所以我们初始cursor为零保证在即使数据库中只有一条记录时,也可以访问到一条合法记录。如果数据库中没有记录,我们将调用函数newRecord()创建一条记录。

// CH.5 Close all open forms
FrmCloseAllForms();

// CH.5 Close the database
DmCloseDatabase( contactsDB );

       在程序结束时,所有记录应释放内存并关闭数据库。为释放最后一条记录,我们再次调用Contact Detail 事件句柄函数中的frmCloseEvent。无论什么时候改变窗体,我们通常都要调用这个事件。为保证能关闭所有打开的窗体,在程序末尾调用FrmCloseAllForms()是个不错的办法。

       调用此函数后,我们就可保证所有记录已被关闭。然后调用函数DmCloseDatabase()关闭数据库。


// 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.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;
}

       函数newRecord()

       现在我们结束事件句柄的讨论,接着我们开始分析刚添加的实用函数。函数newRecord()将在由变量cursor定义的数据库的当前索引位置添加一条新的记录。

       函数开始时调用了DmNewRecord()创建一条新的记录。但是这个记录里只是些垃圾信息。所以我们必须初始化记录,用一空白记录来代替它。向第二章定位数据块一样,我们先锁住记录,然后添加初始化信息。在此例中,我们初始化记录信息全部为零。请注意,在写零时,我们使用了函数DmSet(),而不是直接写零或调用StrCopy()。这是因为数据库所在的存储块被特殊保护免于讹误,而只能利用函数DmSet()才能将其内容改变。如果想写入其它的非零字符需调用函数DmWrite()。

       对于DmNewRecord()的返回值应检查其是否为零,虽然在上例我们没有检查。如果返回值为零则表明记录已溢出内存。我将在第七章修正此疏忽,在那一章将全面介绍致命错误解决策略。

       和前面的frmCloseEvent一样,我们调用DmReleaseRecord()来释放DmGetRecord()和DmQueryRecord()结果记录占用的内存。因为增添了一条新记录,变量numRecords加一。IsDirty位置“真”是为了在下面的attachFields()函数中可以立即访问数据。

       函数getObject()

       函数getObject()可通过关联FrmGetObjectIndex()和FrmGetObjectPtr()为程序节省大量的时间。由于我们经常用到字段指针及其它控件,此函数可使我们现在和将来的代码更加简洁,因此更具可读性。

// 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 ) );
}

       字段和数据库记录

       大多数的计算机系统都有一个RAM和硬盘。RAM用来保存临时数据,硬盘用来永久的保存数据。由于RAM的读写速度要比硬盘快的多,因此程序员做的很多工作是从硬盘上读取数据或写入新的及修改硬盘上的数据。

       因为Palm中的数据都是在内存中,所以访问速度很快,把访问时间缩减到了最小。从理论上讲,在Palm内存中的任何数据被读取的速度时一样的,因此完全没有必要像其它计算机一样,把数据再移动到其它特别的可更快访问的地方。

       在PC上,我们访问数据库时,首先在RAM中为一个新记录分配内存,然后再将其写入到硬盘的数据库中。而在Palm中此过程将变的更快,因为在Palm中的永久性存储器为记录分配内存,然后可直接的向存储器中写入。Palm OS通过调用函数FldSetText()使过程可视化,即可使你在一个编辑区域直接和一个数据库记录联系。这个区域就叫做编辑域(Edit in place)。不幸的是,最近的Palm OS版本中,编辑域有两个主要的缺陷。一个是每一次对于每一条记录只能访问它的一个字段。如果你访问的多于一个的记录,程序可能运行一段时间,但是最终程序将被锁定。第二个是在编辑域编辑字段后面的的字段有时会变成零字段。这在代码中很难跟踪知道这是怎么回事,所以当你编辑数据库记录时,记住把要编辑的字段放到记录的最后。

       由于编辑域的这种缺陷,我们不能不能将其应用到Contacts程序中实现必要的功能。然而也要记住它(特别是它的缺陷),因为当你编辑一个大的单编辑字段的数据库如内嵌程序Memo Pad时,或许会用到它。

       函数setFields()

       在函数setFields()中,数据库中的记录被拷贝到Contacts Detail窗体的三个编辑框中。

// 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 );
precord = MemHandleLock( hrecord );

// 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;
}

       首先,我们先激活窗体和记录。对于每一个编辑框的内容,都调用了函数setText()将数据库中的记录的相应部分拷到里面。

本文来源:嵌入式在线    作者:
热点资讯(一周点击率)
热评博文
评一评已有 0 位网友对此文发表了看法。  我也来评一下

验证码:  看不清?换一张

 

快乐大本营
工程师之星
高福东
擅长嵌入式开发及单片机应用开发
  • 王波涛  熟悉单片机及其接口技术
  • 朱伟平  熟悉51单片机系统LCD驱动程序编写及调试。
热门招聘
论坛热贴