闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌熼梻瀵割槮缁炬儳缍婇弻鐔兼⒒鐎靛壊妲紒鐐劤缂嶅﹪寮婚悢鍏尖拻閻庨潧澹婂Σ顔剧磼閻愵剙鍔ゆ繝鈧柆宥呯劦妞ゆ帒鍊归崵鈧柣搴㈠嚬閸欏啫鐣峰畷鍥ь棜閻庯絻鍔嬪Ч妤呮⒑閸︻厼鍔嬮柛銊ョ秺瀹曟劙鎮欓悜妯轰画濠电姴锕ら崯鎵不閼姐倐鍋撳▓鍨灍濠电偛锕顐﹀礃椤旇偐锛滃┑鐐村灦閼归箖鐛崼鐔剁箚闁绘劦浜滈埀顑惧€濆畷銏$鐎n亜鐎梺鍓茬厛閸嬪棝銆呴崣澶岀瘈闂傚牊渚楅崕鎰版煟閹惧瓨绀冪紒缁樼洴瀹曞崬螖閸愵亶鍞虹紓鍌欒兌婵灚绻涙繝鍥ц摕鐎广儱鐗滃銊╂⒑閸涘﹥灏伴柣鐔濆懎鍨濋悹鍥ф▕閸氬顭跨捄鐚村姛闁挎稓鍠栧缁樼瑹閸パ冾潻闂佸憡顨嗗ú鐔风暦閸洦鏁嗗璺侯儐濞呮牗绻濋悽闈浶㈤柨鏇樺€濋獮濠囧箛閻楀牆鈧灝鈹戦崒姘暈闁绘挾鍠栭獮鏍箹椤撶偟浠紓浣割槺閺佸寮诲☉姘e亾閿濆倹娅囬柛鏂跨Ф缁辨帡宕掑姣欍垺顨滈鍐ㄥ祮鐎规洖銈搁幃銏ゅ传閸曨偆顔囬梻鍌氬€风粈渚€骞夐敓鐘茬闁硅揪绠戠粈澶屸偓鍏夊亾闁告洦鍋嗛崝锕€顪冮妶鍡楃瑨闁稿﹤缍婂鎶筋敆娓氬洦顔旈梺缁樺姌鐏忔瑧绮绘繝姘厱闁宠桨绀侀顓犫偓瑙勬礃鏋い锕€顕槐鎺楁偐鐎圭姴顥濆銈庝簻閸熷瓨淇婇崼鏇炲耿婵°倐鍋撴い顐㈡喘濮婅櫣绮欓崠鈩冩暰濠电偠灏欓崰搴敋閿濆绠瑰ù锝呮憸椤撳搫鈹戦悩顐壕闁搞劌缍婇弻濠囨晲閸涱垱娈鹃梺璇″灦閸嬪﹤顬婇鍫熲拻濠电姴楠告晶濠氭煕閹捐泛鏋涚€殿喖顭烽幃銏㈠枈鏉堛劍娅撻梻浣风串缁蹭粙寮甸鍕棷闁哄顑欏〒濠氭煏閸繂鏆欓柣蹇d簼娣囧﹪顢曢姀鐘虫闂佷紮绲块崗姗€骞冮姀銈嗗亗妤犵儐鍏橀弲鐘诲蓟閺囩喎绶為柛顐g妇閸嬫捇寮撮埗鍝勵槸椤繈鎳滈悽闈涘箺闂備胶绮弻銊╁箺濠婂牊鍎楀┑鐘插绾捐偐绱撴担濮戭亜霉椤旈敮鍋撶憴鍕鐎光偓閹间胶宓侀柟鐑橆殔缁犲鏌涢幘鑼跺厡婵℃彃娲︾换婵嗏枔閸喗鐏嶉梺鎸庢磵閺呯姴鐣峰⿰鍐f闁靛繆鏅滈弲锝呪攽閻愬弶鈻曞ù婊勭箞閸╂盯骞嬮敂鐣屽幍缂備礁顑呴悘婵嬵敆閵忋倖鐓熼柟鎯у船閸旓箓鏌$仦鍓р槈閾伙綁鏌ゆ慨鎰偓妤呮偂婢舵劖鍊甸悷娆忓缁€鍐煕閵娿儲鍋ラ柣娑卞枛铻i柤娴嬫櫇閿涙粌鈹戦埥鍡楃仯缂侇噮鍨堕幃鐢稿冀椤撶啿鎷绘繛鎾村焹閸嬫捇鏌嶈閸撴盯宕戝☉銏″殣妞ゆ牗绋掑▍鐘绘煙缂併垹鏋熼柣鎾寸懄閵囧嫰寮村Δ鈧禍鍓х磽娴e搫校闁绘濮撮锝夘敃閿曗偓缁犺崵绱撴担濮戭亝绂嶈ぐ鎺撯拺闁兼祴鏂侀幏锟犳煕閹垮嫮鐣电€规洏鍎抽埀顒婄秵閸嬪倻鎹㈤崱娑欑厽闁规澘鍚€缁ㄥ鏌嶈閸撴岸鎮ч弴锛勪罕濠电偠鎻紞鈧い顐㈩樀瀹曪綀绠涘☉娆戝帾闂佸壊鍋呯换宥呂熼崼銉︾厸濞达綁娼婚煬顒勬煛瀹€瀣瘈鐎规洖鐖兼俊鐑藉Ψ瑜岄惀顏堟⒒娴g懓鈻曢柡鈧潏鈺傛殰闁圭儤顨嗙粻鎺楁⒒娴g懓顕滅紒璇插€块獮濠呯疀濞戞ḿ鐤呴梺鍛婄缚閸庡磭澹曢悡搴唵閻犳椽缂氱€氫即鏌涢弬娆惧剰閼挎劙鏌涢妷鎴濈Х閸氼偄顪冮妶鍐ㄧ仾婵炶尙鍠愰幈銊╁焵椤掑嫭鐓冮柕澶堝劚鐢姵绻涢敐搴℃珝婵﹦绮幏鍛驳鐎n亝鐣伴梻浣告憸婵潧鐣濈粙璺ㄦ殾闁硅揪绠戝洿婵犮垼娉涢敃銉╂倵椤掑嫭鈷戦柛娑橈工婵箓鏌涢悩宕囧⒈濠㈣娲熷畷妤呭礂閻撳骸浼庢繝纰樻閸ㄦ澘岣块敓鐘蹭紶婵炲樊浜濋悡鐔兼煙閸喖顏紒澶樺枟閹便劍绻濋崟顓炵闂佺懓鍢查幊妯虹暦椤愶箑唯妞ゎ剦鍠撻崝宀勫煘閹达箑鐓¢柛鈩冦仦缁ㄨ鈹戦悙鐐光偓瀣崲濠靛宓侀柛鎰靛枟閸婄兘鏌i幋鐐嗘垵鈻嶉崶顒佲拺缂佸瀵у﹢鎵磼鐎n偄鐏︾紒鍌氱Т铻栭柛娑卞枓閹锋椽姊婚崒姘卞濞撴碍顨呭嵄闁圭虎鍠楅悡娑㈡煕濞戝崬鏋ら柣顓熷浮閺岀喖顢氶崱娆戠槇闂佽鍠撻崹钘夌暦濡ゅ懏鍤冮柍杞扮缁犱即姊婚崒娆戝妽闁诡喖鐖煎畷鏇㈩敋閳ь剙顕i幓鎺嗘婵炲棙鍩堝ù鍕攽閻樿宸ラ柣妤€锕幃锟犲礃椤忓懎鏋戝┑鐘诧工閻楀棛绮堥崼鐔虹闁糕剝顨婇悰婊堟煠閺夋寧鍋ユ慨濠冩そ椤㈡鍩€椤掑倻鐭撻柟缁㈠枟閸婂潡鏌涢…鎴濅簴濞存粍绮撻弻鐔煎传閸曨厜銉╂煕韫囨挾鐒搁柡灞剧洴閹垽宕妷銉ョ哗闂備礁鎼惉濂稿窗閺嵮呮殾婵炲棙鎸稿洿闂佺硶鍓濋〃蹇斿閿燂拷     婵犵數濮烽弫鍛婃叏閻戣棄鏋侀柛娑橈攻閸欏繘鏌i幋锝嗩棄闁哄绶氶弻娑樷槈濮楀牊鏁鹃梺鍛婄懃缁绘﹢寮婚敐澶婄闁挎繂妫Λ鍕⒑閸濆嫷鍎庣紒鑸靛哺瀵鈽夊Ο閿嬵潔濠殿喗顨呴悧濠囧极妤e啯鈷戦柛娑橈功閹冲啰绱掔紒姗堣€跨€殿喖顭烽弫鎰緞婵犲孩缍傞梻浣虹帛閿氭俊顖氾工铻炵€光偓閸曨兘鎷洪柡澶屽仦婢瑰棝藝閿曞倹鐓ラ柡鍥ュ妺闁垳鈧鍠栭…鐑藉垂妤e啫绠涘ù锝呮啞閸婎垶姊虹涵鍛汗閻炴稏鍎甸崺鈧い鎺嗗亾缁剧虎鍙冮、娆撳箛閻楀牃鎷婚梺绋挎湰閼归箖鍩€椤掍焦鍊愮€规洘鍨垮畷鍗炩槈濡櫣鈧參姊绘笟鍥у缂佸鐡ㄩ、濠囨⒒娴e憡鍟炴繛璇х畵瀹曡瀵奸弶鎴狀槰闂侀€炲苯澧存慨濠冩そ濡啫霉閵堝棛娲寸€规洏鍔戦、娑橆潩椤掑倻鎲跨紓鍌氬€搁崐鎼佸磹閹间礁纾圭€瑰嫭鍣磋ぐ鎺戠倞闁冲搫鍟伴ˇ鈺傜箾閺夋垵鎮戞繛鍏肩懅婢规洜鎲撮崟顓犵槇闂傚倸鐗婃笟妤呭磿韫囨稒鐓㈤柛鎰典簻閺嬫盯鏌$仦璇插闁宠鍨垮畷閬嶅煛閸屽們鍥ㄢ拺闁告繂瀚崳娲煟閹垮嫮绡€鐎殿喛顕ч埥澶娢熼柨瀣垫綌闂備礁鎲¢〃鍫ュ磻閻愮儤鍊剁€广儱顦伴埛鎴︽煕閿旇骞栭柛妯绘綑闇夋繝濠傚暟閸╋絾顨ラ悙鎻掓殭閾绘牠鏌涢幇銊︽珔闁哄鍊垮娲川婵犲啫顦╅梺鍛婃尰閻╊垵妫熼梺闈浥堥弲婊堝煕閹寸姷纾藉ù锝堝亗閹达箑绠氶柛顐犲灮绾捐偐绱撴担璐細缂佺姵鎸婚妵鍕即椤忓棛蓱缂備胶绮换鍌烇綖濠靛鏁囬柣鎰閻╁酣姊绘担钘夊惞闁稿鍋熺划娆撳醇閵夈儳鍔﹀銈嗗坊閸嬫捇鏌涘Ο鑽ゅ缂佹梻鍠栧鎾倷閳哄倹鏉搁梻浣虹帛閸旀牕岣垮▎鎾村€堕柨鏂款潟娴滄粓鐓崶褔顎楃€规挸妫欓〃銉╂倷瀹割喖鍓堕梺杞扮閸熸挳宕洪埀顒併亜閹烘垵鈧悂藟濮樿埖鐓曠憸搴ㄣ€冮崱娑欏亗婵炴垯鍨洪悡鏇㈡煏婢跺牆鐏繛鍛嚇閺岋紕鈧綆鍋呴埛鎺旂磼鏉堛劌娴柟顔规櫊閹筹繝濡堕崶銊︾槖闂傚倷绀侀幖顐︽儔婵傜ǹ绐楅柟鎹愵嚙閻掑灚銇勯幒宥堝厡闁愁垱娲栭悾婵嬫晲閸涱喖浠村Δ鐘靛仜閿曨亜鐣峰鈧、娆撴偩鐏炶棄绠洪梻鍌氣看閸嬪嫬煤閵堝鏅濋柕澶堝劗閺嬪秴鈹戦悩鍙夊闁绘挻绋撻埀顒€鍘滈崑鎾绘倵閿濆骸澧扮悮锔戒繆閵堝洤啸闁稿鍋涢悳濠氬锤濡も偓閻掑灚銇勯幒鎴濐仾婵炴嚪鍥ㄧ厵閻犲泧鍛槇閻庤娲橀懝楣冨煡婢舵劕顫呴柍閿亾闁归攱妞藉娲川婵犲嫮鐣甸柣搴㈠嚬閸樺ジ鏁冮姀鈥愁嚤閻庢稒岣块崢浠嬫⒑閸愬弶鎯堥柨鏇樺€濋幃姗€鏁冮崒娑氬幗濠电偞鍨靛畷顒€鈻嶅鍥e亾鐟欏嫭绀€闁绘牕銈搁妴浣肝旈崨顓犲姦濡炪倖甯掗崐濠氭儗閸℃稒鐓曠€光偓閳ь剟宕戦悙鐑樺亗闁哄洨鍠撶弧鈧梻鍌氱墛缁嬫垿顢旈埡鍛厱闁哄啠鍋撶紒顔芥崌瀵鏁撻悩鎻掔獩濡炪倖鎸荤划灞剧椤斿皷鏀芥い鏃傘€嬮崝鐔虹磼椤曞懎鐏︽鐐茬箻瀹曘劎鈧稒蓱閸庮亪姊洪懡銈呮瀾濠㈢懓妫濋、鏇熺附閸涘ň鎷绘繛杈剧悼閸庛倝宕甸埀顒勬⒑绾拋鍤嬬紒缁樼箞閻涱噣宕橀鑲╋紲闂佺粯鍔︽禍鏍磻閹惧鐟归柍褜鍓欓锝嗙鐎n€晠鏌曟竟顖氭噽瀹撲焦绻濋悽闈浶ラ柡浣告啞閹便劎鈧數纭堕崑鎾斥槈閹烘挻鐝栫紓浣戒含閸嬬偤骞嗛弮鍫濈參闁逞屽墴瀵劍绂掔€n偆鍘介梺褰掑亰閸撴瑧鐥閺屽秶绱掑Ο鑽ゎ槬闂傚洤顦扮换婵囩節閸屾凹浼€闂佹椿鍘界敮妤呫€冮妷鈺傚€烽柡澶嬪灩娴煎矂姊虹涵鍛彧缂侇喗鎹囬獮鍐ㄢ枎閹惧鍔靛┑鐐村灦濮樸劌鈻旈崸妤佲拻闁稿本鐟︾粊鐗堛亜椤愩埄妲搁柣锝呭槻铻i悶娑掑墲閻忓啫鈹戦悙鏉戠仸闁荤啙鍥モ偓鍛存煥鐎n剛鐦堟繝鐢靛Т閸婃悂寮虫繝鍥ㄧ厸闁逞屽墯缁傛帞鈧綆鍋嗛崢鎾绘⒑鐎圭姵銆冪紒鈧笟鈧鎶芥倷閻戞ḿ鍘梺鎼炲劘閸斿海绮婚弽顓熺厵濞撴艾鐏濇俊鐣岀磼缂佹ḿ绠炵€规洏鍔戦、姘跺川椤掆偓閹藉姊婚崒娆掑厡闁硅櫕鎸搁锝夊醇閺囩偟顔囬梺鍛婂姌鐏忔瑩寮抽敃鍌涚厽闁哄啫鍊甸幏锟犳煛娴e憡鍠橀柡宀嬬秮瀵噣宕戦崘鑼Ш鐎殿喗鐓¢獮鏍ㄦ媴閸忓瀚奸梺鑽ゅТ濞层倕顕i崼鏇炶埞濠㈣泛顑冩禍婊勩亜閹扳晛鐒烘俊鍙夋倐閹繝濡舵径瀣幗闂佸搫鍊瑰畷姗€鎳¢敍鍕=闁稿本绋掗惃鎴︽煙閸欏鍊愮€殿噮鍣e畷鐓庘槈閹烘垳澹曟繛杈剧到婢瑰﹤岣块埡鍛仯閺夌偞澹嗙粔鍧楁煏婵炵偓娅嗛柣鎾卞劦閺岋繝宕掑鍙樿檸闂佹娊鏀卞Λ鍐蓟閿濆鏅濋柍褜鍓熼幃褔骞橀幇浣圭稁濠电偛妯婃禍婊呯不瑜版帗鐓熼柟閭﹀幖缁茶鈹戦鑲╃劯婵﹥妞藉畷銊︾節閸愵亜寮崇紓鍌欑椤︿即骞愰幎钘夋槬闁靛繒濯崥瀣熆鐠鸿 鐪嬫繛鐓庯躬濮婃椽妫冨☉姘暫濠碘槅鍋勯惌鍌炵嵁韫囨拋娲敂閸涱垰骞楅梻浣虹帛閿氶柛妯荤墵閹虫粓顢旈崼鐔哄幈闂佸搫鍊介崕鑽も偓姘嵆閺屽秶鎷犻懠顑勵殽閻愬弶鍠橀柟顔ㄥ洤閱囬柕蹇曞Т缁犮儵姊婚崒娆戭槮闁硅绻濆畷褰掝敍閻愯尙锛欓梺鍝勭▉閸嬧偓闁稿鎸搁~婵嬫倷椤掆偓椤忥拷

45fan.com - 路饭网

搜索: 您的位置主页 > 网络频道 > 阅读资讯:PalmOS开发教程介绍和分析

PalmOS开发教程介绍和分析

2016-09-07 06:03:53 来源:www.45fan.com 【

PalmOS开发教程介绍和分析

第八章 表和滚动条
在这一章中,我们将讨论Palm OS的两个很重要的用户界面元素:表和滚动条。表能够显示或编辑较大的数据量。在嵌入式应用程序中都它使用的很广泛。滚动条的功能很出色,但由于滚动条不支持1.0版本的Palm OS系统,所以只有在不想支持较早的Pilot 1000和5000时,才可以使用滚动条。我们将同时添加滚动按钮(它可以被所有的Palm设备使用)和滚动条(请不要在一个真正的应用程序中使用!可能会系统崩溃的喔!),然而这些还不够,我们还将论及如何

支持PAGE UP和PAGE DOWN键。

保存工程

现在你已经有了这个习惯了吧,步骤如下:

1.运行Windows浏览器;

2.找到工程存放的文件夹;

3.选中文件夹,按CTRL+C来复制文件夹;

4.选择一个文件夹用来保存副本;

5.按CTRL+V把项目副本粘贴到备份文件夹中;

6.把项目名重命名为你容易记的名字,我把它命名为Contacts CH.7。

删除旧的资源

既然已用表代替了Contact List窗体中的列表框,那么我们需要把列表框删除。

1.运行Metrowerks 构造器;

2.打开资源文件Contacts.rsrc。它位于项目文件夹中的Src文件夹中;

3.双击打开Contact List窗体;

4.点击资源列表中名为List的资源,按DELETE键来删除;

5.Contact List窗体现在看起来如图8-1所示。

删除旧代码

既然已经将列表框删除了,函数buildList()和deleteList()也就不再需要了。找到并删除这两个函数及其有关的内容。你可以将光标放在文件的开始处,在菜单栏中选中Search | Find,输入buildList。在删除了所有与buildList有关内容后,你可以再对deleteList做相似的操作。

另外,删除在Contact List事件处理函数中响应1stSelectEvent事件的代码。这些代码是:

// CH.7 Respond to a list selection

case lstSelectEvent:

{

// CH.7 Set the database cursor to the selected contact

cursor = event->data.lstSelect.selection;

// CH.7 Go to contact details

FrmGotoForm( ContactDetailForm );

}

break;

相对其它的UI(用户界面)元素来说,表是容器。表中的UI元素和表外的Palm OS系统中 UI元素不太一样。在表中的每一个单元(Cell)(行+列)可以有不同的类型,也就是说,它可以支持不同类型UI元素,在接下来的部分中将描述这些类型。

在捕获事件和将单元中的数据传递给UI元素时,表也有所不同。一些普通的函数,对表却可以执行一些特殊的函数。表看起来很像静态的Palm OS,只是要保证将每个可用UI元素的类型区分开来。

不幸地是,许多表中可使用的UI元素,在我的工程中却用不到。本章中,我只是将如何建立了自己的定制单元的技巧做一些论述。

条目类型

表中的每一个单元都有自己的类型。例如,单元可以是一个字编辑框资源或一个复选框资源。表8-1中是这些类型及其操作的纲要。

类型 使用

CheckboxTableItem

除了没有文本和选项框关联外,这种类型的单元操作和一般的选项框一样,可以通过调用TblSetItemInt()将其选中或清除,0表示没有选中,1表示选中。

CustomTableItem

这是一个非常有用的单元类型。你必须为此类型每一列定义一个定制函数,在本章以后的部分中,我们更多讲述了如何处理这个类型的内容。这种类型是可编辑的。

DateTableItem

在此单元中显示的日期已定义为DateType格式,可用TblSetItemPtr()函数将指向DateType的游标传给表。这种格式的缺点是在过去的任何日期后都会写出一个感叹号。有时这或许是件好事,但有时它会强制你使用定制日期显示。这种类型是不可编辑的。

LabelTableItem

它用来显示一个标签。使用TblSetItemPtr()将字符串传递给表。此格式的缺点就是表经常在所传递的字符串后面加上一个冒号(:),并且文本通常是右对齐。这就是为什么在这一章中,我们要使用定制类型地原因。这种类型是不可编辑的。

numericTableItem

显示一个右对齐的数字。这种类型很好,不会加上一些怪异的内容。可调用TblSetItemInt()函数来设置数字。这种类型是不可编辑的。

popupTriggerTableItem

这种类型类似于弹出触发按纽。使用TblSetItemPtr()函数可以指向列表框的游标使列表框显示出来,使用TblSetItemInt()可以设置列表框到底选中哪一个条目。

TextTableItem

这种类型类似于编辑框,它是可编辑的。编辑框的长度可以改变和重叠。使用TblSetLoadDataProcedure()定义一个定制导入函数,将编辑框的句柄传递给表。使用TblSetSaveDataProcedure()定义一个保存函数,可以将数据保存在此句柄的编辑框中。所以你必须写这两个定制函数来支持表中的编辑框操作。

textWithNoteTableItem

这种类型会在一般的文本条目右边加入一个小的提示图标。这提示图标看起来象单独地被选中。当单元被选中后,你须调用TblEditing()看一下编辑框是否为可编辑模式。如果不是,Note图标已经被选中了,你就要切换到你的Note窗体去处理。

narrowTextTableItem

除了可以使用TblSetItemInt()在字段末尾处定义空间的大小,使之符合所填内容外,这个类型和一般的TextTableItem类型相同。例如,在日历窗体中,为了在条目的右边放置小的警告钟图标,Date Book程序就用这种类型来提供空间。

因为所有存在的类型都有其专用性,所以只有自己定制类型才能完成自己想实现的功能。

表的属性

表8-2中是表的属性描述。

和其它资源属性一样,在窗体中选中表资源后,就可以在构造器中进行编辑。

名称 描述

Object Identifier 在资源头文件中,构造器用之代表资源ID

Table ID 表的资源ID号。

Left Origin 水平方向上控件的最左端位置

Top Origin 垂直方向上控件的最顶端位置

Width 表的宽度

Height 表的高度。

Editable 定义表中可编辑的数据是否能被用户输入

Rows 表中可见的行数。

Column Widths 每一列的宽度,如果要定义一个新的列,按CTRL-K

添加一个表

现在将表添加到Contact List窗体中:

1.运行Metrowerks 构造器;

2.打开资源文件Contacts.rsrc,它位于工程文件夹中的Src文件夹中;

3.双击打开Contact List窗体。

4.在菜单中选择Window | Catalog来打开Catalog;

5.拖动表资源到窗体中;

6.设置表的属性:Object Identifier=Table,Left Origin=0,Top Origin=15,Width=153,Height=130。这样就有足够的空间放置十行,然后设置Rows为10,这样设置也可以在窗体右边留有足够的空间放置滚动条;

7.定义Column Widths。设Column Width从1到40。选中Column Width 1,按CTRL-K创建一个新的列。设置此列宽度从2到40。选中Column Width 2,按CTRL-K创建第三列。设置Column Width从3到73;

8.Contact List窗体看起来如图8-2所示。

在表中显示记录

我们将添加表的两个基本函数:drawTable()和drawCell()。drawTable()在光标的当前状态绘制表。函数drawCell()是定制的单元输入函数,当Palm OS要向表中输入一个条目时,就会执行这个函数。我们先加入这些函数的原型:

static void drawTable( void );

static void drawCell( VoidPtr table, Word row, Word column,

RectanglePtr bounds );

drawCell()的函数原型必须和订制字单元输入的回馈函数原型相匹配。在Palm OS文献的TblSetCustomDrawProcedure()中有这个原型的定义。

为了整洁起见,最好在文件的开头定义常量:

// CH.8 Table constants

#define TABLE_NUM_COLUMNS 3

#define TABLE_NUM_ROWS 11

#define TABLE_COLUMN_DATE 0

#define TABLE_COLUMN_TIME 1

#define TABLE_COLUMN_NAME 2

#define BLACK_UP_ARROW "/x01"

#define BLACK_DOWN_ARROW "/x02"

#define GRAY_UP_ARROW "/x03"

#define GRAY_DOWN_ARROW "/x04"

常量TABLE_NUM_COLUMNS和TABLE_NUM_ROWS定义了窗体中表显示的大小,这与以后的很多运算与迭代有关。接下去的三个常量TABLE_COLUMN_DATE、TABLE_COLUMN_TIME和TABLE_COLUMN_NAME定义了每列所填写的信息。最后的四个常量BLACK_UP_ARROW,BLACK_DOWN_ARROW,GRAY_UP_ARROW和GRAY_DOWN_ARROW是Palm Os中Symol 7字体中代表这些图的ASCII值。当滚动条到达顶部或底部,我们使用这些常量给箭头加上灰晕。值得注意的是,在Palm Os中,只有这个控件可以添加灰晕。

函数contactListHandleEvent()的修改

找到Contact List窗体的事件处理函数contactListHandleEvent(),在这里需要添加drawTable()函数调用:

// CH.7 Form open event

case frmOpenEvent:

{

// CH.7 Draw the form

FrmDrawForm( form );

// CH.8 Populate and draw the table

drawTable();

}

break;

接着,处理表中记录被选中后的操作,在选中一条记录后应该调用Contact Detail窗体来显示其详细信息。请注意这些代码与处理列表框记录选中后的代码很相似。为使Contact Detail窗体显示相应的记录,我们设置了游标(Cursor)变量。

// CH.7 Respond to a list selection

case tblSelectEvent:

{

// CH.7 Set the database cursor to the selected contact

cursor += event->data.tblSelect.row;

// CH.7 Go to contact details

FrmGotoForm( ContactDetailForm );

}

break;

因为数据库要根据了不同的标准排序,所以每次排序后都要重新画表来显示新的记录顺序。为此,在DmQuickSort()后加入drawTable()函数来响应popSelectEvent事件。

// CH.7 Sort the contact database by the new criteria

DmQuickSort( contactsDB, (DmComparF*)sortFunc, sortBy );

// CH.8 Rebuild the table

drawTable();

}

break;

这样对这个函数的修改就完成了。

添加drawTable()函数

下面添加drawTable()函数。先定义一些变量,并获取表的指针。

// CH.8 Draw our list of choices using a table object

static void drawTable( void )

{

FormPtr form;

TablePtr table;

Int column;

Int count;

ControlPtr upArrow;

ControlPtr downArrow;

// CH.8 Get the form pointer

form = FrmGetActiveForm();

// CH.8 Get the table pointer

table = getObject( form, ContactListTableTable );

我们将对表中的列做两件事情。首先,每一列都要有一个定制的规则(Routine)。虽然条目类型是基于单元的,但如果单元是定制的,每一单元在特定的列上都要使用相同的规则。在例子中,我们将创建一个定制规则——drawCell(),在表的每个单元中都将使用这个规则。

另外一个要做的事情是使列为可见。列的缺省值是不可见的,为了显示需要将其设置为可见。

// CH.8 For all columns

for( column = 0; column < TABLE_NUM_COLUMNS; column++ )

{

// CH.8 Set the draw routine

TblSetCustomDrawProcedure( table, column, drawCell );

// CH.8 Make the column visible

TblSetColumnUsable( table, column, true );

}

下面,再来讲述表的行。由于表中的每一单元都需要定义一个类型,所以我们对列进行了操作。对于表中的不用(Unused)的行来说,就不需这样做。如果数据库包含的记录少于可见的行数,就需把表中不用的行关闭。这是很重要的,如果不关掉这些不用的行,当写代码时,我们就会试图向行中写不存在的记录,说不定会使系统崩溃的。既然表中的记录数是在变化的,我们就要保证在有记录时,标记行为可用,在没有记录时,标记行为不可用。

// CH.8 Initialize the table styles

for( count = 0; count < TABLE_NUM_ROWS; count++ )

{

// CH.8 If there is data

if( count < numRecords )

{

// CH.8 Show the row

TblSetRowUsable( table, count, true );

// CH.8 Set the cell styles

for( column = 0; column < TABLE_NUM_COLUMNS; column++ )

TblSetItemStyle( table, count, column, customTableItem );

}

else

// CH.8 Hide unused rows if any

TblSetRowUsable( table, count, false );

}

// CH.8 Draw the table

TblDrawTable( table );

一旦表的类型确定,通过命令TblDrawTable()将表画出来。

值得注意的是,使用TblSetRowUsable()函数可以在浏览表时,只显示所览数据库的一列,这种方法的缺点是它比我们后面章节使用的方法要耗费更多的内存。

添加drawCell()函数

通过前面的准备,现在终于可以调用我们定制函数drawCell()了,每次它都会在表中绘制一条目(Item)。下面是函数的开始部分:

// CH.8 The custom drawing routine for a table cell

static void drawCell( VoidPtr table, Word row, Word column,

RectanglePtr bounds )

{

Int record;

CharPtr precord;

Char string[DB_FIRST_NAME_SIZE + DB_LAST_NAME_SIZE];

SWord width;

SWord len;

Boolean noFit;

由于这个函数是通过调用TblSetCustomDrawProcedure()设置的回馈(CallBack)函数,所以它的参数和返回值就由此而决定。我们会从中得到表的指针、每一单元的行和列、每一单元在窗体上的矩形框。

// CH.8 Calculate our record

record = cursor + row;

// CH.8 Get our record

hrecord = DmQueryRecord( contactsDB, record );

precord = MemHandleLock( hrecord );

// CH.8 Get the date and time

MemMove( &dateTime, precord + DB_DATE_TIME_START,

sizeof( dateTime ) );

首先,我们得到一条和这一行相关联的记录,然后提取日期和时间,使之更容易被输入。

// CH.8 Switch on the column

switch( column )

{

// CH.8 Handle dates

case TABLE_COLUMN_DATE:

{

if( dateTime.year != NO_DATE )

{

DateToAscii( dateTime.month, dateTime.day,

dateTime.year,

(DateFormatType)PrefGetPreference(

prefDateFormat ), string );

}

else

StrCopy( string, "-" );

}

break;

根据列的类型,我们创建了要显示的字符串。对日期来说,所用的函数和列表框中显示时间的函数相同,在没有日期的地方将以短划线表示。

// CH.8 Handle times

case TABLE_COLUMN_TIME:

{

if( dateTime.hour != NO_TIME )

{

TimeToAscii( dateTime.hour, dateTime.minute,

(TimeFormatType)PrefGetPreference(

prefTimeFormat ), string );

}

else

StrCopy( string, "-" );

}

break;

下一列显示时间。它和列表框中显示时间的函数相同,如果没有日期,我们以短划线来代替。

// CH.8 Handle names

case TABLE_COLUMN_NAME:

{

StrCopy( string, precord + DB_FIRST_NAME_START );

StrCat( string, " " );

StrCat( string, precord + DB_LAST_NAME_START );

}

break;

第三列也就是最后一列显示名和姓。我们写入了为单元新建的文本。

// CH.8 Unlock the record

MemHandleUnlock( hrecord );

因为我们已经创建了合适的文本字符串,现在就可以将记录解锁(Unlock)向里面写入了。注意,这种方法没有使用永久(Permanently)内存存储单元数据,因此无论数据库中有多少记录,这个函数都能很好的工作。在表和列表框中都使用订制函数写入数据的好处可见一斑。

// CH.8 Set the text mode

WinSetUnderlineMode( noUnderline );

FntSetFont( stdFont );

// CH.8 Truncate the string if necessary

width = bounds->extent.x;

len = StrLen( string );

noFit = false;

FntCharsInWidth( string, &width, &len, &noFit );

下面,为了使WinDrawChars()能达到我们的要求,必须把文本模式设置好。名字或许不能在屏幕上能显示的空间中完全显示出来,所以需要检查字符串避免不要太长而超出单元显示的范围。如果太长,我们只好去掉多余的部分。事实上,如果你很充分的想象力的话,可以想办法在字符串的末尾添上省略号(……)表示其多余的部分。

// CH.8 Draw the cell

WinEraseRectangle( bounds, 0 );

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

// CH.8 We're done

return;

}

最后,在清除屏幕上以前的内容后,将字符串写入。这样定制函数就完成了。它很容易编写且有极高的灵活性。

调试

当在第一次运行时,最好是单步执行drawTable()和drawCell()函数。如果不关闭表中不用的行,由于drawCell()将试图访问不存在的记录,系统有可能会崩溃。记住在Detail 窗体和表之间不断的切换测试,并且使用下拉框使用不同排序标准进行排序。

Contact List窗体看起来如图8-3所示。

三种滚动条

在Palm OS中普遍使用的有三种滚动条。第一种是滚动按钮,它是一对向上和向下重复按钮,在Enter Time窗体中我们已经使用过,它们可以在所有的Palm OS版本中使用;第二种是滚动条;除了不能在Piolt1000或Piolt5000使用外,在其它Palm OS版本中都可以使用;第三种是PAGE UP和PAGE DOWN键。

下面,我们就将加入资源和代码来支持Contact List窗体中的这三种滚动条。但这样做通常并不是个好主意。

滚动条属性

表8-3是滚动条的属性:

名称 描述

Object Identifier 在资源头文件中,构造器用之代表资源ID

Scrollbar ID 滚动条的ID号。

Left Origin 水平方向上控件的最左端位置

Top Origin 垂直方向上控件的最顶端位置

Width 滚动条的宽度值。

Height 滚动条的高度值。

Usable 定义滚动条是否可见。

Value 滚动条的最初值。

Minimum Value 滚动条的最小值。

Maximum Value 滚动条的最小值。

Page Size 滚动条所关联的行或记事行的每一页的大小,这个用来设置滚动条中Box的大校

Orientation 定义滚动条是水平方向还是垂直方向

添加滚动按钮和滚动条资源

添加两个滚动按钮和一个滚动条来支持三种滚动条类型其中的两种。

1.运行Metrowerks 构造器;

2.打开资源文件Contacts.rsrc,它位于你的项目文件夹中的Src文件夹中;

3.双击打开Contact List窗体;

4.在菜单中选择Window | Catalog,打开Catalog。

5.拖动一个滚动条到窗体中;

6.修改滚动条的属性:Object Identifier=Scrollbar,Left Origin=153,Top Origin=15,Width=7,Height=130。这样滚动条正好在表的最右边,紧靠窗体的右边界。

7.添加滚动按钮。你可以从Enter Time窗体中将滚动按钮拷贝过来,打开Enter Time窗体。从Enter Time窗体中把滚动按钮拖到Contact List窗体中。把向上的箭头的Left Origin设为149,Top Origin为145,将Object Identifier改为RecordUp;把向下的箭头Left Origin设为149,Top Origin为152,将Object Identifier改为RecordDown。

8.Contact List窗体看起来如图8-4。

让滚动按钮工作起来

在例子中,所要做的首要工作是要使游标(cursor)变量与表的顶部位置相等。并且使向上箭头在到达记录的顶部时要变灰,向下按钮在到达记录的底部时变灰。首先在contactListHandleEvent()中加入代码:

// CH.8 Respond to arrows

case ctlRepeatEvent:

{

switch( event->data.ctlRepeat.controlID )

{

// CH.8 Up arrow

case ContactListRecordUpRepeating:

if( cursor > 0 )

cursor--;

break;

// CH.8 Down arrow

case ContactListRecordDownRepeating:

if( (numRecords > TABLE_NUM_ROWS) &&

(cursor < numRecords - TABLE_NUM_ROWS) )

cursor++;

break;

}

// CH.8 Now refresh the table

drawTable();

}

return( true );

这些代码十分简单。注意由于响应重复按钮事件,所以需在ctlRepeatEvent事件中添加代码。对于向上的箭头,每按一次游标中减一;对于向下的箭头,没按一次游标中加一。

为了保证安全,需要检查游标到底能移到什么地方。在绘制表的过程中,我们会重新绘制按纽,或在需要的地方使按钮变得不可用。

为了完成这个操作,在drawTable()的按钮响应事件中添加以下代码:

// CH.8 Get pointers to the arrow buttons

upArrow = getObject( form, ContactListRecordUpRepeating );

downArrow = getObject( form, ContactListRecordDownRepeating );

// CH.8 Update the arrow buttons and scrollbars

if( numRecords > TABLE_NUM_ROWS )

{

// CH.8 Show the up arrow

if( cursor > 0 )

{

CtlSetLabel( upArrow, BLACK_UP_ARROW );

CtlSetEnabled( upArrow, true );

}

else

{

CtlSetLabel( upArrow, GRAY_UP_ARROW );

CtlSetEnabled( upArrow, false );

}

CtlShowControl( upArrow );

// CH.8 Show the down arrow

if( cursor >= numRecords - TABLE_NUM_ROWS )

{

CtlSetLabel( downArrow, GRAY_DOWN_ARROW );

CtlSetEnabled( downArrow, false );

}

else

{

CtlSetLabel( downArrow, BLACK_DOWN_ARROW );

CtlSetEnabled( downArrow, true );

}

CtlShowControl( downArrow );

// CH.8 Show the scrollbar

FrmShowObject( form, FrmGetObjectIndex( form,

ContactListScrollbarScrollBar ) );

SclSetScrollBar( getObject( form,

ContactListScrollbarScrollBar ), cursor, 0,

numRecords - TABLE_NUM_ROWS, TABLE_NUM_ROWS );

}

else

{

// CH.8 Hide the arrows

CtlHideControl( upArrow );

CtlHideControl( downArrow );

// CH.8 Hide the scrollbar

FrmHideObject( form, FrmGetObjectIndex( form,

ContactListScrollbarScrollBar ) );

}

// CH.8 We're done

return;

}

如果表的位置在开头或末尾,我们将重复按钮打上灰晕使之为不可用。这样就防止了游标被置到一个不存在值。

这样工作就完成了,重复按纽实现了象表的滚动条箭头一样的功能。

对PAGE UP和PAGE DOWN键的支持

为了捕捉PAGE UP和PAGE DOWN键,首先必须在keyDownEvent里添加代码。数学上的知识可以给我们一些提示。在当向上翻页或向下翻页,最好能在页面上留下一条常识的线。移动记录时不应移动到TABLE_NUM_ROWS,而应移动到TABLE_NUM_ROWS-1。由于不能使上下翻页键为不可用,就必须保证在按下它们时不会超出游标的移出范围。此外,游标和numRecords都是无符号的,所以必须在做数学运算前进行检查,避免它们变为负数而指向了不存在的值。这需要对contactListHandleEvent()作一些修改:

// CH.8 Respond to up and down arrow hard keys

case keyDownEvent:

{

switch( event->data.keyDown.chr )

{

// CH.8 Up arrow hard key

case pageUpChr:

if( cursor > TABLE_NUM_ROWS - 1 )

cursor -= TABLE_NUM_ROWS - 1;

else

cursor = 0;

break;

对向上翻页来说,运算相当简单。如果向上翻页没有使记录游标小于零,向上翻一整页;否则,就翻到零记录为止。

// CH.8 Down arrow hard key

case pageDownChr:

if( (numRecords > 2 * TABLE_NUM_ROWS - 1) &&

(cursor < numRecords -

2 * TABLE_NUM_ROWS - 1) )

cursor += TABLE_NUM_ROWS - 1;

else

cursor = numRecords - TABLE_NUM_ROWS;

break;

}

// CH.8 Now refresh the table

drawTable();

}

break;

对向下翻页来说,必须注意,当游标是numRecords减去TABLE_NUM_ROWS后(切记游标是基于零的),表是否已经到了最后的一条记录。所以首要的是检查翻页是否超出了最后一个记录。首先,保证表中有足够的记录在从numRecords中减去它后仍是一个正数。然后再检查游标是否到了最后一条记录。如果没有,向下翻一个整页。如果已超过了最后一条记录,翻到最后一条记录为止。

在程序的最后,和滚动按钮程序一样重新绘制表。完成这些后就可以支持翻页键了。

设计滚动条

滚动条需要在事件处理和订制程序中都添加一小段代码,首先来看一下事件处理中的代码:

// CH.8 Respond to scrollbar events

case sclRepeatEvent:

cursor = event->data.sclExit.newValue;

drawTable();

break;

使游标和新的滚动条值相等,就可以响应滚动条滚动事件。如果正确地设置了滚动条滚动的范围,就能保证不会使游标得到错误的值。在为游标赋值后,和其它的滚动条类型一样,需要刷新表和滚动条。

下面,看看添加在drawTable()中的代码。代码添加在滚动按钮代码中的if(numRecords>TABLE_NUM_ROWS)声明后面:

// CH.8 Show the scrollbar

FrmShowObject( form, FrmGetObjectIndex( form,

ContactListScrollbarScrollBar ) );

SclSetScrollBar( getObject( form,

ContactListScrollbarScrollBar ), cursor, 0,

numRecords - TABLE_NUM_ROWS, TABLE_NUM_ROWS );

在这里显示了滚动条,并将其设置了精确的值。因为我们已知道表中存在的记录比可见的行数多,所以numRecords-TABLE_NUM_ROWS不会产生一个错误的结果。

但如果不是这样,而是存在的记录比可见的行数要少,就要隐藏滚动条:

// CH.8 Hide the scrollbar

FrmHideObject( form, FrmGetObjectIndex( form,

ContactListScrollbarScrollBar ) );

}

支持滚动条的代码修改就完成了。

调试

和以前一样,首先调试刚刚添加的代码。另外,将所有的滚动条值移动到第一个和最后一个记录上,次数不要太少(一些记录不会显示)或太多(系统会崩溃的)。

Contact List窗体看起来如图8-5。

下一步做什么

在下一章中,我们将通过在Contacts中添加其他的一些很出色的函数,如系统查找、分类、保密记录等,来结束本书的基础知识部分。

清单

这是经过这一章修改后的Contacts.c:

// CH.2 The super-include for the Palm OS

#include <Pilot.h>

// CH.5 Added for the call to GrfSetState()

#include <Graffiti.h>

// CH.3 Our resource file

#include "Contacts_res.h"

// CH.4 Prototypes for our event handler functions

static Boolean contactDetailHandleEvent( EventPtr event );

static Boolean aboutHandleEvent( EventPtr event );

static Boolean enterTimeHandleEvent( EventPtr event );

static Boolean contactListHandleEvent( EventPtr event );

static Boolean menuEventHandler( EventPtr event );

// CH.4 Constants for ROM revision

#define ROM_VERSION_2 0x02003000

#define ROM_VERSION_MIN ROM_VERSION_2

// CH.5 Prototypes for utility functions

static void newRecord( void );

static VoidPtr getObject( FormPtr, Word );

static void setFields( void );

static void getFields( void );

static void setText( FieldPtr, CharPtr );

static void getText( FieldPtr, VoidPtr, Word );

static void setDateTrigger( void );

static void setTimeTrigger( void );

static void setTimeControls( void );

static Int sortFunc( CharPtr, CharPtr, Int );

static void drawTable( void );

static void drawCell( VoidPtr table, Word row, Word column,

RectanglePtr bounds );

// CH.5 Our open database reference

static DmOpenRef contactsDB;

static ULong numRecords;

static UInt cursor;

static Boolean isDirty;

static VoidHand hrecord;

// CH.5 Constants that define the database record

#define DB_ID_START 0

#define DB_ID_SIZE (sizeof( ULong ))

#define DB_DATE_TIME_START (DB_ID_START +/

DB_ID_SIZE)

#define DB_DATE_TIME_SIZE (sizeof( DateTimeType ))

#define DB_FIRST_NAME_START (DB_DATE_TIME_START +/

DB_DATE_TIME_SIZE)

#define DB_FIRST_NAME_SIZE 16

#define DB_LAST_NAME_START (DB_FIRST_NAME_START +/

DB_FIRST_NAME_SIZE)

#define DB_LAST_NAME_SIZE 16

#define DB_PHONE_NUMBER_START (DB_LAST_NAME_START +/

DB_LAST_NAME_SIZE)

#define DB_PHONE_NUMBER_SIZE 16

#define DB_RECORD_SIZE (DB_PHONE_NUMBER_START +/

DB_PHONE_NUMBER_SIZE)

// CH.6 Storage for the record's date and time in expanded form

static DateTimeType dateTime;

static Word timeSelect;

#define NO_DATE 0

#define NO_TIME 0x7fff

// CH.7 The error exit macro

#define errorExit(alert) { ErrThrow( alert ); }

// CH.7 The sort order variable and constants

static Int sortBy;

// CH.7 NOTE: These items match the popup list entries!

#define SORTBY_DATE_TIME 0

#define SORTBY_FIRST_NAME 1

#define SORTBY_LAST_NAME 2

// CH.8 Table constants

#define TABLE_NUM_COLUMNS 3

#define TABLE_NUM_ROWS 11

#define TABLE_COLUMN_DATE 0

#define TABLE_COLUMN_TIME 1

#define TABLE_COLUMN_NAME 2

#define BLACK_UP_ARROW "/x01"

#define BLACK_DOWN_ARROW "/x02"

#define GRAY_UP_ARROW "/x03"

#define GRAY_DOWN_ARROW "/x04"

// CH.2 The main entry point

DWord PilotMain( Word cmd, Ptr, Word )

{

DWord romVersion; // CH.4 ROM version

FormPtr form; // CH.2 A pointer to our form structure

EventType event; // CH.2 Our event structure

Word error; // CH.3 Error word

// CH.4 Get the ROM version

romVersion = 0;

FtrGet( sysFtrCreator, sysFtrNumROMVersion, &romVersion );

// CH.4 If we are below our minimum acceptable ROM revision

if( romVersion < ROM_VERSION_MIN )

{

// CH.4 Display the alert

FrmAlert( LowROMVersionErrorAlert );

// CH.4 PalmOS 1.0 will continuously re-launch this app

// unless we switch to another safe one

if( romVersion < ROM_VERSION_2 )

{

AppLaunchWithCommand( sysFileCDefaultApp,

sysAppLaunchCmdNormalLaunch, NULL );

}

return( 0 );

}

// CH.2 If this is not a normal launch, don't launch

if( cmd != sysAppLaunchCmdNormalLaunch )

return( 0 );

// CH.5 Create a new database in case there isn't one

if( ((error = DmCreateDatabase( 0, "ContactsDB-PPGU", 'PPGU', 'ctct',

false )) != dmErrAlreadyExists) && (error != 0) )

{

// CH.5 Handle db creation error

FrmAlert( DBCreationErrorAlert );

return( 0 );

}

// CH.5 Open the database

contactsDB = DmOpenDatabaseByTypeCreator( 'ctct', 'PPGU',

dmModeReadWrite );

// CH.5 Get the number of records in the database

numRecords = DmNumRecords( contactsDB );

// CH.5 Initialize the record number

cursor = 0;

// CH.7 Choose our starting page

// CH.5 If there are no records, create one

if( numRecords == 0 )

{

newRecord();

FrmGotoForm( ContactDetailForm );

}

else

FrmGotoForm( ContactListForm );

// CH.7 Begin the try block

ErrTry {

// CH.2 Our event loop

do

{

// CH.2 Get the next event

EvtGetEvent( &event, -1 );

// CH.2 Handle system events

if( SysHandleEvent( &event ) )

continue;

// CH.3 Handle menu events

if( MenuHandleEvent( NULL, &event, &error ) )

continue;

// CH.4 Handle form load events

if( event.eType == frmLoadEvent )

{

// CH.4 Initialize our form

switch( event.data.frmLoad.formID )

{

// CH.4 Contact Detail form

case ContactDetailForm:

form = FrmInitForm( ContactDetailForm );

FrmSetEventHandler( form, contactDetailHandleEvent );

break;

// CH.4 About form

case AboutForm:

form = FrmInitForm( AboutForm );

FrmSetEventHandler( form, aboutHandleEvent );

break;

// CH.6 Enter Time form

case EnterTimeForm:

form = FrmInitForm( EnterTimeForm );

FrmSetEventHandler( form, enterTimeHandleEvent );

break;

// CH.7 Contact List form

case ContactListForm:

form = FrmInitForm( ContactListForm );

FrmSetEventHandler( form, contactListHandleEvent );

break;

}

FrmSetActiveForm( form );

}

// CH.2 Handle form events

FrmDispatchEvent( &event );

// CH.2 If it's a stop event, exit

} while( event.eType != appStopEvent );

// CH.7 End the try block and do the catch block

}

ErrCatch( errorAlert )

{

// CH.7 Display the appropriate alert

FrmAlert( errorAlert );

} ErrEndCatch

// CH.5 Close all open forms

FrmCloseAllForms();

// CH.5 Close the database

DmCloseDatabase( contactsDB );

// CH.2 We're done

return( 0 );

}

// CH.4 Our Contact Detail form handler function

static Boolean contactDetailHandleEvent( EventPtr event )

{

FormPtr form; // CH.3 A pointer to our form structure

VoidPtr precord; // CH.6 Points to a database record

// CH.3 Get our form pointer

form = FrmGetActiveForm();

// CH.4 Parse events

switch( event->eType )

{

// CH.4 Form open event

case frmOpenEvent:

{

// CH.2 Draw the form

FrmDrawForm( form );

// CH.5 Draw the database fields

setFields();

}

break;

// CH.5 Form close event

case frmCloseEvent:

{

// CH.5 Store away any modified fields

getFields();

}

break;

// CH.5 Parse the button events

case ctlSelectEvent:

{

// CH.5 Store any field changes

getFields();

switch( event->data.ctlSelect.controlID )

{

// CH.5 First button

case ContactDetailFirstButton:

{

// CH.5 Set the cursor to the first record

if( cursor > 0 )

cursor = 0;

}

break;

// CH.5 Previous button

case ContactDetailPrevButton:

{

// CH.5 Move the cursor back one record

if( cursor > 0 )

cursor--;

}

break;

// CH.5 Next button

case ContactDetailNextButton:

{

// CH.5 Move the cursor up one record

if( cursor < (numRecords - 1) )

cursor++;

}

break;

// CH.5 Last button

case ContactDetailLastButton:

{

// CH.5 Move the cursor to the last record

if( cursor < (numRecords - 1) )

cursor = numRecords - 1;

}

break;

// CH.5 Delete button

case ContactDetailDeleteButton:

{

// CH.5 Remove the record from the database

DmRemoveRecord( contactsDB, cursor );

// CH.5 Decrease the number of records

numRecords--;

// CH.5 Place the cursor at the first record

cursor = 0;

// CH.5 If there are no records left, create one

if( numRecords == 0 )

newRecord();

}

break;

// CH.5 New button

case ContactDetailNewButton:

{

// CH.5 Create a new record

newRecord();

}

break;

// CH.7 Done button

case ContactDetailDoneButton:

{

// CH.7 Load the contact list

FrmGotoForm( ContactListForm );

}

break;

// CH.6 Date selector trigger

case ContactDetailDateSelTrigger:

{

// CH.6 Initialize the date if necessary

if( dateTime.year == NO_DATE )

{

DateTimeType currentDate;

// CH.6 Get the current date

TimSecondsToDateTime( TimGetSeconds(),

&currentDate );

// CH.6 Copy it

dateTime.year = currentDate.year;

dateTime.month = currentDate.month;

dateTime.day = currentDate.day;

}

// CH.6 Pop up the system date selection form

SelectDay( selectDayByDay, &(dateTime.month),

&(dateTime.day), &(dateTime.year),

"Enter Date" );

// CH.6 Get the record

hrecord = DmQueryRecord( contactsDB, cursor );

// CH.6 Lock it down

precord = MemHandleLock( hrecord );

// CH.6 Write the date time field

DmWrite( precord, DB_DATE_TIME_START, &dateTime,

sizeof( DateTimeType ) );

// CH.6 Unlock the record

MemHandleUnlock( hrecord );

// CH.6 Mark the record dirty

isDirty = true;

}

break;

// CH.6 Time selector trigger

case ContactDetailTimeSelTrigger:

{

// CH.6 Pop up our selection form

FrmPopupForm( EnterTimeForm );

}

break;

}

// CH.5 Sync the current record to the fields

setFields();

}

break;

// CH.5 Respond to field tap

case fldEnterEvent:

isDirty = true;

break;

// CH.3 Parse menu events

case menuEvent:

return( menuEventHandler( event ) );

break;

}

// CH.2 We're done

return( false );

}

// CH.4 Our About form event handler function

static Boolean aboutHandleEvent( EventPtr event )

{

FormPtr form; // CH.4 A pointer to our form structure

// CH.4 Get our form pointer

form = FrmGetActiveForm();

// CH.4 Respond to the Open event

if( event->eType == frmOpenEvent )

{

// CH.4 Draw the form

FrmDrawForm( form );

}

// CH.4 Return to the calling form

if( event->eType == ctlSelectEvent )

{

FrmReturnToForm( 0 );

// CH.4 Always return true in this case

return( true );

}

// CH.4 We're done

return( false );

}

// CH.6 Our Enter Time form event handler function

static Boolean enterTimeHandleEvent( EventPtr event )

{

FormPtr form; // CH.6 A form structure pointer

static DateTimeType oldTime; // CH.6 The original time

// CH.6 Get our form pointer

form = FrmGetActiveForm();

// CH.6 Switch on the event

switch( event->eType )

{

// CH.6 Initialize the form

case frmOpenEvent:

{

// CH.6 Store the time value

oldTime = dateTime;

// CH.6 Draw it

FrmDrawForm( form );

// CH.6 Set the time controls

setTimeControls();

}

break;

// CH.6 If a button was repeated

case ctlRepeatEvent:

// CH.6 If a button was pushed

case ctlSelectEvent:

{

Word buttonID; // CH.6 The ID of the button

// CH.6 Set the ID

buttonID = event->data.ctlSelect.controlID;

// CH.6 Switch on button ID

switch( buttonID )

{

// CH.6 Hours button

case EnterTimeHoursPushButton:

// CH.6 Minute Tens button

case EnterTimeMinuteTensPushButton:

// CH.6 Minute Ones button

case EnterTimeMinuteOnesPushButton:

{

// CH.6 If no time was set

if( dateTime.hour == NO_TIME )

{

// CH.6 Set the time to 12 PM

dateTime.hour = 12;

dateTime.minute = 0;

// CH.6 Set the controls

setTimeControls();

}

// CH.6 Clear the old selection if any

if( timeSelect )

CtlSetValue( getObject( form, timeSelect ),

false );

// CH.6 Set the new selection

CtlSetValue( getObject( form, buttonID ), true );

timeSelect = buttonID;

}

break;

// CH.6 Up button

case EnterTimeTimeUpRepeating:

{

// CH.6 If there's no time, do nothing

if( dateTime.hour == NO_TIME )

break;

// CH.6 Based on what push button is selected

switch( timeSelect )

{

// CH.6 Increase hours

case EnterTimeHoursPushButton:

{

// CH.6 Increment hours

dateTime.hour++;

// CH.6 If it was 11 AM, make it 12 AM

if( dateTime.hour == 12 )

dateTime.hour = 0;

// CH.6 If it was 11 PM, make it 12 PM

if( dateTime.hour == 24 )

dateTime.hour = 12;

}

break;

// CH.6 Increase tens of minutes

case EnterTimeMinuteTensPushButton:

{

// CH.6 Increment minutes

dateTime.minute += 10;

// CH.6 If it was 5X, roll over

if( dateTime.minute > 59 )

dateTime.minute -= 60;

}

break;

// CH.6 Increase minutes

case EnterTimeMinuteOnesPushButton:

{

// CH.6 Increment minutes

dateTime.minute++;

// CH.6 If it is zero, subtract ten

if( (dateTime.minute % 10) == 0 )

dateTime.minute -= 10;

}

break;

}

// Revise the controls

setTimeControls();

}

break;

// CH.6 Down button

case EnterTimeTimeDownRepeating:

{

// CH.6 If there's no time, do nothing

if( dateTime.hour == NO_TIME )

break;

// CH.6 Based on what push button is selected

switch( timeSelect )

{

// CH.6 Decrease hours

case EnterTimeHoursPushButton:

{

// CH.6 Decrement hours

dateTime.hour--;

// CH.6 If it was 12 AM, make it 11 AM

if( dateTime.hour == -1 )

dateTime.hour = 11;

// CH.6 If it was 12 PM, make it 11 PM

if( dateTime.hour == 11 )

dateTime.hour = 23;

}

break;

// CH.6 Decrease tens of minutes

case EnterTimeMinuteTensPushButton:

{

// CH.6 Decrement minutes

dateTime.minute -= 10;

// CH.6 If it was 0X, roll over

if( dateTime.minute < 0 )

dateTime.minute += 60;

}

break;

// CH.6 Decrease minutes

case EnterTimeMinuteOnesPushButton:

{

// CH.6 Decrement minutes

dateTime.minute--;

// CH.6 If it is 9, add ten

if( (dateTime.minute % 10) == 9 )

dateTime.minute += 10;

// CH.6 If less than zero, make it 9

if( dateTime.minute < 0 )

dateTime.minute = 9;

}

break;

}

// CH.6 Revise the controls

setTimeControls();

}

break;

// CH.6 AM button

case EnterTimeAMPushButton:

{

// CH.6 If no time was set

if( dateTime.hour == NO_TIME )

{

// CH.6 Set the time to 12 AM

dateTime.hour = 0;

dateTime.minute = 0;

// CH.6 Set the controls

setTimeControls();

}

// CH.6 If it is PM

if( dateTime.hour > 11 )

{

// CH.6 Change to AM

dateTime.hour -= 12;

// CH.6 Set the controls

setTimeControls();

}

}

break;

// CH.6 PM button

case EnterTimePMPushButton:

{

// CH.6 If no time was set

if( dateTime.hour == NO_TIME )

{

// CH.6 Set the time to 12 PM

dateTime.hour = 12;

dateTime.minute = 0;

// CH.6 Set the controls

setTimeControls();

}

// CH.6 If it is AM

if( dateTime.hour < 12 )

{

// CH.6 Change to PM

dateTime.hour += 12;

// CH.6 Set the controls

setTimeControls();

}

}

break;

// CH.6 No Time checkbox

case EnterTimeNoTimeCheckbox:

{

// CH.6 If we are unchecking the box

if( dateTime.hour == NO_TIME )

{

// CH.6 Set the time to 12 PM

dateTime.hour = 12;

dateTime.minute = 0;

// CH.6 Set the controls

setTimeControls();

// CH.6 Set the new selection

timeSelect = EnterTimeHoursPushButton;

CtlSetValue( getObject( form, timeSelect ),

true );

}

else

// CH.6 If we are checking the box

dateTime.hour = NO_TIME;

// CH.6 Set the controls

setTimeControls();

}

break;

// CH.6 Cancel button

case EnterTimeCancelButton:

{

// CH.6 Restore time

dateTime = oldTime;

// CH.6 Return to calling form

FrmReturnToForm( 0 );

}

// CH.6 Always return true

return( true );

// CH.6 OK button

case EnterTimeOKButton:

{

VoidPtr precord; // CH.6 Points to the record

// CH.6 Lock it down

precord = MemHandleLock( hrecord );

// CH.6 Write the date time field

DmWrite( precord, DB_DATE_TIME_START, &dateTime,

sizeof( DateTimeType ) );

// CH.6 Unlock the record

MemHandleUnlock( hrecord );

// CH.6 Mark the record dirty

isDirty = true;

// CH.6 Return to the Contact Details form

FrmReturnToForm( 0 );

// CH.6 Update the field

setTimeTrigger();

}

// CH.6 Always return true

return( true );

}

}

break;

}

// CH.6 We're done

return( false );

}

// CH.7 Our Contact List form event handler function

static Boolean contactListHandleEvent( EventPtr event )

{

FormPtr form; // CH.7 A form structure pointer

// CH.7 Get our form pointer

form = FrmGetActiveForm();

// CH.7 Parse events

switch( event->eType )

{

// CH.7 Form open event

case frmOpenEvent:

{

// CH.7 Draw the form

FrmDrawForm( form );

// CH.8 Populate and draw the table

drawTable();

}

break;

// CH.7 Respond to a list selection

case tblSelectEvent:

{

// CH.7 Set the database cursor to the selected contact

cursor += event->data.tblSelect.row;

// CH.7 Go to contact details

FrmGotoForm( ContactDetailForm );

}

break;

// CH.7 Respond to a menu event

case menuEvent:

return( menuEventHandler( event ) );

// CH.7 Respond to the popup trigger

case popSelectEvent:

{

// CH.7 If there is no change, we're done

if( sortBy == event->data.popSelect.selection )

return( true );

// CH.7 Modify sort order variable

sortBy = event->data.popSelect.selection;

// CH.7 Sort the contact database by the new criteria

DmQuickSort( contactsDB, (DmComparF*)sortFunc, sortBy );

// CH.8 Rebuild the table

drawTable();

}

break;

// CH.8 Respond to arrows

case ctlRepeatEvent:

{

switch( event->data.ctlRepeat.controlID )

{

// CH.8 Up arrow

case ContactListRecordUpRepeating:

if( cursor > 0 )

cursor--;

break;

// CH.8 Down arrow

case ContactListRecordDownRepeating:

if( (numRecords > TABLE_NUM_ROWS) &&

(cursor < numRecords - TABLE_NUM_ROWS) )

cursor++;

break;

}

// CH.8 Now refresh the table

drawTable();

}

return( true );

// CH.8 Respond to up and down arrow hard keys

case keyDownEvent:

{

switch( event->data.keyDown.chr )

{

// CH.8 Up arrow hard key

case pageUpChr:

if( cursor > TABLE_NUM_ROWS - 1 )

cursor -= TABLE_NUM_ROWS - 1;

else

cursor = 0;

break;

// CH.8 Down arrow hard key

case pageDownChr:

if( (numRecords > 2 * TABLE_NUM_ROWS - 1) &&

(cursor < numRecords -

2 * TABLE_NUM_ROWS - 1) )

cursor += TABLE_NUM_ROWS - 1;

else

cursor = numRecords - TABLE_NUM_ROWS;

break;

}

// CH.8 Now refresh the table

drawTable();

}

break;

// CH.8 Respond to scrollbar events

case sclRepeatEvent:

cursor = event->data.sclExit.newValue;

drawTable();

break;

} // CH.7 End of the event switch statement

// CH.7 We're done

return( false );

}

// CH.3 Handle menu events

Boolean menuEventHandler( EventPtr event )

{

FormPtr form; // CH.3 A pointer to our form structure

Word index; // CH.3 A general purpose control index

FieldPtr field; // CH.3 Used for manipulating fields

// CH.3 Get our form pointer

form = FrmGetActiveForm();

// CH.3 Erase the menu status from the display

MenuEraseStatus( NULL );

// CH.4 Handle options menu

if( event->data.menu.itemID == OptionsAboutContacts )

{

// CH.4 Pop up the About form as a Dialog

FrmPopupForm( AboutForm );

return( true );

}

// CH.3 Handle graffiti help

if( event->data.menu.itemID == EditGraffitiHelp )

{

// CH.3 Pop up the graffiti reference based on

// the graffiti state

SysGraffitiReferenceDialog( referenceDefault );

return( true );

}

// CH.3 Get the index of our field

index = FrmGetFocus( form );

// CH.3 If there is no field selected, we're done

if( index == noFocus )

return( false );

// CH.3 Get the pointer of our field

field = FrmGetObjectPtr( form, index );

// CH.3 Do the edit command

switch( event->data.menu.itemID )

{

// CH.3 Undo

case EditUndo:

FldUndo( field );

break;

// CH.3 Cut

case EditCut:

FldCut( field );

break;

// CH.3 Copy

case EditCopy:

FldCopy( field );

break;

// CH.3 Paste

case EditPaste:

FldPaste( field );

break;

// CH.3 Select All

case EditSelectAll:

{

// CH.3 Get the length of the string in the field

Word length = FldGetTextLength( field );

// CH.3 Sound an error if appropriate

if( length == 0 )

{

SndPlaySystemSound( sndError );

return( false );

}

// CH.3 Select the whole string

FldSetSelection( field, 0, length );

}

break;

// CH.3 Bring up the keyboard tool

case EditKeyboard:

SysKeyboardDialogV10();

break;

}

// CH.3 We're done

return( true );

}

// CH.5 This function creates and initializes a new record

static void newRecord( void )

{

VoidPtr precord; // CH.5 Pointer to the record

// CH.7 Create the database record and get a handle to it

if( (hrecord = DmNewRecord( contactsDB, &cursor,

DB_RECORD_SIZE )) == NULL )

errorExit( MemoryErrorAlert );

// CH.5 Lock down the record to modify it

precord = MemHandleLock( hrecord );

// CH.5 Clear the record

DmSet( precord, 0, DB_RECORD_SIZE, 0 );

// CH.6 Initialize the date and time

MemSet( &dateTime, sizeof( dateTime ), 0 );

dateTime.year = NO_DATE;

dateTime.hour = NO_TIME;

DmWrite( precord, DB_DATE_TIME_START, &dateTime,

sizeof( DateTimeType ) );

// CH.5 Unlock the record

MemHandleUnlock( hrecord );

// CH.5 Clear the busy bit and set the dirty bit

DmReleaseRecord( contactsDB, cursor, true );

// CH.5 Increment the total record count

numRecords++;

// CH.5 Set the dirty bit

isDirty = true;

// CH.5 We're done

return;

}

// CH.5 A time saver: Gets object pointers based on their ID

static VoidPtr getObject( FormPtr form, Word objectID )

{

Word index; // CH.5 The object index

// CH.5 Get the index

index = FrmGetObjectIndex( form, objectID );

// CH.5 Return the pointer

return( FrmGetObjectPtr( form, index ) );

}

// CH.5 Gets the current database record and displays it

// in the detail fields

static void setFields( void )

{

FormPtr form; // CH.5 The contact detail form

CharPtr precord; // CH.5 A record pointer

Word index; // CH.5 The object index

// CH.5 Get the contact detail form pointer

form = FrmGetActiveForm();

// CH.5 Get the current record

hrecord = DmQueryRecord( contactsDB, cursor );

// CH.6 Initialize the date and time variable

precord = MemHandleLock( hrecord );

MemMove( &dateTime, precord + DB_DATE_TIME_START,

sizeof( dateTime ) );

// CH.6 Initialize the date control

setDateTrigger();

// CH.6 Initialize the time control

setTimeTrigger();

// CH.5 Set the text for the First Name field

setText( getObject( form, ContactDetailFirstNameField ),

precord + DB_FIRST_NAME_START );

// CH.5 Set the text for the Last Name field

setText( getObject( form, ContactDetailLastNameField ),

precord + DB_LAST_NAME_START );

// CH.5 Set the text for the Phone Number field

setText( getObject( form, ContactDetailPhoneNumberField ),

precord + DB_PHONE_NUMBER_START );

MemHandleUnlock( hrecord );

// CH.5 If the record is already dirty, it's new, so set focus

if( isDirty )

{

// CH.3 Get the index of our field

index = FrmGetObjectIndex( form, ContactDetailFirstNameField );

// CH.3 Set the focus to the First Name field

FrmSetFocus( form, index );

// CH.5 Set upper shift on

GrfSetState( false, false, true );

}

// CH.5 We're done

return;

}

// CH.5 Puts any field changes in the record

static void getFields( void )

{

FormPtr form; // CH.5 The contact detail form

// CH.5 Get the contact detail form pointer

form = FrmGetActiveForm();

// CH.5 Turn off focus

FrmSetFocus( form, -1 );

// CH.5 If the record has been modified

if( isDirty )

{

CharPtr precord; // CH.5 Points to the DB record

// CH.7 Detach the record from the database

DmDetachRecord( contactsDB, cursor, &hrecord );

// CH.5 Lock the record

precord = MemHandleLock( hrecord );

// CH.5 Get the text for the First Name field

getText( getObject( form, ContactDetailFirstNameField ),

precord, DB_FIRST_NAME_START );

// CH.5 Get the text for the Last Name field

getText( getObject( form, ContactDetailLastNameField ),

precord, DB_LAST_NAME_START );

// CH.5 Get the text for the Phone Number field

getText( getObject( form, ContactDetailPhoneNumberField ),

precord, DB_PHONE_NUMBER_START );

// CH.7 Find the proper position

cursor = DmFindSortPosition( contactsDB, precord, NULL,

(DmComparF*)sortFunc, sortBy );

// CH.5 Unlock the record

MemHandleUnlock( hrecord );

// CH.7 Reattach the record

DmAttachRecord( contactsDB, &cursor, hrecord, NULL );

}

// CH.5 Reset the dirty bit

isDirty = false;

// CH.5 We're done

return;

}

// CH.5 Set the text in a field

static void setText( FieldPtr field, CharPtr text )

{

VoidHand hfield; // CH.5 Handle of field text

CharPtr pfield; // CH.5 Pointer to field text

// CH.5 Get the current field handle

hfield = FldGetTextHandle( field );

// CH.5 If we have a handle

if( hfield != NULL )

{

// CH.5 Resize it

if( MemHandleResize( hfield, StrLen( text ) + 1 ) != 0 )

errorExit( MemoryErrorAlert );

}

else

// CH.5 Allocate a handle for the string

{

hfield = MemHandleNew( StrLen( text ) + 1 );

if( hfield == NULL )

errorExit( MemoryErrorAlert );

}

// CH.5 Lock it

pfield = MemHandleLock( hfield );

// CH.5 Copy the string

StrCopy( pfield, text );

// CH.5 Unlock it

MemHandleUnlock( hfield );

// CH.5 Give it to the field

FldSetTextHandle( field, hfield );

// CH.5 Draw the field

FldDrawField( field );

// CH.5 We're done

return;

}

// CH.5 Get the text from a field

static void getText( FieldPtr field, VoidPtr precord, Word offset )

{

CharPtr pfield; // CH.5 Pointer to field text

// CH.5 Get the text pointer

pfield = FldGetTextPtr( field );

// CH.5 Copy it

DmWrite( precord, offset, pfield, StrLen( pfield ) );

// CH.5 We're done

return;

}

// CH.6 Set the Contact Detail date selector trigger

static void setDateTrigger( void )

{

FormPtr form; // CH.5 The contact detail form

// CH.6 Get the contact detail form pointer

form = FrmGetActiveForm();

// CH.6 If there is no date

if( dateTime.year == NO_DATE )

{

CtlSetLabel( getObject( form, ContactDetailDateSelTrigger ),

" " );

}

else

// CH.6 If there is a date

{

Char dateString[dateStringLength];

// CH.6 Get the date string

DateToAscii( dateTime.month, dateTime.day, dateTime.year,

(DateFormatType)PrefGetPreference( prefDateFormat ), dateString );

// CH.6 Set the selector trigger label

CtlSetLabel( getObject( form, ContactDetailDateSelTrigger ),

dateString );

}

// CH.6 We're done

return;

}

// CH.6 Set the Contact Detail time selector trigger

static void setTimeTrigger( void )

{

FormPtr form; // CH.5 The contact detail form

// CH.6 Get the contact detail form pointer

form = FrmGetActiveForm();

// CH.6 If there's no time

if( dateTime.hour == NO_TIME )

{

CtlSetLabel( getObject( form, ContactDetailTimeSelTrigger ),

" " );

}

else

// CH.6 If there is a time

{

Char timeString[timeStringLength];

// CH.6 Get the time string

TimeToAscii( dateTime.hour, dateTime.minute,

(TimeFormatType)PrefGetPreference( prefTimeFormat ), timeString );

// CH.6 Set the selector trigger label

CtlSetLabel( getObject( form, ContactDetailTimeSelTrigger ),

timeString );

}

// CH.6 We're done

return;

}

// CH.6 Set the controls in the Enter Time form based on dateTime

static void setTimeControls( void )

{

FormPtr form;

ControlPtr hourButton;

ControlPtr minuteTensButton;

ControlPtr minuteOnesButton;

ControlPtr amButton;

ControlPtr pmButton;

ControlPtr noTimeCheckbox;

Char labelString[3];

SWord hour;

// CH.6 Get the form

form = FrmGetActiveForm();

// CH.6 Get the control pointers

hourButton = getObject( form, EnterTimeHoursPushButton );

minuteTensButton = getObject( form,

EnterTimeMinuteTensPushButton );

minuteOnesButton = getObject( form,

EnterTimeMinuteOnesPushButton );

amButton = getObject( form, EnterTimeAMPushButton );

pmButton = getObject( form, EnterTimePMPushButton );

noTimeCheckbox = getObject( form, EnterTimeNoTimeCheckbox );

// CH.6 If there is a time

if( dateTime.hour != NO_TIME )

{

// CH.6 Update the hour

hour = dateTime.hour % 12;

if( hour == 0 )

hour = 12;

CtlSetLabel( hourButton,

StrIToA( labelString, hour ) );

// CH.6 Update the minute tens

CtlSetLabel( minuteTensButton,

StrIToA( labelString, dateTime.minute / 10 ) );

// CH.6 Update the minute ones

CtlSetLabel( minuteOnesButton,

StrIToA( labelString, dateTime.minute % 10 ) );

// CH.6 Update AM

CtlSetValue( amButton, (dateTime.hour < 12) );

// CH.6 Update PM

CtlSetValue( pmButton, (dateTime.hour > 11) );

// CH.6 Uncheck the no time checkbox

CtlSetValue( noTimeCheckbox, false );

}

else

// If there is no time

{

// CH.6 Update the hour

CtlSetValue( hourButton, false );

CtlSetLabel( hourButton, "" );

// CH.6 Update the minute tens

CtlSetValue( minuteTensButton, false );

CtlSetLabel( minuteTensButton, "" );

// CH.6 Update the minute ones

CtlSetValue( minuteOnesButton, false );

CtlSetLabel( minuteOnesButton, "" );

// CH.6 Update AM

CtlSetValue( amButton, false );

// CH.6 Update PM

CtlSetValue( pmButton, false );

// CH.6 Uncheck the no time checkbox

CtlSetValue( noTimeCheckbox, true );

}

// CH.6 We're done

return;

}

// CH.7 This function is called by Palm OS to sort records

static Int sortFunc( CharPtr precord1, CharPtr precord2, Int sortBy )

{

Int sortResult;

// CH.7 Switch based on sort criteria

switch( sortBy )

{

// CH.7 Sort by date and time

case SORTBY_DATE_TIME:

{

DateTimePtr pdateTime1;

DateTimePtr pdateTime2;

Long lDiff;

pdateTime1 = (DateTimePtr)(precord1 + DB_DATE_TIME_START);

pdateTime2 = (DateTimePtr)(precord2 + DB_DATE_TIME_START);

// CH.7 Compare the dates and times

lDiff = (Long)(TimDateTimeToSeconds( pdateTime1 ) / 60 ) -

(Long)(TimDateTimeToSeconds( pdateTime2 ) / 60 );

// CH.7 Date/time #1 is later

if( lDiff > 0 )

sortResult = 1;

else

// CH.7 Date/time #2 is later

if( lDiff < 0 )

sortResult = -1;

else

// CH.7 They are equal

sortResult = 0;

}

break;

// CH.7 Sort by first name

case SORTBY_FIRST_NAME:

{

sortResult = StrCompare( precord1 + DB_FIRST_NAME_START,

precord2 + DB_FIRST_NAME_START );

}

break;

// CH.7 Sort by last name

case SORTBY_LAST_NAME:

{

sortResult = StrCompare( precord1 + DB_LAST_NAME_START,

precord2 + DB_LAST_NAME_START );

}

break;

}

// CH.7 We're done

return( sortResult );

}

// CH.8 Draw our list of choices using a table object

static void drawTable( void )

{

FormPtr form;

TablePtr table;

Int column;

Int count;

ControlPtr upArrow;

ControlPtr downArrow;

// CH.8 Get the form pointer

form = FrmGetActiveForm();

// CH.8 Get the table pointer

table = getObject( form, ContactListTableTable );

// CH.8 For all columns

for( column = 0; column < TABLE_NUM_COLUMNS; column++ )

{

// CH.8 Set the draw routine

TblSetCustomDrawProcedure( table, column, drawCell );

// CH.8 Make the column visible

TblSetColumnUsable( table, column, true );

}

// CH.8 Initialize the table styles

for( count = 0; count < TABLE_NUM_ROWS; count++ )

{

// CH.8 If there is data

if( count < numRecords )

{

// CH.8 Show the row

TblSetRowUsable( table, count, true );

// CH.8 Set the cell styles

for( column = 0; column < TABLE_NUM_COLUMNS; column++ )

TblSetItemStyle( table, count, column, customTableItem );

}

else

// CH.8 Hide unused rows if any

TblSetRowUsable( table, count, false );

}

// CH.8 Draw the table

TblDrawTable( table );

// CH.8 Get pointers to the arrow buttons

upArrow = getObject( form, ContactListRecordUpRepeating );

downArrow = getObject( form, ContactListRecordDownRepeating );

// CH.8 Update the arrow buttons and scrollbars

if( numRecords > TABLE_NUM_ROWS )

{

// CH.8 Show the up arrow

if( cursor > 0 )

{

CtlSetLabel( upArrow, BLACK_UP_ARROW );

CtlSetEnabled( upArrow, true );

}

else

{

CtlSetLabel( upArrow, GRAY_UP_ARROW );

CtlSetEnabled( upArrow, false );

}

CtlShowControl( upArrow );

// CH.8 Show the down arrow

if( cursor >= numRecords - TABLE_NUM_ROWS )

{

CtlSetLabel( downArrow, GRAY_DOWN_ARROW );

CtlSetEnabled( downArrow, false );

}

else

{

CtlSetLabel( downArrow, BLACK_DOWN_ARROW );

CtlSetEnabled( downArrow, true );

}

CtlShowControl( downArrow );

// CH.8 Show the scrollbar

FrmShowObject( form, FrmGetObjectIndex( form,

ContactListScrollbarScrollBar ) );

SclSetScrollBar( getObject( form,

ContactListScrollbarScrollBar ), cursor, 0,

numRecords - TABLE_NUM_ROWS, TABLE_NUM_ROWS );

}

else

{

// CH.8 Hide the arrows

CtlHideControl( upArrow );

CtlHideControl( downArrow );

// CH.8 Hide the scrollbar

FrmHideObject( form, FrmGetObjectIndex( form,

ContactListScrollbarScrollBar ) );

}

// CH.8 We're done

return;

}

// CH.8 The custom drawing routine for a table cell

static void drawCell( VoidPtr table, Word row, Word column,

RectanglePtr bounds )

{

Int record;

CharPtr precord;

Char string[DB_FIRST_NAME_SIZE + DB_LAST_NAME_SIZE];

SWord width;

SWord len;

Boolean noFit;

// CH.8 Calculate our record

record = cursor + row;

// CH.8 Get our record

hrecord = DmQueryRecord( contactsDB, record );

precord = MemHandleLock( hrecord );

// CH.8 Get the date and time

MemMove( &dateTime, precord + DB_DATE_TIME_START,

sizeof( dateTime ) );

// CH.8 Switch on the column

switch( column )

{

// CH.8 Handle dates

case TABLE_COLUMN_DATE:

{

if( dateTime.year != NO_DATE )

{

DateToAscii( dateTime.month, dateTime.day,

dateTime.year,

(DateFormatType)PrefGetPreference(

prefDateFormat ), string );

}

else

StrCopy( string, "-" );

}

break;

// CH.8 Handle times

case TABLE_COLUMN_TIME:

{

if( dateTime.hour != NO_TIME )

{

TimeToAscii( dateTime.hour, dateTime.minute,

(TimeFormatType)PrefGetPreference(

prefTimeFormat ), string );

}

else

StrCopy( string, "-" );

}

break;

// CH.8 Handle names

case TABLE_COLUMN_NAME:

{

StrCopy( string, precord + DB_FIRST_NAME_START );

StrCat( string, " " );

StrCat( string, precord + DB_LAST_NAME_START );

}

break;

}

// CH.8 Unlock the record

MemHandleUnlock( hrecord );

// CH.8 Set the text mode

WinSetUnderlineMode( noUnderline );

FntSetFont( stdFont );

// CH.8 Truncate the string if necessary

width = bounds->extent.x;

len = StrLen( string );

noFit = false;

FntCharsInWidth( string, &width, &len, &noFit );

// CH.8 Draw the cell

WinEraseRectangle( bounds, 0 );

WinD

本文地址:http://www.45fan.com/a/question/73509.html
Tags: 教程 开发 PalmOS
编辑:路饭网
关于我们 | 联系我们 | 友情链接 | 网站地图 | Sitemap | App | 返回顶部