|
|
本月,Two for the Road 专栏将我带到了阳光明媚的加利福尼亚南部海滩,在这里,我试图在冲浪和 Pocket PC 开发之间保持平衡,做到劳逸结合。开始本打算在这两者之间保持完全的平衡,但是我很快惊异地发现 iPaq 在冲浪板上根本无法使用,因此我只能进行单调的冲浪运动了。谁曾想会是这样呢?您或许会想,Compaq 可能已在用户手册中对此问题提出了某种警告。 对于此问题,你们不必多虑,如果我能编写一个移动应用程序就好了,这样就可以打消那些开发同仁们的疑虑“Roof 这家伙将 iPaq 带到冲浪板上去到底想干什么?”。好吧,我承认,我需要帮助。一身古铜色的皮肤和一方滴着盐水的引人注目的镇纸石,这就是我此次旅行的唯一收获。 好了,有关我遇到的技术问题这一话题就到此为止吧。现在,让我们开始讨论移动数据的问题。与我上个月教您构建的移动字幕应用程序有所不同,那个程序的实用性和简单程度简直令人难以置信,您将要构建的大多数 Pocket PC 应用程序最终或多或少都要涉及到数据问题。 移动数据选项 开发移动应用程序的关键在于如何处理选项及其相关的权衡问题。这同样适用于将数据集成到移动应用程序这一主题。对于数据存储,您可以有以下选项: 顺序存取、随机存取和二进制文件:这些久经验证的方法,使得将数据合并到应用程序变得非常简单。其优点在于:根据您的需要,它们可以变得非常简单,也可以相当复杂。如果使用得当,它们能够提供最好的性能。不足之处在于:它们往往成为专用的解决方案,不易合并到企业环境。 Pocket Access 数据库:不要被 Pocket Access 这个名字所误导。这种移动数据方案与功能强大的桌面产品 Microsoft Access 差别很大。Pocket Access 缺乏窗体、报表、存储查询以及表之间的关系。简而言之,它只提供对一组表的存储和访问。另外,它还与 Microsoft Access 兼容,支持数据同步,提供一些简单的工具用于将表在设备间移动,而且可以使用 ADOCE(即 ActiveX® 数据对象的移动版本)对它进行操作(这一点以后再讨论)。 注意: Pocket Access 表的扩展名为 .cdf。 SQL Server 2000 Windows CE 版:目前功能最全面、性能最稳定的移动数据库非 SQL Server CE 莫属。它提供的关系数据库所占空间很小,但包含了一个查询处理器和一些合并复制功能。和 Pocket Access 一样,您可以使用 ADOCE 对 SQL Server CE 数据库进行操作。事实上,要将移动应用程序从用于 Pocket Access 转变为用于 SQL Server CE,通常需要对代码进行小小的改动。 注意: SQL Server CE 数据库的扩展名为 .sdf。 SQL Server CE 最适合于那些专门用来与基于企业的 SQL Server 数据库交互的应用程序。不过,尽管其性能卓越,它却不能和 Pocket Access 一样支持简单的 ActiveSync® 和 ADOCE API。 什么是 ADOCE? 使用 ADOCE,就可以从移动应用程序访问 Pocket Access 和 SQL Server CE 数据库。ADOCE 提供 ADO 功能的子集。它包含自己的内部数据库提供程序,通过该程序,您可以对存储在 Pocket PC 本地的数据库进行操作。使用 ADOCE,还可以通过 OLEDB 提供程序来访问数据源。 ADOCE 的主要优点在于:您可以使用自己的桌面 ADO 编码技术来开发移动应用程序。有关 ADOCE 支持哪些功能的详细信息,请查阅 eMbedded Visual Basic® (eVB) 中附带的帮助。 本月的应用程序:Mobile Sales 我在撰写本文时,意识到上个月的应用程序 Hello World 字幕具有明显的适销性,以后的应用程序要想达到这一境界恐怕很难。于是,我决定在本月的应用程序 Mobile Sales 中加点新鲜内容。尽管它可能不如 Hello World 字幕那样有吸引力,却说明了如何利用 ADOCE 将存储在 Pocket Access 数据库中的数据合并到移动解决方案。 Mobile Sales 展示了使用 Pocket PC 很容易就能使交货过程自动化。使用 Mobile Sales,交货人员能够快速填写每一地点订购的订单。这样,当他们进货时,只需要选择要添加到订单的每个项目的类型和数量。该数据存储在 Pocket PC 上的表中,可以从它下次连接的设备处检索该表。 Mobile Sales 由以下两个组件组成: 桌面交货应用程序 移动条目应用程序 我将带您一步步浏览这两个应用程序,以便在浏览之后,您对分发、使用和收集移动应用程序生成的数据这整个过程能有一个全面的了解。 Mobile Sales 数据库 用于 Mobile Sales 的 Microsoft Access 数据库包含四个表: Customers:Customers 表专门用于填充设备应用程序中的组合框。该表在设备上是只读的。 Products:Products 表用于填充设备应用程序中的组合框,也是每个产品的定价信息源。该表在设备上是只读的。 Route:Route 表包含司机每天预定要访问的客户列表。根据这种情况,每天要用一组新客户来生成该表。该表在设备上是只读的。 Orders:Orders 表用于存储在字段中输入的订单信息。它在设计上非常简单,表中的每个记录代表客户订购的一个项目。 “Mobile Sales - 桌面”组件 “Mobile Sales - 桌面”组件负责将数据库传输到设备,然后从设备检索回单个 Orders 表。其界面非常简单,如下图所示。 图 1:“Mobile Sales - 桌面”界面 “Mobile Sales – 桌面”使用 ADOCE API 来执行这些传输。ADOCE 提供了程序方法,用于将表传输到设备或从设备传输表。ADOCE API 简单得令人难以置信。它只包含两个函数:DESKTOPTODEVICE 和 DEVICETODESKTOP。 DESKTOPTODEVICE 用于将表从 Microsoft Access 桌面数据库复制到 Pocket Access 设备数据库。该函数的语法为: DESKTOPTODEVICE(DesktopLocn, TableList, Sync, Overwrite, DeviceLocn) 其中: DesktopLocn 是包含要传输的表的 Access 数据库的路径和文件名。您还可以将 DSN 用作该值。 TableList 是要复制的表和字段的列表。 该参数的格式为: [!]表名.字段名。 前导 ! 用于指定表是只读的。 Sync 定义 ActiveSync 是否应维护桌面和设备数据库之间的同步。 Overwrite 指定是否应覆盖任何现有的表。 DeviceLocn 是指设备上的 Pocket Access 数据库的路径和文件名。 DEVICETODESKTOP:该函数用于将表从 Pocket Access 设备数据库复制到 Microsoft Access 桌面数据库。该函数的语法为: DEVICETODESKTOP(DesktopLocn, TableList, Sync, Overwrite, DeviceLocn) DEVICETODESKTOP 函数的参数与 DESKTOPTODEVICE 函数中所用参数完全相同。 下载 Mobile Sales 数据 使用 DESKTOPTODEVICE 函数之前,必须在“Mobile Sales – 桌面”窗体模块的“一般声明”部分声明此函数,如下所示: Private Declare Function DESKTOPTODEVICE Lib _ "c:\Program Files\Microsoft ActiveSync\adofiltr.dll" _ (ByVal desktoplocn As String, _ ByVal tablelist As String, _ ByVal sync As Boolean, _ ByVal overwrite As Integer, _ ByVal devicelocn As String) As Long 然后,使用以下代码将这四个表从桌面 Microsoft Access 数据库复制到设备 Pocket Access 数据库: lResult = DESKTOPTODEVICE(App.Path & "\Mobile Sales.mdb", _ "!Customers..!Products..!Route..Orders..", False, True, _ "\tonked\Mobile Sales\Mobile Sales.cdb") 注意: 该代码位于示例的 mnuFunctionsDownload 单击事件中。 在此处,正在将 Customers、Products、Route 和 Orders 表复制到设备。其中 Customers、Products 和 Route 这三个表是只读的,其名称前标有“!” 。在这一过程中将覆盖任何现有的表。 上载订单数据 使用 DEVICETODESKTOP 函数之前,也必须在“Mobile Sales – 桌面”窗体模块的“一般声明”部分声明此函数。 Private Declare Function DEVICETODESKTOP Lib _ "c:\program files\Microsoft ActiveSync\adofiltr.dll" _ (ByVal desktoplocn As String, _ ByVal tablelist As String, _ ByVal sync As Boolean, _ ByVal overwrite As Integer, _ ByVal devicelocn As String) As Long 然后使用以下代码将 Orders 表从设备 Pocket Access 数据库复制到桌面 Microsoft Access 数据库: lResult = DEVICETODESKTOP(App.Path & "\Mobile Sales.mdb", _ "Orders..", False, True, "\tonked\Mobile Sales\Mobile Sales.cdb") 注意: 该代码位于示例的 mnuFunctionsUpload 单击事件中。 桌面组件概述 该组件展示了将数据移到设备或从设备移回数据实现起来很简单。利用 ADOCE API,您可以轻松地传输 Microsoft Access 表。 注意: 您可以使用示例附带的 Microsoft Access 数据库中的 Today's Orders 报表来生成订单报表,如下图所示: 图 2:订单报表 运行报表之前,您应该执行以下步骤: 使用“Mobile Sales – 桌面”组件,将数据库下载到您的设备。 运行“Mobile Sales – 设备”组件,处理一些客户订单。 使用“Mobile Sales – 桌面”组件,从您的设备上载 Orders 表。 在 Microsoft Access 中,运行 Today's Orders 报表。 “Mobile Sales - 设备”组件 “Mobile Sales – 设备”组件展示了创建移动数据条目应用程序很容易。其界面如下图所示: 图 3:“Mobile Sales - 设备”界面 在“Mobile Sales – 设备”界面的顶部有一个组合框,显示司机今天将要访问的客户。该组合框从 Route 表加载。 紧挨着该框下面是第二个组合框,其中存放司机要运送的产品列表。该信息从 Products 表加载。 再往下,还有两个控件:文本框和水平滚动条。这两个组件为一前一后输入数据提供了便利。 注意: 凭我的经验,尽量不要让用户使用 SIP。否则,您以后每年都可能收到他们寄送的圣诞贺卡,因为他们太高兴了。 紧接着是两个命令按钮,使用它们可在订单中添加或删除项目。 再往下是 ListView 控件,用于维护客户的订单。我喜欢使用该控件代替网格,因为它提供了更为灵活、有用的界面。 最后,在界面的底部是菜单栏控件。 用于支持“Mobile Sales - 设备”组件的代码 使用 ADOCE 时,您需要做的第一件事就是在工程中添加对 Microsoft CE ADO Control 的引用。要添加引用,请执行以下操作: 在 Project 菜单下,选择 References 从 References 对话框中,选择 Microsoft CE ADO Control 3.0 图 4:“引用”对话框 注意: 如果您的开发程序计算机上已安装了 SQL Server CE,您会发现存在 Microsoft CE ADO Control 的两个版本:3.0 和 3.1(如上图所示)。使用 SQL Server CE 时,请确保选择 3.1 版。而对于 Pocket Access,选择其中任何一个 ADOCE Control 版本都可以。 “窗体加载”事件 完成这一切之后,我就可以开始使用数据了。在“窗体加载”事件中,我建立了到数据库的连接。 ' 连接到数据库。 Set cnMobileSales = CreateObject("ADOCE.Connection.3.0") cnMobileSales.Open "data source=" & App.Path & "\Mobile Sales.cdb" ADOCE 连接对象会提供 Errors 集合,您可以对该集合进行查询,以确定 ADOCE 操作是否成功。 ' 连接是否已成功? If cnMobileSales.Errors.Count > 0 Then MsgBox "连接到数据库时出错。" & vbCrLf & _ "错误" & Err.Number & vbCrLf & _ Err.Description, _ vbCritical, "打开数据库" Set cnMobileSales = Nothing On Error GoTo 0 App.End End If 接着,我将加载 Customer 和 Product 这两个组合框控件。调用 LoadComboBox 例程会执行加载操作。 ' 加载客户和产品信息。 LoadComboBox cmbCustomers, "Customers", "CustomerID", _ "CustomerName", True LoadComboBox cmbProducts, "Products", "ProductID", _ "Description", False LoadComboBox 例程 通过该例程,我们首先来看一下如何对存储在 Pocket Access 表中的数据进行操作。LoadComboBox 例程可分成两个部分。首先,我使用了简单的 SQL SELECT 语句创建一个记录集。我将要检索的表会作为参数传送到 LoadComboBox 例程。 ' 构建所有信息的记录集。 strSQL = "SELECT * FROM " & strTableName Set rsTable = CreateObject("ADOCE.Recordset.3.0") rsTable.Open strSQL, cnMobileSales, adOpenDynamic, _ adLockOptimistic If (Err.Number) Then MsgBox "检索信息时" & strTableName & _ "出错。", vbCritical, "填写组合框" On Error GoTo 0 Exit Sub End If 加载指定的组合框控件时,只需要对最终生成的记录集进行迭代处理,每次处理一个记录。我将要添加到组合框的字段会作为参数传送到 LoadComboBox 例程。继而,每个项目的唯一 ID 会存储在 ItemData 属性中。这样,我们以后就能在应用程序中快速检索特定的字段。存放 ID 的字段也会作为参数传送到该例程。 请注意在以下代码示例中,记录集 MoveNext 方法用于遍历所有记录。ADOCE 记录集对象支持 MoveNext、MovePrevious、MoveFirst 和 MoveLast 方法以及 BOF 和 EOF 属性,这与您在桌面应用程序中已经熟悉的那些用法完全一样。 ' 将信息放入组合框。 Do While Not (rsTable.EOF) ' 将名称放入组合框。 cmbObject.AddItem rsTable(strNameColumn).Value ' 为刚添加的名称将 ID 号放入 ' itemdata 元素中。 cmbObject.ItemData(cmbObject.NewIndex) = _ CLng(rsTable(strIDColumn).Value) ' 尝试移到下一个记录。 rsTable.MoveNext Loop “添加项目单击”事件 cmdAdd 按钮的这一单击事件过程包含如何使用 ADOCE 的下一个示例。在此例程中,首先将产品添加到 ListView 控件,然后调用 GetProductInfo 例程,该例程会从 Pocket Access 数据库检索有关特定产品的信息。继而,该信息会被加载到 ListView 控件。 ' 将当前项目添加到列表。 Set objDetails = lvwOrder.ListItems.Add(, , _ cmbProducts.List(cmbProducts.ListIndex)) ' 获取当前产品的有关信息并将 ' 该信息添加到当前行(即刚刚 ' 添加的行)。 GetProductInfo cmbProducts.ItemData(cmbProducts.ListIndex), _ curProductCost objDetails.SubItems(1) = curProductCost objDetails.SubItems(2) = txtQuantity.Text objDetails.SubItems(3) = cmbProducts.ItemData(cmbProducts.ListIndex) GetProductInfo 例程 GetProductInfo 例程使用 ADOCE 从 Pocket Access 数据库检索有关特定产品的信息。它使用带 Product ID 的 SQL SELECT 语句,Product ID 将作为参数传送到该例程。GetProductInfo 然后将请求的产品费用返回给调用它的例程。 ' 查找指定产品的记录。 strSQL = "SELECT Cost FROM Products " & _ "WHERE ProductID = " & lngProductID Set rsProduct = cnMobileSales.Execute(strSQL) ' 查看是否出现错误。 If (Err.Number <> 0) Then MsgBox "尝试检索产品信息时" & _ "出错。" & vbCrLf & _ "错误号" & Err.Number & ", " & _ Err.Description, vbCritical, "数据库错误" curProductCost = 0 Else curProductCost = rsProduct.Fields("Cost") End If SaveOrder 例程 到目前为止,我一直在集中讲述如何从 Pocket Access 数据库检索数据。在某些地方,您可能发现所述的内容也可用于保存数据。这就是 SaveOrder 例程的主要用途。 SaveOrder 例程展示了一个使用 ADOCE 将记录添加到数据库的方法。它使用 SQL INSERT 语句。另外,您还可以使用记录集对象的 AddNew 方法。 ' 存储该订单的各个项目。 For intCounter = 1 To lvwOrder.ListItems.Count ' 将信息放入数据库。 strSQL = "INSERT INTO Orders (CustomerID, ProductID," & _ " Quantity) VALUES (" & _ cmbCustomers.ItemData(cmbCustomers.ListIndex) & ", " & _ lvwOrder.ListItems(intCounter).SubItems(3) & ", " & _ lvwOrder.ListItems(intCounter).SubItems(2) & ")" cnMobileSales.Execute strSQL ' 查看是否出现错误。 If (Err.Number <> 0) Then For intErrors = 1 To cnMobileSales.Errors.Count MsgBox cnMobileSales.Errors(intErrors).Number & _ " - " & _ cnMobileSales.Errors(intErrors).Description, _ vbCritical, _ "保存订单" Next intErrors Err.Clear On Error GoTo 0 Exit Sub End If Next intCounter 现在,您应该逐渐意识到:在大多数情况下,使用 ADOCE 的方法与使用 ADO 的方法完全相同。您将发现,能够将已应用于桌面 Visual Basic® 应用程序中的代码和编码技术重新用于使用 eVB 构建的移动应用程序。 使用 SQL Server CE 您可能在想:如果使用 SQL Server CE 而不是 Pocket Access 来存储 Mobile Sales 数据,会有什么区别呢?答案是区别不大。首先,您必须引用 Microsoft CE ADO Control 的 3.1 版。其次,只需更改“窗体加载”事件中提供的连接信息。 Set cnMobileSales = CreateObject("ADOCE.Connection.3.1") cnMobileSales.Open "Provider=Microsoft.SQLSERVER.OLEDB.CE.1.0; " & _ " data source=" & App.Path & "\Mobile Sales.sdf" 而其它的一切都可照搬。这正是 ADOCE 的精妙之处:只要对代码进行小小的改动,就可以轻松地在 Pocket Access 和 SQL Server CE 数据库之间切换。您能够随着应用的增长和需要的改变,快速在这两个数据库之间转换移动应用程序。 Mobile Sales 概述 至此,您已了解使用 eVB 和 ADOCE 能够轻松开发支持移动数据的应用程序。但是这并非您要了解的有关 ADOCE 的全部。如果您愿意花一些时间仔细阅读 eVB 附带的 ADOCE 文档以及 SQL Server CE 附带的其它信息,将会对 ADOCE 有更为全面的认识。这些文档和信息都可从 eVB 开发环境中的“帮助”菜单下获得。 ADOCE 也并不局限用于 eVB 开发程序。它还能用于 eMbedded Visual C++®,尽管我可能说过,eMbedded Visual C++® 无法提供更快的数据存取速度。除非 eVC++ 狂热分子要挺身而出担当起重新编写 ADOCE DLL 的重任,否则您只好“稍微牺牲一下性能”。 注意: 您可能还想看一下 Mobile Sales 应用程序的另一部分(尽管我不打算在此处讲述)。这一部分与 ADOCE 无关,它提供了一些示例,说明如何配置带菜单和按钮的菜单栏控件以及如何对 ListView 控件进行格式化。 旅途偶得 下载此部分的代码示例。 几个星期前,我在华盛顿讲授过如何使用 eVB 开发应用程序。当我们在课堂上谈论 eVB 的方方面面时,我的一个学生 Linda 提出了一个我事先未打算涉及的话题。她想知道如何才能创建一个列表,用户只需要使用简单的拖放方法即可对该列表进行重新排序。Linda 的问题非常有诱惑力,以致当全班同学都忙于进入下一个实验室时,我忍不住启用 eVB 来创建我命名的 Pocket List Ordering Thingy。 图 5:Pocket List Ordering Thingy 您现在可能在想:“Roof,为什么你会一再泄露这些易于销售、商机无限的应用程序呢?”老实说只是为了吊您的胃口,这样,我就能兜售 Hello World Marquee Pro 和 Pocket List Ordering Thingy Special Edition 这两个应用程序了。您不会取笑我居心不良吧?您看,Pocket List Ordering Thingy 这几个单词的首字母合起来正是 PLOT,巧不巧? 对不起,我离题了。让我们回到手头的应用程序上来。PLOT 界面非常简单。它包含一个用于输入新项目的文本框,此外还有两个命令按钮和一个列表框。 实现这一神奇拖放技术的代码基于两个事件过程。第一个事件过程是列表框的“鼠标下移”事件。在此,需要记住用户首次选择的项目。这是因为当用户拖动列表指针时,选定的项目会发生更改。 ' 保存他们要移动的项目。 iStartingItem = lstItems.ListIndex 第二个事件过程是列表框的“鼠标上移”事件。在此处会出现一只小手。现在需要记住指针的位置。这一位置即为我经过时要放置所选项目的地方。 ' 保存目前所处的位置。 iEndingItem = lstItems.ListIndex 下一步,需要检查开始拖动的位置与结束拖动的位置是否不同(毕竟,执行移动后还停留在开始的地方是说不通的)。如果这两个位置不同,则保存起始项目,将它从起始位置删除,然后将它插入所需的结束位置。 ' 查看是否已移动。如果已移动,则进行重新排序。 If (iStartingItem <> lstItems.ListIndex) Then sTemp = lstItems.List(iStartingItem) lstItems.RemoveItem (iStartingItem) lstItems.AddItem sTemp, iEndingItem lstItems.ListIndex = -1 End If 以上就是如何通过拖放来对列表重新排序的全部过程。 返回原路 这就是本月的话题。我希望您能使用 eVB 和 ADOCE 构建支持移动数据的完美应用程序。如果您有什么想法,请给我来信,我的地址是 lroof@tonked.com。现在,我要收拾起我的冲浪板和浸水的 iPaq,向东部进发了。亚特兰大是我旅途的下一站,我将去那里参加 Microsoft Tech-Ed 会议。在会议上,我将讲述如何为 Pocket PC 开发应用程序,包括如何使用 eVC++ 来创建 DLL API 包装。嗨,我能说什么呢,您要编写那些代码(它们可是按小时付费的),就必须借助 eVC++。如果您在亚特兰大,请来找我吧。到时再见吧,现在,我要动身了。
|
|