Delphi开发PocketPC应用程序入门指南
编程乐园
Pocket PC,顾名思义,即是装在口袋里的电脑。它具有自己的操作系统,能够运行众多软件,实现各种功能,例如,上网、管理客户资料、打游戏、看电影等。它已经不再是简单的电子产品,可以说是一台功能强大的“移动电脑”。
Delphi 2005特别提供对.NET框架的支持,是一款优秀的Pocket PC程序开发工具。本文将详细介绍两个实例的开发过程,引领你进入移动应用程序开发的“乐园”。
一、开发前的准备
1.下载开发工具
在进行实际开发以前,我们需要准备一些工具,有了这些工具,才能在Delphi 2005下开发Pocket PC应用程序:
(1)Microsoft Pocket PC 2003 SDK
微软Pocket PC 2003开发包。
(2)Delphi for .NET Compact Framework Technology Preview
Borland官方提供的预览版,也是我们的关键工具,你可以到Borland官方网站下载,下载地址为http://bdn.borland.com/article/0,1410,33066,00.html。
(3)Microsoft .NET Compact Framework
该工具可在微软VS 2003或者MSDN开发包中找到,在后文中,我们称其为CF。
(4)Microsoft WinCE 5.0 Emulator
微软WinCE模拟机,是调试Pocket PC程序必不可少的工具,你可以到微软网站下载,在MSDN开发包中也可以找到。
(5)Windows Mobile 2003 Second Edition Emulator Images for Pocket PC
配合模拟机使用的镜像文件,可以免费到微软网站下载。
(6)Make CF Compatible - Compact Framework project preprocessor
该工具可以方便我们编译Pocket PC程序。你可以到http://cc.borland.com/item.aspx?id=23609下载。
2.配置开发环境
(1)安装Delphi for .NET Compact Framework Technology Preview
直接解压cfpreview.zip后运行Borland Delphi .NET Compact Framework Tech Preview.msi即可进行安装。
(2)安装Microsoft .NET Compact Framework
直接解压到C:\CF目录。
(3)安装Microsoft WinCE 5.0 Emulator
保持默认安装。
(4)安装Windows Mobile 2003 Second Edition Emulator Images for Pocket PC
保持默认安装。
(5)安装Make CF Compatible - Compact Framework project preprocessor
直接解压到C:\CF\Make目录。
二、实例详解——实现Pocket PC软复位
首先,我们要编写一个简单的Pocket PC软复位程序,通过此程序可以让Pocket PC系统重新启动,从而释放内存或者解决一些系统问题。
1.应用程序界面设计
启动Delphi 2005,因为我们使用CF框架,所以新建一个“Delphi for .NET Projects”类别中的“Windows Forms Application”工程。
在窗体上放置一个Label1控件,其“Caption”属性设置为“Hello World!”。再放置一个Button1控件,其“Caption”属性设置为“软复位”。

为了取消“软复位”操作,需要设计一个定时器,延迟3秒执行复位操作,所以我们再添加一个定时器(Timer1),“Interval”属性设置为“3000”。
2.代码编写
要做到软复位操作并不是一件容易的事情,为此微软提供了标准的结构和I/O控制(IOCTL)代码用于向硬件设备直接发送命令,而且必须使用平台调用服务(P/Invoke)来调用非托管的Win32 API函数,即用“KernelIoControl”来实现我们需要的功能。
(1)编写软复位函数
首先,我们在uses中增加“System.Runtime.InteropServices”命名空间的引用,有了它才能调用“coredll.dll”中的函数。
接着,导入动态连接库:
[DllImport('coredll.dll', CharSet = CharSet.Auto, SetLastError = True, EntryPoint = 'KernelIoControl')]
申明“KernelIoControl”函数:
function KernelIoControl(dwIoControlCode: Integer; lpInBuf: IntPtr;
nInBufSize: Integer; lpOutBuf: TBytes; nOutBufSize: Integer;
var lpBytesReturned: Integer): Boolean; external;
这里我们可以看到一些新的变量类型,例如,TBytes,它等同于以前Delphi中的array of Byte,只不过他们是Delphi语言的基本类型(Types)与CLR (Common Language Run-time)的对应关系而已,完整的对照表可以参考Delphi 2005帮助。
上面的函数只能提供向硬件设备发送命令的能力,如果要实现软复位操作,我们还需要构造它的命令参数,也就是“KernelIoControl”函数的第一个参数“dwIoControlCode”。
在C#中有现成的构造函数CTR_CODE,很可惜Delphi for .NET没有,因此需要我们自己编写:
function CTL_CODE(Devicetype, Func, Method, Access: Integer): Integer;
begin
Result:=(Devicetype shl 16) or (Access shl 14) or (Func shl 2) or Method;
end;
要定义IOCTL,需要用到以下常量:
METHOD_BUFFERED = 0;
FILE_ANY_ACCESS = 0;
FILE_DEVICE_HAL = $00000101;
到这里,软复位命令参数即诞生了:
CTL_CODE(FILE_DEVICE_HAL, 15, METHOD_BUFFERED, FILE_ANY_ACCESS);
最后,完整的软复位函数如下:
function ResetDevice: Boolean;
const
METHOD_BUFFERED = 0;
FILE_ANY_ACCESS = 0;
FILE_DEVICE_HAL = $00000101;
var
iRet: Integer;
buffer: TBytes;
IOCTL_HAL_REBOOT: Integer;
begin
IOCTL_HAL_REBOOT := CTL_CODE(FILE_DEVICE_HAL, 15, METHOD_BUFFERED, FILE_ANY_ACCESS);
Result := KernelIoControl(IOCTL_HAL_REBOOT, IntPtr.Zero, 0, buffer, 0, iRet);
end;
我们将复位函数放在Timer事件中延迟3秒执行,3秒内可以取消软复位操作,这里的代码比较简单:
procedure TWinForm1.Timer1_Tick(sender: System.Object; e: System.EventArgs);
begin
Self.Timer1.Enabled := False;
if not ResetDevice then
begin
Self.Button1.Text := '重试';
Self.Label1.Text := '复位失败';
end;
end;
(2)启动计时器
在“软复位”按钮中启动计时器:
procedure TWinForm1.Button1_Click(sender: System.Object; e: System.EventArgs);
begin
if Self.Timer1.Enabled then
begin
Self.Button1.Text := '软复位';
Self.Label1.Text := 'Hello World!';
Self.Timer1.Enabled := False;
end else
begin
Self.Button1.Text := '取消';
Self.Label1.Text := '准备复位';
Self.Timer1.Enabled := True;
end;
end;
至此,程序编写完成,按F9键运行,单击复位按钮,3秒后将显示错误信息,不用担心,这个程序不是给PC机用的。先保存项目为D:\My Documents\Borland Studio Projects\Pocket PC \Project1.bdsproj,在后面我们将用到它。
3.特殊的编译调试
为了让编写好的程序能够运行在Pocket PC中,直接编译运行是不行的,我们需要特殊的方法来实现:
打开C:\CF\Make\MakeCFCompatibleGUI.exe程序,在打开的对话框中选择刚才保存的项目,选中“Launch Notepad”选项,双击“Launch MakeCFCompatible”按钮,运行以后在打开的编译日志文件中,我们会发现很多错误信息,例如,WinForm.pas(52) Error: E2003 Undeclared identifier: 'SuspendLayout'、WinForm.pas(57) Error: E2003 Undeclared identifier: 'Name'等等,这是因为我们使用的精简版.NET Framework,也就是.NET Compact Framework的很多属性是不被支持的原因,这些属性包括Name、TabIndex、TabStop、Index、AutoScaleBaseSize、ResumeLayout、SuspendLayout等。
于是我们需要对代码做少量的修改,按照日志文件中的提示去除代码中的.Name等不被支持的属性;去掉“System.Windows.Forms.Timer.Create;”中后面的参数;在Delphi中单击 “Project→View Source”菜单命令打开Project1.bdsproj代码,去掉代码段中的“[STAThread]”语句。
再次编译,这次应该能看到日志文件中显示“86 lines, 0.36 seconds, 22024 bytes code, 0 bytes data.”的类似内容,OK,到这里我们已经成功编译出了一个地道的Pocket PC程序了。
另外,如果我们没有Make CF Compatible - Compact Framework project preprocessor工具,可以参考Delphi for .NET Compact Framework Technology Preview中cfpreview.zip文件的readme.txt说明,使用命令行方式来编译项目,在这里,我们使用的批处理文件内容如下:
"C:\Program Files\Borland\Bds\3.0\CFPreview\Bin\DCCIL.EXE" Project1.dpr -U"C:\Program Files\Borland\BDS\3.0\CFPreview\lib" -lu"C:\CF\System.dll;C:\CF\System.Xml.dll;C:\CF\System.Windows.Forms.dll;C:\CF\System.Data.dll;C:\CF\System.Drawing.dll"
Pause
将上面的批处理文件保存到项目文件目录,取名“Make.bat”,运行该文件也可以编译出同样的Pocket PC程序。
4.使用虚拟机测试程序
程序编译生成以后,我们需要测试程序是否能正常运行于Pocket PC上,这里我们使用Pocket虚拟机来测试。
为了方便程序测试,我们编写了一个Emulator.bat批处理文件,用来快速调用微软的WinCE虚拟机:
"C:\Program Files\Windows CE 5.0 Emulator\Emulator_500.exe" "C:\Program Files\Pocket PC 2003 Second Edition Emulators\CHS\Pocket_PC\Pocket PC_2003_SE_CHS.bin" /sharedfolder "D:\My Documents\Borland Studio Projects\Pocket PC" /video 240x320x16
Microsoft WinCE 5.0 Emulator可以使用的命令行参数说明如下:
/ceimage:连接到镜像文件。
/sharedfolder:共享的目录,共享以后在虚拟机文件系统的“Storage Card"目录中。
/video:显示分辨率及深度,Pocket PC一般为240×320×16,SmartPhones为176×220×16。
/ethernet:指定网络支持方式(none、shared、virtualswitch)。
/skin:使用XML皮肤文件,一般不需要设置。
运行上面的批处理文件之后可以看到虚拟机已经打开了,单击“开始→程序”菜单项,打开“资源管理器”对话框,选择“我的设备”中的“Storage Card”目录。

提示:如果你找不到“Storage Card”目录,可能是你共享的目录失败,这时你可以在虚拟机中单击“Emulator→Folder Sharing”菜单命令,以重新共享你项目所在的目录。
运行Project1.exe文件,刚才编译的程序出现在Pocket PC的屏幕上。单击“软复位”按钮,3秒钟后系统将自动复位。
三、实例进阶——XML数据库开发
在上面的实例中,我们介绍了Delphi 2005编写简单Pocket PC应用程序。下面我们将重点介绍Delphi for .NET针对CF库编写程序,这里以编写XML数据库程序为例。
1.应用程序界面设计
新建一个Delphi for .NET WinForm项目,在窗体上放置下表所示的组件:


2.绑定单击事件
双击ContextMenu1,增加“复制”、“删除”子菜单。
双击界面中的“复制”按钮,IDE将自动添加Button1.Click事件,然后将MenuItem1的Click事件绑定到“复制”按钮的Click事件上。
MenuItem2也采用同样的方法绑定到“删除”按钮事件,同时设置ListBox组件的“ContextMenu”属性为“ContextMenu1”。
3.创建XML文件
界面设置完成,接下来看看如何操作XML文件当作数据库来处理。首先,我们手工建立一个简单的XML文件,例如:
<?xml version="1.0" standalone="yes"?>
<test>
<item name="许先生">
<tel>139000*****</tel>
</item>
<item name="万先生">
<tel>139000*****</tel>
</item>
<item name="电脑报">
<tel<130000*****</tel>
</item>
</test>
保存上面的文件到项目目录中,取名为“Test.xml”。
在Delphi for .NET中对XML数据进行操作,我们需要理清DataSet、DataTable、XML文件之间的关系。
如下图所示,通过DataSet读取XML数据文件,界面组件如ListBox,TextBox通过DataTable绑定数据字段进行显示及更新:

4.代码编写
双击窗体进入程序的Load事件,首先读入XML文件并绑定到界面组件:
procedure TWinForm.TWinForm_Load(sender: System.Object; e: System.EventArgs);
var
AppPath: String;
begin
AppPath := Assembly.GetExecutingAssembly.GetModules[0].FullyQualifiedName;
AppPath := Path.GetDirectoryName(AppPath);
XmlFileName := AppPath + '\Test.xml';
Self.DataSet1.ReadXML(XmlFileName);
XmlTable := Self.DataSet1.Tables['item'];
Self.ListBox1.DataSource := XmlTable;
Self.ListBox1.DisplayMember := 'name';
Self.TextBox1.DataBindings.Add('Text', XmlTable, 'name');
Self.TextBox2.DataBindings.Add('Text', XmlTable, 'tel');
end;
AppPath是程序运行的目录,要得到此路径,需要访问Assembly及Path成员,它们在System.Reflection及System.IO命名空间中,同时为了读取XML数据及绑定数据还需要引用System.Data及System.XML命名空间。
在我们的程序中,DataSet1读入XML文件,XmlTable(DataTablel类型)架起XML数据与界面ListBox、TextBox的“桥梁”。
在Delphi 2005中,新增加了“重构”功能,可以很容易地将重复使用的代码或功能类似的代码放置到新的函数中。例如,在Load事件中,我们可以将读取XML的功能提取出来放到一个新函数“XmlDataLoad”中。
选中“Self.DataSet1.ReadXML(XmlFileName);”、“Self.TextBox2.DataBindings.Add('Text', XmlTable, 'tel');”之间的代码段,使用Ctrl+Shift+M组合键打开“重构”对话框。这样在Load事件中就只需简单地调用“XmlDataLoad”函数就可以了。

接下来,我们需要添加“复制”和“删除”按钮的单击(Click)事件代码。为了方便,这里直接通过已经绑定了数据的TextBox来新建数据,所以新建的数据和选中的数据是一致的,因此使用了“复制”而不是“新建”,代码如下:
procedure TWinForm.Button1_Click(sender: System.Object; e: System.EventArgs);
var
XmlRow: DataRow;
begin
XmlRow := XmlTable.NewRow; //新建一行
XmlRow['name'] := Self.TextBox1.Text;
XmlRow['tel'] := Self.TextBox2.Text;
XmlTable.Rows.Add(XmlRow); //插入数据表
end;
“删除”按钮代码,如果数据不为空即删除选中行的数据:
procedure TWinForm.Button2_Click(sender: System.Object; e: System.EventArgs);
begin
if Self.ListBox1.SelectedIndex <> -1 then
XmlTable.Rows.RemoveAt(Self.ListBox1.SelectedIndex);
end;
在上面,因为我们已经将弹出菜单的代码绑定到上诉按钮中,所以执行菜单可以得到同样的功能。
“保存”数据只需要将表中的数据写回到XML文件:
procedure TWinForm.Button3_Click(sender: System.Object; e: System.EventArgs);
begin
Self.DataSet1.WriteXml(XmlFileName);
end;
提示:如果使用了“DataColumnCollection”等成员,则需要保存XSD文件,读取和写入方法类似,你只需使用Read/WriteXMLSchema即可。
最后,我们要让TextBox控制WinCE的输入控制面板进行数据输入。在Microsoft.WindowsCE.Forms命名空间中集合了InputPanel组件,我们需要手工填写代码调用它来实现我们的功能。
引用Microsoft.WindowsCE.Forms命名空间,添加申明:
InputPanel1: Microsoft.WindowsCE.Forms.InputPanel;
在程序InitializeComponent事件创建InputPanel组件:
Self.InputPanel1 := Microsoft.WindowsCE.Forms.InputPanel.Create;
当TextBox1获取焦点时打开输入面板,失去焦点关闭输入面板,这里需要注意,在Delphi环境中TextBox没有GotFocus/ LostFocus事件,我们只能手工输入:
procedure TWinForm.TextBox1_GotFocus(sender: System.Object; e: System.EventArgs);
begin
Self.InputPanel1.Enabled := True; //打开输入面板
end;
procedure TWinForm.TextBox1_LostFocus(sender: System.Object; e: System.EventArgs);
begin
XmlTable.AcceptChanges; //更新数据
Self.InputPanel1.Enabled := False;
end;
将TextBox2的获取和失去焦点和事件绑定到TextBox1的相同事件上,其方法与“绑定单击事件”操作完全相同。
至此,代码编写完毕,来编译一下我们的程序吧。由于这里使用了输入面板,引用了“Microsoft.WindowsCE.Forms”命名空间,所以Make CF Compatible - Compact Framework project preprocessor编译程序的“DCCIL parameters”参数或者“Make.bat”批处理文件都需要修改一下,增加如下的参数:
-lu" C:\CF\MICROSOFT.WINDOWSCE.FORMS.DLL"
然后根据错误提示把不支持的属性统统删除,由于CF的特殊性,除了第一个例子提到的很多属性不被支持外,很多方法也不太一样,例如,针对菜单或列表组件的AddRange()批量添加节点的方法就需要替换为.Add一个一个添加,例如:
Self.ContextMenu1.MenuItems.Add(Self.MenuItem1);
Self.ContextMenu1.MenuItems.Add(Self.MenuItem2);
而不能使用:
Self.ContextMenu1.MenuItems.AddRange(TArrayOfSystem_Windows_Forms_MenuItem.Create(Self.MenuItem1, Self.MenuItem2));
编译成功后,即可打开虚拟机测试其实际运行效果。
5.实现关闭与最小化
细心的读者可能已经注意到了,我们介绍的两个例子运行在虚拟机上时右上角的按钮不太一样,一个是“X”,一个是“OK”,这是因为“X”表示最小化程序,我们通过WinCE系统的内存管理程序可以看到程序仍然在后台运行,打开WinCE,单击“开始→设置→系统”菜单项,双击打开“内存”对话框。
单击“停止”按钮后程序才真正退出,而“OK”则表示直接关闭程序。因而在编译过程中,如果出现“Fatal: F2039 Could not create output file 'Project1.exe'”错误提示,可能就是程序最小化尚未退出,这时需要打开内存管理器关闭运行中的程序后才可以继续编译调试。
在WinCE系统中,默认是最小化操作,而关闭操作需要设定“MinimizeBox”来实现:
MinimizeBox=True:最小化
MinimizeBox=False:关闭
所以,只要在程序Load事件中加入“MinimizeBox=False”代码即可通过右上角的按钮关闭程序。
四、发布Pocket PC应用程序
发布Pocket PC应用程序即是将程序打包为CAB文件供Pocket PC安装,我们需要用到Microsoft Pocket PC 2003 SDK开发包的打包程序Cabwiz.exe,此开发包可以从微软的网站下载(http://www.microsoft.com/downloads/details.aspx?FamilyID=9996b314-0364-4623-9ede-0b5fbb133652&DisplayLang=en)。如果只是简单地制作CAB文件,只需要编写一个INF文件然后调用Cabwiz.exe即可。
以我们的第一个实例为例,编写一个简单的“Reset.inf”文件,内容如下:
[Version]
Signature = "$Windows NT$"
Provider = "电脑报"
CESignature = "$Windows CE$"
[CEStrings]
AppName = "Reset"
InstallDir = %CE1%\%AppName%
[CEDevice]
VersionMin = 3.000
VersionMax = 4.999
[SourceDisksNames]
1 = ,"App Files",,"D:\My Documents\Borland Studio Projects\Pocket PC"
[DefaultInstall]
CopyFiles = Files.App
CEShortcuts = Shortcuts
[DestinationDirs]
Files.App = 0,%InstallDir%
DefaultDestDir = 0,%InstallDir%
[SourceDisksFiles]
Project1.exe = 1
[Files.App]
"Project1.exe",Project1.exe
[Shortcuts]
"软复位",0,Project1.exe,%CE11%
以上都是必须的信息段,其中%CE*%表示特定的目录:
%CE1% \Program Files
%CE2% \Windows
%CE4% \Windows\StartUp
%CE5% \My Documents
%CE8% \Program Files\Games
%CE11% \Windows\Start Menu\Programs
%CE14% \Windows\Start Menu\Programs\Games
%CE15% \Windows\Fonts
%CE17% \Windows\Start Menu
接下来调用Cabwiz.exe打包文件,这只需要编写一个批处理文件:
"C:\Program Files\Windows CE Tools\wce420\POCKET PC 2003\Tools\Cabwiz.exe" "D:\My Documents\Borland Studio Projects\Pocket PC\Reset.inf"
将其保存为“MakeCAB.bat”,运行该文件即可在当前目录生成“Reset.CAB”包文件,共享“Reset.CAB”文件到虚拟机,运行以后即可看到程序的安装界面。安装完毕,单击“开始→程序”菜单项,会看到多了一个“软复位”的应用程序快捷方式,“Program Files”目录中也多了一个“Reset”应用程序目录,同时在“设置→删除程序”中可以看到安装的“软复位”程序。