PalmOS开发教程-5.3
小技巧:
如何获得一个记录有两种办法可以获取一条已存在的记录。一个是函数DmGetRecord()另一个是函数DmQueryRecord()。对于DmGetRecord()来说,有一点要注意,就是当对记录操作完成后,必须要调用函数DmReleaseRecord()释放记录。如果你忘记了这一点,那么当下一次获取这一记录的句柄时,你得到将是0。
新记录我们可以通过设置“脏”位来识别。当出现新记录这种特殊情况时,我们把游标设置在First Name 这个字段上。这里的设置焦点和第三章讲到的一样。为设置upper shift on须调用函数setGrfState()。这是因为当将这一字段设置焦点后,即使我们设置了Auto Shift属性后,系统也不会设置upper shift on。在调用函数SetGrfState()前,在头文件Pilot.h下面写入它的头文件Graffti.h。
// CH.5 Added for the call to GrfSetState()
#include
函数getFields()
函数getFields()将在编辑框中的字段写入到数据库当前记录中。
// CH.5 Wipes out field references to the record and releases it
//
static void detachFields( 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;
}
首先把焦点移动到所指记录上。这意味着在此之前应指定一记录并设置此记录为“脏”。
第二步,从窗体上收集数据并输入到要修改的记录中。
最后,我们必须清除“脏”位。不然的话,第一个记录被修改后,所有的记录都将被置“脏”。只有当用户指定修改记录或调用函数newRecord()后,“脏”位才被重新设置。
函数setText()
和第三章中将字符串资源传递到字段中一样,函数setFields()将一个字符串和字段指针传递到指定的记录中。
// 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;
}
首先我们获取字段的句柄。如果字段有一个句柄,那么我们调整它以适合字符串。如果字段还没有句柄,我们以合适的大小为其分配一个。不对调整和分配句柄判断是否成功是十分危险的。像处理newRecord()那样,我们将在第七章介绍错误处理时补充这些代码。
当获得正确的句柄后,下面所做的你就很熟悉了。锁定句柄、拷贝字符串、解锁字符串然后写入字段。最后我们绘制(draw)编辑框以反映其变化。
函数getText()
getText()将字段的内容拷贝到数据库的记录中。
// 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;
}
首先我们获取字段字符串的指针。因为这是字段内部的指针,所以我们没必要解锁它!我们调用函数DmWrite()依据给出的偏移量将字段的字符串拷贝到相应的数据库记录中。由于我们限制了能写入字段的字符的个数并且规定了相应记录的大小,所以可保证此拷贝是有效的。这也是为什么没有添加错误检查的原因。
函数contactDetailHandleEvent()内容的添加
在第一个case语句,即frmOpenEvent语句里,在函数FrmDrawForm()后调用函数setFields(),用来拷贝当前的记录到数据库的各字段中。放在FrmDrawForm()后的原因是在绘制完窗体的其它部分前,不能绘制编辑框。否则会造成程序运行错误。
// CH.4 Form open event
case frmOpenEvent:
{
// CH.2 Draw the form
FrmDrawForm( form );
// CH.5 Draw the database fields
setFields();
}
break;
另外,有一些新的事件需要被处理。首先是frmCloseEvent。
// CH.5 Form close event
case frmCloseEvent:
{
// CH.5 Store away any modified fields
getFields();
}
break;
这个case语句的作用是,在关闭窗体前,应获得最后一次被修改的字段内容。
下一步我们象以前处理按钮事件ctlSelectEvent一样处理其它的按钮事件。这一次我们应该知道哪一个按钮被按下了。为此,我们利用switch...case 语句,以controlID作为标识符进行判断。
// 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;
我们处理的第一个按钮即First按钮,这个按钮的作用是将定位到数据库的第一个记录上。在处理所有按钮事件以前,先调用函数getFields()获取当前记录以保证在移动新记录前能够保存所有的变化。在这个按钮处理事件中,我们把游标移动到第一个记录上。在处理完所有按钮事件以后,我们调用setFields()来获得新的当前记录并拷贝到各字段中。
下一个按钮事件为Previous按钮,它将记录移动到前一个。
// CH.5 Previous button
case ContactDetailPrevButton:
{
// CH.5 Move the cursor back one record
if( cursor > 0 )
cursor--;
}
break;
在检查是否到达第一个记录后(到达第一个记录后,不能在向前移),我们将记录前移一个并将新记录和各字段相关联。
下面处理Next按钮。
// CH.5 Next button
case ContactDetailNextButton:
{
// CH.5 Move the cursor up one record
if( cursor < (numRecords - 1) )
cursor++;
}
break;
这个代码块和处理向前的代码块十分相似。在判断完是否到达最后一个记录后,将记录后移一个。
为什么我们对变量cursor的处理如此的小心呢?这是因为如果我们调用函数DmQueryRecord()时访问了一个不存在的记录,程序会立即崩溃。因此,必须保证cursor必须是一个有效的值。
现在我们看一下按钮Last的处理,此按钮将移动记录到最后一个。
// CH.5 Last button
case ContactDetailLastButton:
{
// CH.5 Move the cursor to the last record
if( cursor < (numRecords - 1) )
cursor = numRecords - 1;
}
break;
这段代码和Previous按钮事件处理代码有些相似。注意因为第一个记录编号为0所以最后一个记录应为总记录数减一。
下面是Delete按钮的处理:
// 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;
为保证函数setFields()中的DmQueryRecord()函数的成功调用,必须确保至少剩余一条记录。因此当检查到已经没有记录时,调用newRecord()创建一条新记录。
做一个一条记录也没有的数据库应用程序也是可能的,但须保证不能调用函数DmQueryRecord()。
最后,我们处理New按钮。
// CH.5 New button
case ContactDetailNewButton:
{
// CH.5 Create a new record
newRecord();
}
break;
}
// CH.5 Sync the current record to the fields
setFields();
}
break;
以上代码也和其它浏览按钮代码差不多。只不过它没有将游标移动到一个已存在的记录上,而是游标指到一个我们刚刚创建的记录之上。结果该记录被作为当前记录并且其后的记录编号自动的加一。
在处理完所有按钮事件后,调用setFields()。它的作用是获取并拷贝当前记录(或许是条新记录,或许是并没有修改的记录)到各字段中。
另外,还有一个事件需要处理。那就是如果记录被修改后,其“脏”位应被标识。其中有一种方法就是判断编辑域是否被调用来识别记录是否被修改。
// CH.5 Respond to field tap
case fldEnterEvent:
isDirty = true;
break;
这里我们通过判断编辑域是否被选中来表示“脏”位。
调试
一旦你做完了上面所有对Contacts.c程序的修改后,你就可以开始使用Debug工具来调试程序了。
1.打开Code Warrior IDE 集成开发环境。
2.打开Contacts目录下的Contacts.mcp工程文件
3.选择 Project | Make 菜单项。如果你的程序不能成功编译连接的话,就回过头检查一下最后修改的代码。如果你的问题是出在某些资源的命名上,记得去检查Contacts_res.h文件,以确保.h文件和.c文件中的资源命名相匹配。
4.使Palm与PC同步,这样做主要是为了保护Palm中的其它数据即便使你被迫重启设备也不会丢失数据。
5.退出HotSync同步软件。
6.一旦你的代码通过了编译和连接,你就可以选点 Project | Debug 来打开调试器了。
你可以通过不断按下调试器窗口顶部的单步调试按钮(样子就像一个向右的箭头下面有个大括号),来实现对程序的逐行运行调试。当你第一次运行程序的时候,ContactsDB数据库将被建立,此时DmCreateDatabase()函数将返回0。而等到后来再次运行时,DmCreateDatabase()函数的返回值将是537,那表示,数据库已经存在(dmErrAlreadyExists)。
在每一个按钮处理事件的进入case语句的第一行设置断点,这样做后,有断点的语句的左边将出现一个小红点。点击调试器顶部向右箭头的按钮,使程序自由的运行。
Contact Detail 窗体将出现,在窗体上的文本框中输入一些数据,然后点击“New”按钮来添加一条新的记录。我想你有必要在这里加断点观察一下程序的执行情况。
为了进入函数newRecord()观察,我们可以先在函数语句的前一条语句加断点,当程序运行到这个断点时,点击调试器顶部的“jump into”按钮(样子像一个向下的箭头,外面有一个打括号)进入函数体内部运行。然后再连续按单步调试按钮逐行的运行函数中的语句,以确保函数运行是正常的。函数内运行结束后再按下调试器顶部的”run”按钮。
运行程序,为你的数据库创建最少4条记录。然后你就可以开始测试你的“navigation”按钮了。调试步骤与上面介绍的相同,当你调试完相应函数,确保他们正常工作后,你可以在有断点的语句左边的小红点上点击,以取消那里的断点设置,这样你的程序就不会在已经调试过的代码上停住了。
当你确保“First”、“Previous”、“Next”和“Last”等按钮都正常运行后,就可以着手调试“Delete”按钮。单步跟踪”Delete”按钮的代码,删除所有的记录,最后再创建一条记录,以确保数据库中至少存在一条记录。
下一步是什么?
在第六章我们将继续完善Contacts 应用程序。我们将为Contacts Detail窗体添加时间和日期输入框,将使用到更多的控件。
源程序清单
下面是经过本章修改的Contacts.c的源代码。本章的源代码在随书所带的光盘的CH.5目录中有一份完整的拷贝。
// CH.2 The super-include for the Palm OS
#include
// CH.5 Added for the call to GrfSetState()
#include
// 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 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 );
// 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.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;
}
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
// 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.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.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.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;
}
// 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 );
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;
}
// 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;
}

