UML软件工程组织

从 eMbedded Visual Basic 迁移到 Visual Basic .NET
Microsoft Corporation

2003 年 8 月 

适用于:
   Microsoft® Windows® Pocket PC 2002
   Microsoft® eMbedded Visual Basic®
   Microsoft® .NET Framework
   Microsoft® Visual Basic® .NET
   Microsoft® .NET Compact Framework
   Microsoft® Visual Studio® .NET

摘要:了解如何将 Microsoft Windows Pocket PC 2002 软件开发从 Microsoft eMbedded Visual Basic 迁移到 Microsoft .NET Framework 和 Visual Basic .NET。发布 .NET Compact Framework 之后,在服务器和台式机应用程序开发中使用的语言和工具也可以用于开发移动应用程序。(本文包含一些指向英文站点的链接。)

目录

简介
基本变化
开发变化及改进概述
用户界面和应用程序导航
Pocket PC 和 Smartphone 的开发
应用程序集成
使用数据库
部署和分布
小结

简介

本白皮书探讨如何将 Microsoft® Windows® Pocket PC 2002 的软件开发从 Microsoft® eMbedded Visual Basic® 迁移到 Microsoft® .NET Framework 和 Visual Basic® .NET。发布 Microsoft® .NET Compact Framework 之后,在服务器和台式机应用程序开发中使用的语言和工具也可以用于开发移动应用程序。

.NET Compact Framework 包含 .NET Framework 类型和命名空间的子集。Microsoft® Visual Studio® .NET 环境中还包含一个名为 Smart Device Programmability 的集成部件,它包括窗体设计器、设备仿真器等支持功能,从而允许用户使用 .NET Compact Framework 进行开发。从本质上讲,新的平台以及工具、语言的改进有助于提高移动应用程序在开发、代码、执行和部署方面的质量。下图简要描述了不同的开发构造块之间的关系。

图 1:如何配套使用

基本变化

Microsoft eMbedded Visual Basic 在问世之后就成为了主流的应用程序开发环境。由于它与 Microsoft Visual Basic 6.0 极其相似,而且简单易用,因此吸引了许多开发人员。但是,它在语言和平台相关性方面还存在一些固有的局限性,因此还需要妥善的解决方法、第三方产品,以及调用底层 Windows CE API。这些局限中的大多数在新的环境中都已得到解决,但是有些仍需要您自己解决。最基本的变化就是 Visual Basic .NET 是一种面向对象的现代语言,构建代码要使用公共的 .NET Compact Framework 类库,如用于 Windows 窗体的 System.Windows.Forms 和用于数据库管理的 System.Data,以及它们的公共方法、属性和事件。它本身支持许多常见的任务,如严格类型、使用类、调用 XML Web Service、实现结构化的异常处理等,而这些任务以前在 eMbedded Visual Basic 中需要开发人员花费很多精力才能完成。

移植您的应用程序

对于现有的 eMbedded Visual Basic 代码,并没有自动的升级方法,由于平台和语言的差别较大,因此要成功、有效地设计和实现这种支持比较困难。移植工作主要包括以下几个方面:

  • 语言的语法:eMbedded Visual Basic 是一种 Visual Basic Script 语言,因此语法区别有的很小,有的很大。
  • 公共代码库的实现:需要将现有的 eMbedded Visual Basic 公共代码库导出。由于现在支持使用类,因此在代码库的使用和实现上可能有所不同。而 .NET Compact Framework 是 .NET Framework 的子集,所以在 Pocket PC 的开发中,可以使用现有的 Visual Basic .NET 类库。
  • 应用程序导航和控制流:窗体的管理和应用程序导航通过 System.Windows.Forms 中的类型来处理,其实现方式与 eMbedded Visual Basic 中的实现方式不同。
  • 数据库:通过 ADO.NET 的子集处理数据访问。Microsoft 为 Microsoft SQL Server™ 2000 Windows CE Edition 2.0 (SQL Server CE 2.0) 提供了一个托管数据提供程序。.NET Compact Framework 不包括用于访问本地数据库(有时称为 CEDB 或 Pocket Access)的托管类型,而 eMbedded Visual Basic 开发人员却经常会用到。
  • XML Web Service:eMbedded Visual Basic 本身不支持调用远程组件,调用和使用远程组件需要第三方的支持。对 Web 服务的支持是 .NET Compact Framework 的核心类型之一,被认为是整个 .NET Framework 中的主要集成机制。大多数开发项目中有关系统集成的工作量很大,因此,系统集成代码极有可能要重写。
  • 异常处理:eMbedded Visual Basic 中的错误处理体现在四个词上:“On Error Resume Next”,以及“没完没了”的 If Err.Number <> 0 Then 语句。而使用 Try ... Catch ... Finally 语句块的有效的结构化异常处理,可以增强代码的健壮性和容错能力。

其他概要信息请参阅以下页面:

Microsoft .NET Compact Framework
(http://msdn.microsoft.com/vstudio/device/compact.asp)

Frequently Asked Questions About Microsoft .NET Compact Framework
(http://msdn.microsoft.com/vstudio/device/compactfaq.asp)

Microsoft .NET Framework
(http://msdn.microsoft.com/netframework/)

GotDotNet .NET Smart Device Development
(http://www.gotdotnet.com/team/netcf/default.aspx)

开发变化及改进概述

平台的改进

eMbedded Visual Basic 中的开发在独立的环境中进行,并使用 eMbedded Visual Basic 专用的运行时。用 .NET Compact Framework 代替 Visual Basic 运行时环境,意味着开发人员要重新学习新的平台、语言和工具技能。大部分变化所带来的主要益处来自于“公共”这个词。.NET Compact Framework 与完整的 .NET Framework 有许多共同点,包括:

  • 公共语言运行库 (CLR):所有移动应用程序开发均使用相同的可执行程序运行时环境。CLR 提供程序加载、内存管理和其他的核心操作系统功能(图 2)。
  • 公共类型系统 (CTS):CTS 定义如何在运行时中声明、使用和管理类型,可以通过所有 .NET Framework 语言进行访问。
  • 公共中间语言 (CIL):也称为 Microsoft Intermediate Language (MSIL),CIL 是一套独立于 CPU 的指令集,这些指令集可以有效地转换为本机代码。

图 2:“公共”之一:公共语言运行库

实际的核心框架是操作系统和硬件。但是,这种平台交叉方法并不表示编程工作的各个方面都具备了共同的基础。平台调用(图 3)就是一种可以使托管代码调用动态链接库 (DLL) 中实现的非托管函数的服务,如 Windows CE API 中的非托管函数。幸运的是,Visual Studio .NET 开发环境隐藏了这些复杂内容,只为开发人员提供了合适的元素。开发人员可以在开始新项目时选择目标平台来实现此目的。

图 3:在 Visual Studio .NET 中选择目标平台

开发出的应用程序是实时 (JIT) 编译的,CLR 负责管理内存维护进程 - 内存回收。对于使用 CreateObject 语句并由此可能导致内存泄漏的 eMbedded Visual Basic 开发人员来说,这种内存维护进程或者缺少工作进程已经成为一大问题。从移动解决方案的角度来看,最重要的平台改进体现在连接性。.NET Compact Framework 本身就支持主要的 Internet 协议(TCP IP、HTTP 等)和文档标准(XML、SOAP 等),这就为连接的移动应用程序奠定了基础。

开发环境的改进

eMbedded Visual Basic 编程是在独立的开发环境中完成的。.NET Compact Framework 的开发则是在 Visual Studio .NET 2003 中进行的,这样就可以共享完整的 .NET Framework 开发环境。eMbedded Visual Basic 开发人员可以看到与 Visual Studio 元素类似的元素,注意到编辑器中的 IntelliSense、用户界面设计器、强壮调试,以及直观的编译、部署及调试步骤等。对使用远程 Web 服务和其他 .NET Framework 组件进行的开发,提供了内置的支持,并且无缝地集成到了工具和语言中。

重要的语言改进

从概念上讲,eMbedded Visual Basic 和 Visual Basic .NET 有相同的起源,都是从普通的 Basic 语言进化而来。对现代的 Visual Basic 开发者来说,关键的语言结构和元素并没有改变,重要的是引入了许多新特点。语言方面的改进主要包括:

  • 基于 .NET Compact Framework 类库的开发
  • 支持真正的面向对象设计
  • 结构化的异常处理
  • 多线程功能
  • 与其他语言的集成

但是,.NET Compact Framework 不支持与 COM 的互操作,这就意味着必须重新开发现有 COM 和 ActiveX 组件的包装,并使用平台调用 (P/Invoke) 来调用这些包装。但如果使用 Odyssey Software 提供的第三方解决方案,开发人员则可以在托管代码中无缝地使用 COM 和 ActiveX 组件。

有关不同之处及改进的详细信息,请参阅
Ten Code Conversions for Visual Basics for Applications, Visual Basic .NET , and C# (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnoxpta/html/odc_tencodeconverts.asp)。

用户界面和应用程序导航

这种新的开发工具 (Visual Basic .NET) 将为您带来前所未有的编码体验。由于该开发环境继承了 Visual Basic 和 eMbedded Visual Basic 开发人员所熟悉的很多功能,因此过渡并不十分困难。许多熟悉的功能在新环境中仍然有效,例如双击按钮,然后输入“Click”事件的代码。但从本质来看,改变还是很大的。

在 eMbedded Visual Basic 中,许多步骤是开发人员看不到的,如设计时属性和事件的定义。另外,由于不是真正的面向对象环境,有些类(窗体、控件)在首次使用时会自动实例化。而在 Visual Basic .NET 中,在设计器中对窗体进行的所有处理都反映在代码中。窗体和控件是必须进行声明的对象,并且要在代码中实例化。

窗体的基本知识

现在,让我们来看一个设计器中的窗体示例(图 4)。(请注意,图中只显示了窗体的上半部分。)

图 4:示例窗体

使用窗体设计器创建窗体之后,用来声明窗体中控件的生成代码如下所示:

Friend WithEvents Label1 As System.Windows.Forms.Label
Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
Friend WithEvents Button1 As System.Windows.Forms.Button

用来创建控件的生成代码如下所示:

Me.Label1 = New System.Windows.Forms.Label
Me.TextBox1 = New System.Windows.Forms.TextBox
Me.Button1 = New System.Windows.Forms.Button

最后,设置这些控件的属性的生成代码如下所示:

Me.Label1.Location = New System.Drawing.Point(8,11)
Me.Label1.Size = New System.Drawing.Size(48,16)
Me.Label1.Text = "Label1"

Me.TextBox1.Location = New System.Drawing.Point(56,8)
Me.TextBox1.Size = New System.Drawing.Size(104,22)
Me.TextBox1.Text = "TextBox1"

Me.Button1.Location = New System.Drawing.Point(168,8)
Me.Button1.Size = New System.Drawing.Size(64,24)
Me.Button1.Text = "Button1"
Me.Controls.Add(Me.Button1)
Me.Controls.Add(Me.TextBox1)
Me.Controls.Add(Me.Label1)
Me.Text = "Form1"

每个控件的位置、大小和内容(Text 属性)均被设定,请注意,生成的代码实际上将控件添加到了窗体中。一个很重要的细节是,现在所有的控件都具有 Text 属性(窗体和标签没有 Caption 属性)。

还需要一些控件来保存额外的元数据(像设计时的控件属性和其他资源,如图片),这些数据保存在与窗体同名的资源文件(在以上的示例中为 Form1.resx)中。

为窗体生成的其余代码则创建窗体的基本结构。首先,像声明其他类一样声明窗体,并从 .NET Compact Framework 提供的基本 Windows 类中继承窗体:

Public Class Form1 : Inherits System.Windows.Forms.Form

以上的代码实际是两行,为了便于阅读,将这两个语句放在了一行。创建窗体后,调用构造函数,代码如下所示:

Public Sub New()
MyBase.New()
InitializeComponent()
End Sub

构造函数按要求调用基类构造函数,然后又按要求调用生成的私有函数 (Sub InitializeComponent),该函数的代码用于创建控件并设置属性,如上所示。窗体设计器需要 InitializeComponent 函数,而该函数的代码需要谨慎处理。

开始编写应用程序时,在“Project Properties”(项目属性)对话框中将窗体 Form1 定义为“Startup object”(右击“Solution Explorer”[解决方案资源管理器] 中的项目,选择“Properties”[属性]),定义方法与 eMbedded Visual Basic 中的方法类似。

Pocket PC 和 Smartphone 的开发

事件

设计应用程序中的窗体时,应该为控件创建事件处理程序。与在 eMbedded Visual Basic 中一样,可以双击控件以创建默认的事件处理程序。如果我们继续上面的示例,双击其中的按钮,将生成 Click 事件的事件处理程序:

Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
MessageBox.Show("Text on button is: " & Button1.Text)
End Sub

事件过程的各个参数总是一样的。参数“sender”是对触发事件的对象的引用,参数“e”针对不同的事件类型。它通常包含有关所触发事件的数据,这些数据在 eMbedded Visual Basic 中是独立的事件参数。请注意,声明的结尾有一个特殊的关键字 (Handles),它用于指明使用该事件的控件。一个需要注意的地方是,关键字 Handles 接受多个参数,也就是说,可以利用这一点让同一个事件处理程序来处理多个控件的事件。让我们向窗体再添加一个按钮 (Button2),并修改事件处理程序的代码:

Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click,
Button2.Click
MessageBox.Show("Text on button is: " & CType(sender,
Button).Text)
End Sub

现在,事件处理程序可以处理两个按钮的 Click 事件。请注意,“sender”参数用于获取触发事件的按钮对象。

添加和删除事件的方法与在 eMbedded Visual Basic 中的方法非常相似。要添加其他事件处理程序,只需在代码窗口右上角的方法名称下拉列表中选择即可。要删除事件处理程序,只需删除事件处理代码即可。另外,与在 eMbedded Visual Basic 一样,当把控件从一个窗体复制到另一窗体时,必须手动复制事件处理程序代码。

外观

要迁移到 Visual Basic .NET 的 eMbedded Visual Basic 开发人员经常会问这样一个问题:如何控制任务栏右上角的按钮?创建新窗体时,默认设置将显示一个智能最小化按钮(看起来象一个 X),这个按钮不会关闭窗口,但会将窗口放在后台(不可见,但仍在运行)。这是因为默认情况下窗体的 Minimize 属性设置为 True。由于 ControlBox 属性默认也设置为 True,要显示 OK 按钮,只需将 Minimize 属性设置为 False。如果希望在点击 OK 按钮后、关闭窗体前添加事件(例如验证),可以向窗体添加一个 Closing 事件处理程序。继续以该示例为例,窗体的 Minimize 属性设置为 False,并添加以下的事件处理程序:

Private Sub Form1_Closing(ByVal sender As Object, _
ByVal e As System.ComponentModel.CancelEventArgs) _
Handles MyBase.Closing
If TextBox1.Text <> "TextBox1" Then
MessageBox.Show("Text in TextBox cannot be changed!")
e.Cancel = True
End If
End Sub

该示例显示了如何在 TextBox 中的文字被改变时防止窗体关闭。请注意,示例是如何使用第二个参数 (e) 来处理事件特定的值(属性)的。

另一个需要特别注意的地方是,需要向窗体添加一个菜单(实际上是 MainMenu)控件,以使底部的菜单栏和 SIP (软输入面板)可见。创建新项目时,会在生成的第一个窗体中添加一个菜单控件,但是在添加新窗体时,必须手动添加菜单。

要使您的应用程序看起来与其他 Pocket PC 应用程序一样,还要遵循以下原则:

  • 一定要将应用程序名称放到所有窗体的标题中(窗体的 Text 属性)。
  • 在子窗体中,在窗体的左上角(最好在 X=8,Y=5 的位置)添加一个 Label 控件,其名称与对话框的名称相同。在该标签下面,添加一条单点线,长度等于窗体的宽度。建议使用 Panel 控件,将其高度设置为 1,宽度设置为 240(最好在 X=0,Y=23 的位置)。
  • 要将 Label 控件与 TextBox 控件中的文字对齐,Label 的位置要低于 TextBox 3 个像素(TextBox 的 Y 值加 3)。

有关用户界面的其他注意事项,请参阅 Visual Basic .NET 的帮助文件和通用的 Pocket PC 设计原则 (http://www.microsoft.com/mobile/assets/Design_PocketPC.pdf)。

窗体导航和互操作

只包含一个窗体的应用程序很少见,所以,让我们来看一下如何在不同的窗体间导航,这是很常用的。我们仍使用以上示例,再添加另一个窗体(选择菜单选项“Project”[项目],“Add Windows Form”[添加 Windows 窗体],接受默认名称 Form2)。第一步是添加一个 MainMenu 控件,以使底部菜单栏可见,然后也将窗体的 Minimize 属性改变为 False,以使右上角的 OK 按钮可见。创建子窗体时最好也这样做,这种习惯有助于节省调试时间。

生成新窗体的最简单的方法是将这段代码添加到第一个窗体的按钮上:

Dim secondForm As Form2
secondForm = New Form2
secondForm.ShowDialog()

如前所述,与在 eMbedded Visual Basic 中相反,必须先创建所有类的实例,然后才能使用它们,包括窗体。

根据所加载的窗体的大小,您可能希望在加载窗体的过程中看到等待光标,您可以将下面一行代码放到调用 ShowDialog 的代码之前:

Cursor.Current = Cursors.WaitCursor

然后,应该在窗体加载完毕时删除等待光标。为此,可以在第二个窗体的构造函数中添加以下代码:

Public Sub New()
MyBase.New()
InitializeComponent()
Cursor.Current = Cursors.Default
End Sub

但是,如果第二个窗体 (Form2_Load) 的 Load 事件处理程序包含大量初始化过程,例如加载控件的值,则最好在 Load 事件处理程序的结尾恢复光标。

一个常会遇到的问题是不同窗体间的通信。调用 ShowDialog 可以提供最简单的通信形式。ShowDialog 可以返回任意预定义的结果代码(OK、Cancel、Yes、No、Abort、Retry、Ignore 和 None)。在第一个窗体中,可以将代码更改为:

MessageBox.Show ("You selected: " +
secondForm.ShowDialog().ToString())

这将显示第二个窗体返回的结果代码。有两种方法可以从第二个窗体返回该值:设置按钮的 DialogResult 属性,或者设置窗体本身的 DialogResult 属性。如果在第二个窗体中添加了按钮(名为 btnCancel),可以在构造函数中添加以下代码来设置该属性:

btnCancel.DialogResult = DialogResult.Cancel

请注意,设计器中未提供该属性,只能用代码来设置。如果将第二个窗体的 Minimize 属性改为 False,则将从窗体返回两组结果代码:按右上角的 OK 按钮返回 OK,按 btnCancel 按钮返回 Cancel。

根据通用的 Pocket PC 用户界面设计原则(参阅上文),应用程序应该在“Running Programs”(正在运行的程序)列表(“Memory Setting”[内存设置] 中的选项卡)中只显示一个实例。但是,如果在第一个窗体可见(在第二个下面)的情况下打开第二个窗体,则“Running Programs”(正在运行的程序)列表中将显示这两个窗体。因此,需要在第二个窗体可见时隐藏第一个窗体。在第一个窗体中调用 Hide 方法可以隐藏窗体,方法是在第一个窗体中调用 Me.Hide(),然后调用 ShowDialog 和 Me.Show()。但是,这样做也存在一些问题。如果第二个窗体的加载时间过长,则在窗体的转换过程中将看不到任何窗体(这对应用程序用户来说有些不方便),在上面的例子中,“You selected ... ”消息框就是在不显示任何窗体的情况下显示的。更为可行的处理方法是让第二个窗体隐藏第一个窗体。为此,第二个窗体需要以某种方式接收第一个窗体的实例。要传递此信息,最明显的方法是使用第二个窗体的构造函数。在第二个窗体中,添加一个私有类变量以保存第一个窗体的实例:

Private firstForm As Form1

用新参数更新构造函数,并使用以下代码将该参数保存到私有变量:

Public Sub New(ByVal firstForm As Form1)
Me.firstForm = firstForm
' 其他代码...
End Sub

现在,第一个窗体中用来实例化第二个窗体的代码行变为:

secondForm = New Form2(Me)

这样,第二个窗体现在就“了解”了第一个窗体,并且可以调用它:

firstForm.Hide()

最好在窗体的 Load 事件 (Form2_Load) 结束后再使用它。在窗体的 Closing 事件 (Form2_Closing) 中,可以使用以下代码再次显示第一个窗体:

firstForm.Show()

这样一来,窗体间的过渡就比较平滑,而且过渡不会造成任何性能问题,如不会影响加载或关闭第二个窗体所用的时间。这是一种在窗体间通信的好方法。向第二个窗体的构造函数添加多少个参数都不会出现问题,因此,可以从第一个窗体向第二个窗体传递任意的数据。但是,如何将数据从第二个窗体传递回第一个窗体呢? 我们知道,可以用结果代码通知第一个窗体(如上所述),但是,要传递回数据还需要其他方法。在上面的代码中,您可能已经注意到了变量 firstForm(及其参数)被声明为 Form1,而不是基类型 Form。可以这样做是由于 Hide 和 Show 方法都是基类型 Form 的一部分。但是,如果变量 firstForm 被声明为类型 Form1,则 Form1 类就可以实现一个公共(或相当友好的)方法,能够被第二个窗体调用,以将数据传递回第一个窗体。

我们还以上面的示例为例,将以下方法添加到第一个窗体:

Friend Sub Form2Data(ByVal text As String)
textFromForm2 = text
End Sub

textFromForm2 是 Form1 中被私自声明的类变量。以下是修改后的按钮 Click 事件的代码,此按钮用来加载第二个窗体:

Dim secondForm As Form2
Dim result As DialogResult
Cursor.Current = Cursors.WaitCursor
secondForm = New Form2(Me)
result = secondForm.ShowDialog()
MessageBox.Show("You selected: " + result.ToString())
If result = DialogResult.OK Then
txtFromForm2.Text = textFromForm2
End If

用来显示第二个窗体中的数据的 TextBox 控件名为 txtFromForm2

在第二个窗体中,将以下代码添加到 Closing 事件 (Form2_Closing):

firstForm.Form2Data(TextBox1.Text)

该方法允许将任何数据从第二个窗体传递到第一个窗体。

应用程序导航对于增加应用程序的可用性十分重要。通过学习以上内容,您现在应该了解了在窗体间高效导航和传递数据的方法。

大多数企业移动解决方案是对现有系统的扩展。因此,这种解决方案体系结构面临的主要问题是它如何与现有的 IT 系统集成。

应用程序集成

集成策略

集成包括两个方面:集成模式(如何集成)和连接频率(每隔多久连接一次)。

集成模式

目前,许多商用 Pocket PC 应用程序根本没有实现连接。因此称之为“独立式”应用程序,它们不具备集成能力。但是,随着连接的设备日益增多,集成就成了一个关键问题。随之而来的另一个问题就是“如何集成?”。最常见的集成体系结构模式包括:

  • 设备数据到服务器数据:设备上的数据库直接与服务器数据库同步。如果数据同步所涉及的逻辑较少,可以采用这种模式,因为这样做可以获得很好的性能。
  • 设备逻辑到服务器逻辑:设备应用程序连接到服务器的组件。这是最常用的方法,因为大多数同步都会涉及业务逻辑。
  • 设备逻辑到服务器数据:设备直接连接到服务器数据库。如果涉及的逻辑不太多而且带宽足够用时,可以考虑使用这种方法。
  • 仅服务器:使用设备浏览器连接到服务器端的 Web 应用程序。由于所有基于客户端 (Web) 的应用程序都比较小,因此这也是一种不错的选择,但是很显然,这样做需要设备与服务器始终保持连接。

最后一种方法并不是真正的集成方法,我们将其作为集成方法,是想说明有时侯可用的带宽对始终联机的应用程序来说是足够的。坦率地讲,这种方法不是最佳方法。这些模式用到的技术包括:

  • 设备数据到服务器数据:SQL Server CE .NET 的 Remote Data Access 和 Merge Replication
  • 设备逻辑到服务器逻辑:Web 服务
  • 设备逻辑到服务器数据:SQL Server .NET Provider
  • 仅服务器:Visual Studio .NET 2003 中的 Mobile Device Programmability

如上所述,使用这些模式实现应用程序所需的大多数技术都是现成的。

连接频率

另一个关键问题是解决方案要求的网络相关性。传统的选项是或者选择脱机,或者选择联机,现在,这种方式已经不能满足要求了。根据不同的使用环境,更倾向于选择脱机和联机之间的某种状态。

最重要的连接频率包括:

  • 很少:大多数时间,应用程序都可以脱机工作,只有当并发(许多用户使用相同的数据)很少或根本没有时需要以一定的间隔进行同步,因此需要的带宽较少,只在一定的时间段才要求很高的带宽(如在办公室时)。
  • 自动:它与“很少”(参阅前一项)很相似,但是应用程序中包括用于确定何时需要同步的逻辑。
  • 经常:应用程序频繁向服务器发出调用以发送或接收数据。由于应用程序只是来回传输数据,因此不需要连续的连接,需要的带宽也就相对较低。如果带宽的使用成本与发送的字节数有关,那么这是一个不错的选择。
  • 始终:这适用于需要保持连接才能工作的联机(大多数是 Web)应用程序。

不同时间段内可用的带宽对于确定方案中要传输的数据量也很重要。最常见的选项包括:

  • 高(WLAN/Wi-Fi = 11 Mb 或更高)
  • 低 (WAN/GPRS = 43.2 Kb)
  • 最低(WAN/CDMA = 4.8 Kb 或 WAN/GSM = 9.6 Kb)

做出选择

以上选项的任一组合不可能满足所有方案,但是在评价各种方案时,都需要将这些选项作为考虑的因素。对 eMbedded Visual Basic 开发人员来说,可以完成上面提到的大多数同步选项(大多数时候要使用一些中间件)。使用 SQL Server for Windows CE 的 Remote Data Access 或 Merge Replication 进行的数据库同步与使用 Visual Basic .NET 进行的同步非常相似。到服务器数据库的直接连接与在台式机中使用 Visual Basic .NET 的方法也非常相似,实际上,创建联机 (Web) 应用程序与 eMbedded Visual Basic 的关系不大。因此,最需要考虑的选项是使用 Web 服务来连接服务器组件。

基于 XML 的 Web 服务

简言之,Web 服务是一种使用可扩展标记语言 (XML) 以及超文本传输协议 (HTTP)(大多数情况下)调用(请求/响应)方法的方法。Web 服务的总体目标是应用程序集成,它可以在公司内部应用程序中使用,也提供了连接到不同公司的系统的方法。Web 服务会取得成功有两大原因:一是它构建的基础是一些公认的标准(HTTP、XML、SOAP、WSDL、UDDI 等),二是大多数大公司(IBM、Microsoft、Sun、Oracle 等等)都在使用它。

为扩展 Web 服务简单对象访问协议 (SOAP) 的功能而做的大量工作称为全局 XML 体系结构 (Global XML Architecture [GXA])。GXA 不是产品,而是一系列应用程序规范的统称,包括 Web 服务发现 (WS-Inspection)、安全调用 (WS-Security)、定义消息和备用路径 (WS-Routing/WS-Referral)、支持事务处理 (WS-Transaction) 以及发送二进制附件 (WS-Attachments)。GXA 的基石是 Web Services Enhancements (http://msdn.microsoft.com/webservices/building/wse/),它包括这些规范的 .NET Framework 实现。

连接到服务器组件

eMbedded Visual Basic 未提供本机方法来轻松地连接到服务器组件,开发人员必须依赖第三方的中间件组件以实现此功能。这样的中间件产品有很多,其中效果最好的是那些使用组件类似于使用本机方法的产品。例如使用 Odyssey Software 公司的 CEfusion,eMbedded Visual Basic 开发人员可以在服务器上创建对象,然后就像在台式机上使用 DCOM(分布式组件对象模型)一样来使用它们。在 CEfusion 中,传输数据时要使用 Recordset,与台式机中的 Recordset 几乎完全相同。而 Visual Basic .NET 就提供了使用 Web 服务集成应用程序的本机方法。服务器上的组件可以使用简单协议(如 XML 和 HTTP)来发布它们的功能。Visual Basic .NET 和 .NET Compact Framework 都内置了对 Web 服务的支持,我们从下文可以看出,这种支持不仅使应用程序集成成为可能,而且集成方法简单、有效。

发布 Web 服务

在服务器上使用 Visual Studio .NET 创建 Web 服务的过程很简单,Web 服务也可用来发布 .NET Framework 和组件对象模型 (COM) 组件。有现成的模板可以创建 Active Server Page (ASP) .NET Framework Web 服务项目。MSDN (http://msdn.microsoft.com/) 中提供了很多好的资源,可以创建 Web 服务。

有关在 .NET Framework 中创建 Web 服务的最佳指南,请参阅 Getting Started with XML Web Services in Visual Basic.NET and Visual C# (http://msdn.microsoft.com/library/en-us/dv_vstechart/html/vbtchGetting StartedWithXMLWebServicesInVisualStudioNET.asp),有关在 .NET 中使用 COM 组件的内容,请参阅 Visual Basic .NET documentation (http://msdn.microsoft.com/library/en-us/vbcn7/html/vaconIntroductionToCOMInteroperability.asp)。

由于 .NET Framework 对 COM 互操作提供了非常有效的支持,封装 COM 组件的效率要比将现有组件记录到本机 .NET Framework 组件的效率高出许多倍。

即使服务器没有提供 .NET Framework,借助 Microsoft SOAP Toolkit (http://www.microsoft.com/downloads/details.aspx?FamilyID=ba611554-5943-444c-b53c-c0a450b7013c&DisplayLang=en),您也可以将 COM 组件发布为 Web 服务。

许多其他服务器平台允许将服务器组件发布为 Web 服务,因此,您可以将移动解决方案与运行在各种平台上的大多数现有系统相集成。

客户端基础知识

让我们从一个非常简单的 Web 服务开始,即一个使用 Visual Basic .NET 在客户端实现的 Web 服务。为了说明该示例现有的功能,示例中使用了一个公共的(免费)Web 服务。在新项目中,添加一个 Web 引用(菜单项“Project”[项目] 和“Add Web Reference”[添加 Add Web 引用]),该示例使用一个免费的公共 Web 服务来检查信用卡号的有效性。图 5 显示了窗体的外观。(请注意,图中只显示了窗体的上半部分。)

图 5:Web 服务示例窗体

创建新的 Visual Basic .NET 项目后,只需要改变窗体和控件的 Name 和 Text 属性。改变这些属性是为了反映更恰当的值。要创建对 Web 服务的引用,请选择菜单项“Project”(项目)、“Add Web Reference”(添加 Web 引用),然后添加 Web 服务的 URL(本示例使用的 URL 为 http://secure.cdyne.com/creditcardverify/luhnchecker.asmx)。

在按钮的 Click 事件中,添加以下代码:

Dim CreditCardWS As New cdyne.LUHNChecker
Dim Result As cdyne.ReturnIndicator
Result = CreditCardWS.CheckCC(txtCreditCardNumber.Text)
MessageBox.Show("Card type: " & Result.CardType & _
Microsoft.VisualBasic.vbCrLf & _
"Card valid:" & IIf(Result.CardValid, "Yes", "No"))

请注意,Web 引用的名称改为“cdyne”(建议的名称,实际上是域 URL 的反向,但这样不太方便)。当设定对 Web 服务的引用后,就可以将 Web 服务类型当作应用程序中的任意类型来看待。

Web 服务可能返回一个基本类型(String、Integer 等),但也可能如本示例所示,返回一个可以转换为 Visual Basic .NET 类的复杂类型。在本示例中,这个类是有两个成员(CardType 和 CardValid)的 ReturnIndicator。

如果您想看一下该应用程序的具体内容,请单击“Solution Explorer”(解决方案资源管理器)中的“Show All Files”(显示所有文件),然后找到 Reference.vb 文件(在 Reference.map 文件下面)。找到该文件的代码之后,您就可以看到 Web 服务和此复杂类型是如何声明为真正的 .NET Framework 类的。

“吵闹的”交互与“臃肿的”交互

该示例还说明了在使用 Web 服务时会遇到的一个有趣情况。由于返回值是一个复杂类型,因此在一次调用中可以返回多个值。如果需要使用多个调用以获取各个值,从性能上考虑,这种解决方案通常是首选方案。但是,如果一次调用中返回的数据量非常大,则仅通过一次调用来返回所有数据可能会出现问题。这种问题一般称为“吵闹的”交互与“臃肿的”交互。“吵闹的”交互是指为完成一项任务而执行过多的调用,“臃肿的”交互是指每次调用都包含大量数据。而在许多移动应用程序中,带宽一般是有限制的,所以,如果带宽较低,必须特别注意,谨慎选择最优的解决方案。

DataSet 集成

在 eMbedded Visual Basic 中处理数据的最方便结构是 ActiveX Data Objects (ADO) Recordset。Visual Basic .NET 中与之相对应但又完全不同的结构是 DataSet(有关 Recordset 和 DataSet 之间的区别的详细信息,请参阅后面的“使用数据库”一节)。简言之,DataSet 是一种不连接的数据容器,可将其数据提取为 XML,这在与 Web 服务结合使用时十分有用。

要了解如何使用 Web 服务和 DataSet 来集成服务器组件,让我们使用 .NET Framework 工具创建一个 Web 服务。在 Visual Studio .NET 中,创建一个类型为“ASP.NET Web Service”、名称为“DataSetWS”的新项目。为了说明在 .NET Framework 中集成与所用的语言无关,此 Web 服务被创建为 C# 项目。但愿这段代码不是很难,即便是没有 C# 使用经验的读者也可以理解。创建新项目后,添加以下代码:

[WebMethod]
public DataSet GetEmployees(int EmployeeID)
{
SqlConnection con;
SqlDataAdapter da;
DataSet ds;

con = new SqlConnection("data source=(local);initial
catalog=Northwind;" +
"uid=sa;pwd=");
con.Open();
if (EmployeeID > 0)
da = new SqlDataAdapter("SELECT EmployeeID,LastName,
FirstName, " +
"HomePhone FROM Employees WHERE EmployeeID="
+
EmployeeID.ToString(),con);
else
da = new SqlDataAdapter("SELECT EmployeeID,LastName,
FirstName, " +
"HomePhone FROM Employees",con);
ds = new DataSet("Employees");
da.Fill(ds,"Employees");

return ds;
}

这个示例中使用的数据库是 SQL Server 中的示例数据库:Northwind。Web 服务方法 (GetEmployees) 接受一个参数 (EmployeeID),并返回一个 DataSet。创建连接并打开后,需要进行检查以确定所提供的员工身份是否有效(大于零)。如果有效,将从数据库中选出一名特定的员工;如果无效,则检索所有员工。查询结果保存在 DataSet 的表中(DataSet 和表的名称都为“Employees”),而 DataSet 则返回调用者。请注意,只有必要的字段从数据库查询返回,以尽量减少数据量。构建项目时,您可以利用 Web 服务从您的“服务器”(可能是您开发所使用的计算机)获取员工信息。

现在,让我们进一步扩展上面的示例,向窗体添加一些项目,完成后的情形如下所示(图 6):

图 6:DataSet Web 服务示例窗体

新项目包括一个标签和一个文本框(名为“txtEmployeeID”),用于输入员工身份;一个按钮,用于调用 Web 服务;一个列表视图(名为“lvwEmployees”),用于保存返回员工的数据。如果文本框中未输入员工身份,将返回所有员工的数据。列表视图中包含三列(姓名、电话、ID),最后一列是隐藏的(宽度为 0)。

添加 Web 引用后(使用新创建的 Web 服务的 URL 进行添加),将以下代码添加到 Get 按钮的 Click 事件:

Dim DataSetWS As New server.NETCF
Dim ds As DataSet
Dim iEmployeeID As Integer = 0
Dim dr As DataRow
Dim lvi As ListViewItem

If IsNumeric(txtEmployeeID.Text) Then
iEmployeeID = Convert.ToInt32(txtEmployeeID.Text)
End If

ds = DataSetWS.GetEmployees(iEmployeeID)

If ds.Tables(0).Rows.Count < 1 Then
MessageBox.Show("No Employee(s)found!")
Else
lvwEmployees.Items.Clear()
For Each dr In ds.Tables(0).Rows
lvi = New ListViewItem(dr("FirstName").ToString() & " " & _
dr("LastName").ToString())
lvi.SubItems.Add(dr("HomePhone").ToString())
lvi.SubItems.Add(dr("EmployeeID").ToString())
lvwEmployees.Items.Add(lvi)
Next
End If

首先,创建 Web 服务(在本例中,Web 服务被引用为“server”,服务器中 Web 服务类的名称为“NETCF”),完成其他声明后,检查是否在文本框 (txtEmployeeID) 中添加了员工身份。如果未提供身份,则在调用 Web 服务时使用默认值 (0)。检查返回的 DataSet (ds) 是否包含行,如果包含,将这些行添加到列表视图中。需要注意的是,第三列用于检索员工的身份,如果要从列表视图的某一行获取更多的员工信息,可以利用这一列。

这种方法对于在服务器和客户端之间传输关系型数据十分方便,如果考虑完成这种任务所需的代码量,最好的选择就是利用 .NET Framework 中提供的支持。检查传输的数据之后,您会发现,不仅传输了数据,还传输了元数据(字段类型、长度等)。

异步 Web 服务调用

根据上文有关“吵闹”和“臃肿”的论述,很显然,XML 格式的数据会很容易变得非常“臃肿”。因此,应该尽可能地减少发送的数据量(如以上示例中只选择需要的列)。有时需要在不锁定设备的情况下传输大量数据,这时,就需要使用异步 Web 服务调用。

仍然以上面的示例代码为例,再添加一个按钮(名为“btnGetAsync”),然后向该按钮的 Click 事件添加以下代码:

Dim DataSetWS As server.NETCF = New server.NETCF
Dim iEmployeeID As Integer = 0a
Dim cb As AsyncCallback

btnGetAsync.Enabled = False
lvwEmployees.Items.Clear()

If IsNumeric(txtEmployeeID.Text) Then
iEmployeeID = Convert.ToInt32(txtEmployeeID.Text)
End If

cb = New AsyncCallback(AddressOf GetAsyncReturn)

DataSetWS.BeginGetEmployees(iEmployeeID,cb,DataSetWS)

大部分代码都与上文介绍的同步调用代码相同,但这一次在调用过程中禁用了按钮。这是通知用户操作处于激活状态的最方便的方法。最重要的区别是,在对 Web 服务 (BeginGetEmployees) 的异步调用中使用了异步回调变量 (cb)。

这个回调变量 (cb) 其实是一个指向公共函数的函数指针,其代码如下:

Public Sub GetAsyncReturn(ByVal ar As IAsyncResult)
Dim DataSetWS As server.NETCF
Dim ds As DataSet
Dim i As Integer = 0
Dim dr As DataRow
Dim lvi As ListViewItem

DataSetWS = ar.AsyncState

ds = DataSetWS.EndGetEmployees(ar)

If ds.Tables(0).Rows.Count < 1 Then
MessageBox.Show("No Employee(s) found!")
Else
For Each dr In ds.Tables(0).Rows
lvi = New ListViewItem(dr("FirstName").ToString() & " "
& _
dr("LastName").ToString())

lvi.SubItems.Add(dr("HomePhone").ToString())
lvi.SubItems.Add(dr("EmployeeID").ToString())
lvwEmployees.Items.Add(lvi)
Next
End If

btnGetAsync.Enabled = True

End Sub

大部分声明与同步调用中的相同,但是需要注意的是,Web 服务 (DataSetWS) 并没有在声明中进行实例化,而是从异步状态参数 (ar) 中检索实例。通过最后调用 Web 服务 (EndGetEmployees) 来检索返回的 DataSet (ds)。按照以前的方法填充列表视图后,重新启用按钮。

有意思的是,在调用按钮 Click 事件的执行和调用 GetAsyncReturn 的过程中,用户可以与应用程序正常交互。尽管检索得到的数据相同,但是这会给用户带来新的体验。

集成优化

尽管能够异步调用 Web 服务,还能给用户带来更为愉悦的使用体验,但传输的数据量对可用的带宽(或资金)来说可能仍然太大。除了从设计角度采取一些措施外(比如将传输的数据分成较小的部分),还应该考虑压缩数据的可能性。但这方面的内容很多,需要单独撰文论述。

有关详细信息,请参阅以下页面:

XML Web Services Developer Center
(http://msdn.microsoft.com/webservices/)

SOAP
(http://msdn.microsoft.com/soap/)

使用数据库

本节详细介绍前面提到的“设备数据到服务器数据”集成模式。大多数移动应用程序对从远程数据库检索数据、在本地保存收集的数据以及将数据传输回远程数据库,都有着相同的要求。eMbedded Visual Basic 提供了一个基于 ADOCE 的编程模型,它是一个服务器端 ADO 对象模型的“同胞”。与之类似,.NET Compact Framework 通过 System.Data 命名空间提供了一个 ADO.NET 的子集。表面看来,新的编程模型与 ADOCE 没什么区别,一些基本任务,如进行连接、使用记录和字段集合、通过连接对象使用事务处理等,工作方式大致相同。

但是,在数据访问方面有两个明显的变化。首先,没有用于本地数据库(有时称为 CEDB 或 Pocket Access)的托管提供程序。相反,Microsoft 为 SQL Server CE 2.0 及到服务器端 SQL Server 的远程连接提供了托管提供程序。其次,当数据量较小时,可以将保存为本地文件的 XML 作为数据库,换句话说,就是可以不使用 SQL Server CE 2.0。但在这两种情况下都要使用 ADOCE Recordset 的对等对象 - DataSet。表 1 概括了三种可用的数据库策略。

表 1:三种可用的数据库策略

    作为本地文件中 XML 的 DataSet 本地 SQL Server
CE 数据库
远程 SQL
Server 数据库
数据量 数据量较小 数据量中等 远程数据量很大,设备使用小结果集
查询 简单,脱机 丰富,脱机 丰富,(始终)脱机
主要优势 成本低 最健壮的本地数据解决方案,带宽的利用率高 实时,没有复制问题

从 eMbedded Visual Basic 和 SQL Server CE 1.0/1.1 迁移到 Visual Basic .NET 和 SQL Server CE 2.0 时,两种数据库版本的文件格式是相同的,它们可以在同一设备中和平共存,了解这一点是很有帮助的。

System.Data 命名空间中的许多类型都与 DataSet 类相关。下图说明了 DataSet 如何实现读、写 XML,以及本地和远程 SQL Server 的用法。

图 7:System.Data 的核心

DataSet

DataSet 类代替了 ADOCE Recordset,除此之外,还添加了许多功能。一个 DataSet 可以包含多个名为 DataTable 对象的结果集,这些结果集又与 DataRelation 和 DataRelationCollection 对象关联。

以下代码使用 eMbedded Visual Basic 从 SQL Server CE 数据库打开连接和记录集:

' 变量
Dim provider As String
Dim datasource As String
Dim rs As ADOCE.Recordset
Dim cn As ADOCE.Connection

' 创建 ADOCE 对象
Set cn = CreateObject("ADOCE.Connection.3.1")
Set rs = CreateObject("ADOCE.Recordset.3.1")

' 设置 provider 和 datasource 字符串
provider = "Provider=Microsoft.SQLSERVER.OLEDB.CE.2.0"
datasource = "Data Source=\SQL\sample.sdf"
' 打开连接
cn.Open provider & "; " & datasource

' 打开记录集
rs.Open "SELECT SupplierID, CompanyName FROM Suppliers ORDER
BY CompanyName",cn,adOpenDynamic,adLockOptimistic

以下是 Visual Basic .NET 中与之相对应的代码:

' 变量
Dim cn As New SqlServerCe.SqlCeConnection
Dim ds As New DataSet
Dim da As New SqlServerCe.SqlCeDataAdapter
Dim cmd As New SqlServerCe.SqlCeCommand

' 设置 connectionstring,打开连接
cn.ConnectionString = "Data Source=\SQL\sample.sdf"
cn.Open()

' 设置命令属性
cmd.CommandText = "SELECT SupplierID,CompanyName FROM
Suppliers ORDER BY CompanyName"
cmd.Connection = cn

' 设置数据适配器命令
da.SelectCommand = cmd

' 使用适配器的数据表填充 Dataset
da.Fill(ds,"Suppliers")

Visual Basic .NET 代码还可以再简洁一些,即在实例化类时通过构造函数的参数设置属性值,而这在 eMbedded Visual Basic 中是无法做到的。以下就是具体方法:

' 变量
Dim cn As New SqlServerCe.SqlCeConnection("Data
Source=\SQL\sample.sdf")
Dim ds As New DataSet
Dim da As New SqlServerCe.SqlCeDataAdapter("SELECT SupplierID,
CompanyName FROM Suppliers", cn)

' 打开连接
cn.Open()

' 使用适配器的数据表填充 DataSet
da.Fill(ds, "Suppliers")

DataAdapter 用于向数据库插入数据、更新其中的数据以及从中删除数据。要创建所需的命令,最简单的方法是构建一个桌面 Windows Forms 应用程序,并使用“Data Adapter Configuration Wizard”(数据适配器配置向导)创建一个数据适配器。向导将创建相应的代码和命令。

DataSet 将数据和架构作为 XML 文档来管理,并实现方法(WriteXml、ReadXml、WriteXmlSchema 和 ReadXmlSchema)以读、写数据和架构。因此,DataSet 本身在数据量较低时,就可以实现本地数据库的具体实现。

以下两行代码根据 DataSet 编写 XML 架构和数据:

ds.WriteXmlSchema("\SuppliersSchema.xsd")
ds.WriteXml("\Suppliers.xml")

XML 架构的代码如下所示:

<?xml version="1.0" standalone="yes" ?>
- <xs:schema id="NewDataSet" xmlns=""
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xs:element name="NewDataSet" msdata:IsDataSet="true"
msdata:Locale="sv-SE">
- <xs:complexType>
- <xs:choice maxOccurs="unbounded">
- <xs:element name="Suppliers">
- <xs:complexType>
- <xs:sequence>
<xs:element name="SupplierID" type="xs:int" minOccurs="0" />
<xs:element name="CompanyName" type="xs:string"
minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>

数据文件中的前几行 XML:

<?xml version="1.0" standalone="yes" ?>
- <NewDataSet>
- <Suppliers>
<SupplierID>1</SupplierID>
<CompanyName>Aux joyeux eccl???©siastiques</CompanyName>
</Suppliers>
- <Suppliers>
<SupplierID>2</SupplierID>
<CompanyName>New Orleans Cajun Delights</CompanyName>
</Suppliers>
- <Suppliers>
<SupplierID>3</SupplierID>
<CompanyName>Grandma Kelly's Homestead</CompanyName>
</Suppliers>
- <Suppliers>
<SupplierID>4</SupplierID>
<CompanyName>Tokyo Traders</CompanyName>
</Suppliers>
</NewDataSet>

DataSet 的 XML 功能很好地体现了对 eMbedded Visual Basic 的超越。使用 DataSet 将数据从数据库传输到用户界面将十分简单。以下代码说明了如何使用 DataSet 清除组合框的内容,然后再填充组合框:

' 将 DataSet 绑定到组合框并选择第一个项目
With cboSuppliers
.Items.Clear()
.DataSource = ds.Tables("Suppliers")
.ValueMember = "SupplierID"
.DisplayMember = "CompanyName"
.SelectedIndex = 0
End With

DataReader

DataSet 会生成一个不连接的结果集,而 DataReader 则是一个连接的、可读的、游标只能向前的结果集,可以提供更好的性能。从本质上讲,DataReader 的行为与使用以下 eMbedded Visual Basic 代码创建的 ADOCE Recordset 的行为相似:

rs.CursorType = adOpenForwardOnly
rs.LockType = adLockReadOnly
rs.ActiveConnection = cn
rs.Open "SELECT SupplierID, CompanyName FROM Suppliers ORDER
BY CompanyName"

请注意,变量“cn”是一个 ADOCE Connection 对象。Visual Basic .NET 中的与之相对应代码如下所示(包括 Connection 对象的实例化):

Dim cn As New SqlServerCe.SqlCeConnection("Data
Source=\SQL\sample.sdf")
cn.Open()
Dim dc As New SqlServerCe.SqlCeCommand("SELECT SupplierID,
CompanyName FROM Suppliers ORDER BY CompanyName", cn)
Dim dr As SqlServerCe.SqlCeDataReader = dc.ExecuteReader()

以下代码说明了如何对 DataReader 数据进行循环,并填充组合框。代码实现了一个接受两个参数的通用过程,这两个参数一个是 ComboBox 引用,一个是 SQL 字符串。该过程假设有一个开放的连接 (cn) 可用,并且使用 ArrayList(一个 .NET Compact Framework 中新提供的类)来解决一个常见的问题:填充每个项目都有隐藏的唯一 ID 和一个描述性可见名称的组合框。

Private Sub PopulateComboBox(ByRef ComboBox As ComboBox, ByVal
SQL As String)
' 填充具有 ID/Name 的组合框的通用子程序

' 声明
Dim IDNameArray As ArrayList = New ArrayList

' 清除组合框
With ComboBox
.Visible = False
.DataSource = Nothing
.Items.Clear()
End With

Try
Dim dc As New SqlServerCe.SqlCeCommand(SQL, cn)
Dim dr As SqlServerCe.SqlCeDataReader = dc.ExecuteReader()

' 循环读取程序
While dr.Read()
IDNameArray.Add(New IDName(Trim(dr("ID")),
Trim(dr("Name"))))
End While

' 设置组合框属性
With ComboBox
.DataSource = IDNameArray
.DisplayMember = "Name"
.ValueMember = "ID"
.SelectedIndex = 0
End With

Catch ex As Exception
MessageBox.Show(ex.Message)
Finally
ComboBox.Visible = True
End Try

End Sub

组合框由 ArrayList 填充,ArrayList 是由自定义类中的项目数据构建的,这个自定义类实现两个通用属性:ID 和 Name。类的代码如下所示:

Class IDName
Private _ID As String
Private _Name As String

Public Sub New(ByVal ID As String, ByVal Name As String)
MyBase.New()
_ID = ID
_Name = Name
End Sub

Public ReadOnly Property ID() As String
Get
Return (_ID)
End Get
End Property

Public ReadOnly Property Name() As String
Get
Return _Name
End Get
End Property

Public Overrides Function ToString() As String
Return _ID + " - " + _Name
End Function
End Class

管理本地数据库

有两种方法可以将 SQL Server CE 数据库植入设备:

  • 安装时发布数据库文件
  • 运行时创建数据库

要使用 eMbedded Visual Basic 创建新数据库:

' 变量
Dim provider As String
Dim datasource As String
Dim ct As ADOXCE.Catalog

' 处理错误
On Error Resume Next

' 创建 ADOXCE 种类
Set ct = CreateObject("ADOXCE.Catalog.3.1")

' 设置 provider 和 datasource 字符串
provider = "Provider=Microsoft.SQLSERVER.OLEDB.CE.2.0"
datasource = "Data Source=\SQL\sample.sdf"

' 创建数据库
ct.Create provider & "; " & datasource

' 检查错误
If Err.Number <> 0 Then
' 将处理代码放到此处!
End If

很显然,在运行时创建的数据库可能已经存在。有两种方法可以处理这种情况。第一,代码可以检查数据库文件是否存在。如果确实存在,代码就不创建新文件。第二,代码可以处理当它试图创建已经存在的数据库时出现的错误。在 eMbedded Visual Basic 代码中,这是在 Create 方法之后,通过使用前置的“On Error Resume Next”并检查 Err 对象的 Number 属性 (Err.Number) 实现的。要在 Visual Basic .NET 中实现相同的目的,可以使用更加结构化的 Try ... Catch ... Finally 异常处理:

' 变量
Dim db As String = "\sample2.sdf"
Dim sqlEngine As New SqlServerCe.SqlCeEngine("Data Source=" +
db)
Try
' 创建数据库
sqlEngine.CreateDatabase()
Catch ex As SqlServerCe.SqlCeException
' 将处理代码放到此处!
Finally
' 无论 CreateDatabase 运行或失败,都将执行代码放到此处!

cn.Open()
End Try

首先检查现有数据库,然后使用 Visual Basic .NET 创建新数据库:

' 变量
Dim db As String = "\sample.sdf"
Dim sqlEngine As New SqlServerCe.SqlCeEngine("Data Source=" +
db)

' 如果数据库已经存在,则删除它
If File.Exists(db) Then
File.Delete(db)
End If
0

' 创建数据库
sqlEngine.CreateDatabase()

创建数据库之后,接着创建表格并开始处理数据。

以下代码:

  • 创建表格供应程序
  • 插入两条记录
  • 更新一条记录,删除一条记录
  • 管理事务中的数据更改
' 变量
Dim db As String = "\sample.sdf"
Dim sqlEngine As New SqlServerCe.SqlCeEngine("Data Source=" +
db)
Dim cn As New SqlServerCe.SqlCeConnection("Data Source=" + db)
Dim cmd As New SqlServerCe.SqlCeCommand

' 如果数据库已经存在,则删除它
If File.Exists(db) Then
File.Delete (db)
End If

Try
' 创建数据库
sqlEngine.CreateDatabase()
Catch
' 将处理代码放到此处!
Finally
' 无论 CreateDatabase 运行或失败,都将执行代码放到此处!

cn.Open()
End Try

' 使用 Command 对象创建表格
cmd.Connection = cn
cmd.CommandText = "CREATE TABLE Suppliers(SupplierID int
PRIMARY KEY, CompanyName nvarchar(50))"
cmd.ExecuteNonQuery()

Try
' 填充表格
cmd.CommandText = "INSERT INTO Suppliers(SupplierID,
CompanyName) VALUES (1, 'Best Foods')"
cmd.ExecuteNonQuery()
cmd.CommandText = "INSERT INTO Suppliers(SupplierID,
CompanyName) VALUES (2, 'Best Clothes')"
cmd.ExecuteNonQuery()
Catch ex As SqlServerCe.SqlCeException
cn.Close()
MsgBox("Could not insert data!" + ex.Message)
Exit Sub
Finally
End Try

Try
' 更新和删除!
cmd.CommandText = "UPDATE Suppliers SET CompanyName = 'Good
Foods' WHERE SupplierID = 1"
cmd.ExecuteNonQuery()
cmd.CommandText = "DELETE FROM Suppliers WHERE SupplierID =
2"
cmd.ExecuteNonQuery()
Catch ex As SqlServerCe.SqlCeException
MsgBox("Could not modify data!" + ex.Message)
Finally
cn.Close()
End Try

使用远程数据库

.NET Compact Framework 提供了两种方法用来从 Pocket PC 访问远程数据库:

  • 多层 Web 服务应用程序:您可以将 DataSet (XML) 传递到服务器端 Web 服务,而服务器端 Web 服务又连接到远程数据库。或者,可以通过服务器端的 SQLXML 托管类 (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sqlxml3/htm/dotnet_9n03.asp) 利用 SQL Server 2000 内置的 XML 支持。
  • 远程数据库应用程序:开发远程数据库应用程序。方法有两种,一是在 .NET Compact Framework 中使用 SQL 托管提供程序,从而可以对数据库进行直接查询,这在 eMbedded Visual Basic 中是做不到的;二是在 .NET Compact Framework 中使用 SQL Server CE 托管提供程序,以启用对数据库表格的推、拉和合并复制。数据库与数据库的紧密集成可以提供更好的性能,但是很难或者根本无法使用经常要在 Web 服务中实现的业务逻辑。

以下两篇 MSDN 文章探讨了如何安全地配置远程服务器:

Security Models and Scenarios for SQL Server 2000 Windows CE Edition 2.0
(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsqlce/html/sqlce_secmodelscen20.asp)

Troubleshooting SQL Server CE Connectivity Issues
(http://www.microsoft.com/sql/techinfo/administration/2000/CEconnectivitywp.asp)

配置过 SQL Server CE 1.x 连接的 eMbedded Visual Basic 开发人员会很高兴地发现,现在他们可以使用 SQL Server CE Connectivity Management 控制台和 SQL Server CE Virtual Directory Creation Wizard(图 8)。

图 8:SQL Server CE 虚拟目录创建向导

直接数据库查询

System.Data.SqlClient 命名空间 (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sqlce/htm/_lce_sqlclient_705.asp) 中的组件用于连接到远程 SQL Server、在其中执行命令并从中检索结果。以下代码显示了如何使用 DataReader 实现此目的。示例代码要求用户输入公司名称,该名称将在对 Northwind 数据库的 Suppliers 表进行查询时用到。请注意,SqlCommand 对象引用一个驻留在远程 SQL Server 的存储过程。

' 变量
Dim cmd As SqlClient.SqlCommand
Dim dr As SqlClient.SqlDataReader
Dim cn As New System.Data.SqlClient.SqlConnection("user
id=sa;password=;initial
catalog=northwind;server=192.168.100.100")
Dim CompanyName As String

' 获取要搜索的公司名称
CompanyName = txtCompanyName.Text

Try
' 打开到远程服务器的连接
cn.Open()
' 获取供应商
cmd = New System.Data.SqlClient.SqlCommand("getSuppliers " +
CompanyName, cn)
dr = cmd.ExecuteReader()

' 填充组合框
cboSuppliers.Items.Clear()
While dr.Read()
cboSuppliers.Items.Add(dr("CompanyName"))
End While

' 显示第一个供应商
cboSuppliers.SelectedIndex = 0

Catch ex As SqlClient.SqlException
MsgBox(ex.Message)
Finally
dr.Close()
cn.Close()
End Try

存储过程 getSuppliers 的代码如下所示:

CREATE PROCEDURE getSuppliers
@CompanyName NVARCHAR(50)
AS
SELECT SupplierID, CompanyName
FROM Suppliers
WHERE CompanyName LIKE '%' + @CompanyName + '%'

许多使用 eMbedded Visual Basic 构建的应用程序都利用 SQL Server CE 的 COM 组件 Remote Data Access (RDA) 和 Merge Replication (MR) 以同步设备与远程服务器之间的数据。.NET Compact Framework 的 SqlCeRemoteDataAccess 和 SqlCeReplication 命名空间也可以实现相同的功能。

有关这些命名空间的详细信息,请参阅 SQL Server 2000 Windows CE Edition and the .NET Compact Framework
(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsqlce/html/sqlwince.asp)。

有关详细信息,请参阅以下页面:

.NET Compact Framework Data Providers (http://msdn.microsoft.com/library/default.asp?url=/library/enus/sqlce/htm/_lce_sql_server_ce_manage_provider.asp)

Microsoft SQL Server 2000 Windows® CE Edition Home
(http://www.microsoft.com/sql/CE/default.asp)

Converting an eMbedded Visual Basic 3.0 Application from SQL Server CE 1.x to SQL Server CE 2.0
(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsqlce/html/sqlce_convertssce10to20_.asp)

部署和分布

构建应用程序之后,需要用一种方便的方法来部署和发布应用程序。在 eMbedded Visual Basic 中,有一个“Application Install Wizard”(应用程序安装向导),它可以将项目和所需的资源打包到安装可执行文件中 (setup.exe)。而在 Visual Basic .NET 中,则不像这样简单明了。

创建压缩文件 (Cabinet)

当“Application Installation Wizard”(应用程序安装向导,包含在 eMbedded Visual Basic 中)创建安装程序之后,安装程序中总会包含一个压缩 (.cab) 文件,该文件在设备上运行,以执行实际的安装过程。生成的文件(Setup.exe、Setup.ini 和压缩文件)位于名为 CD1 的文件夹中。在 Visual Basic .NET 中,无法创建完整的计算机安装,但它本身支持创建压缩文件。在 Visual Basic .NET 中加载并构建(可能使用“Release”配置)项目后,选择菜单选项“Build”(构建)和子菜单项“Build Cab File”(构建 Cab 文件),就会生成适合多种处理器类型的压缩文件。生成的压缩文件位于项目文件夹(<项目路径>/cab/Release)下的一个文件夹中。

要在 Pocket PC 上进行安装,请使用后缀为“_PPC.ARM.CAB”的压缩文件。这是一个 Pocket PC 安装程序,将此文件复制到设备上,并从“File Explorer”(文件资源管理器,设备上的)中打开,就可以开始安装应用程序。这个文件也可以放置在 Web 页面中,已连接的 Pocket PC 用户可以直接从 Pocket PC 的 IE 启动安装过程。

但是,如果将生成的压缩文件安装到设备上,您将会发现应用程序的名称与 Visual Basic .NET 中使用的项目名称是相同的。另外,公司名称被设置为“My Company”,这可能不符合您公司的要求。要自定义压缩文件的创建,必须用到项目文件夹(位于 <项目路径>/obj/Release)下面的另一个文件夹。您可以在其中找到创建压缩文件所需的安装信息文件 (.inf)。在该文件中,有应用程序条目和公司名称条目。以下示例是此文件的节选(不包括用句点标记的小节):

[Version]
Signature="$Windows NT$"
Provider="CompanyName"
CESignature="$Windows CE$"

[CEStrings]
AppName="ApplicationName"
InstallDir=%CE1%\%AppName%
.
.
[DefaultInstall]
CEShortcuts=Shortcuts
CopyFiles=Files.Common
.
.
[SourceDisksFiles]
ProjectName.exe=1
.
.
[Files.Common]
ProjectName.exe,,,0
.
.
[Shortcuts]
ApplicationName,0,ProjectName.exe,%CE11%

生成此文件的 Visual Basic .NET 项目名称是“ProjectName”,在生成的文件中,Version 小节中的 Provider 值从“My Company”变为“CompanyName”。同样,CEStrings 小节的 AppName 值从“ProjectName”变为“ApplicationName”。Shortcuts 小节中也有类似的改变(列表中的第一个值是生成的快捷键名)。以上只是简单介绍,因为 .inf 文件涉及的内容很多(详细信息,请参阅 Visual Basic .NET 帮助文件的索引主题“设备项目的 inf 文件”)。

更新 .inf 文件时,您可以运行与 .inf 文件在同一文件夹中的批处理文件 (BuildCab.bat) 来生成一套新的压缩文件。

有关详细信息,请参阅以下页面:

Microsoft Windows CE 3.0 Installation and Configuration Guide
(http://msdn.microsoft.com/library/en-us/wcesetup/htm/_wcesdk_installation_and_configuration_guide.asp)

ActiveSync 安装

如前所述,eMbedded Visual Basic 中包含的“Application Installation Wizard”(应用程序安装向导)可以创建一个文件夹 (CD1),其中包含了安装文件。这些文件包括在计算机上运行的安装应用程序 (Setup.exe) 和一个配置设置文件 (Setup.ini)。要从 Visual Basic .NET 项目创建计算机安装,您可以使用这两个文件,前提是您已经安装了 eMbedded Visual Basic。仍以以上示例为例,setup.ini 文件的结构和内容如下所示:

[General]
Component=ProjectName
DefaultDirectory=ApplicationName
CabCount=1
Cab0=ProjectName_PPC.ARM.CAB
Description=CompanyName ApplicationName

对于 Pocket PC 安装,只需要后缀为“_PPC.ARM.CAB”的压缩文件。您需要将三个文件(Setup.exe、Setup.ini 和 ProjectName_PPC.ARM.CAB)发送给您的用户,以便通过 ActiveSync 从计算机来安装应用程序。为了方便最终用户,您甚至可以将这三个文件压缩到自解压的压缩文件中,当用户解压缩时即可启动安装应用程序 (Setup.exe)。大多数的商用压缩工具(如 WinZip 和 WinRAR)都可以实现此目的。

小结

从 eMbedded Visual Basic 到 Visual Basic .NET,对于 Pocket PC 的开发来说是一大进步。许多开发工作,如用户界面的开发、应用程序导航、系统集成、数据库管理和解决方案部署等,都有重大改进。环境、语言和平台得到彻底重新构建,从而提供了更为健壮的功能,也提高了编写代码的效率,开发工作由此变得更为轻松,移动应用程序的连接性也得到改善。


来源:microsoft

 

版权所有:UML软件工程组织