Microsoft .NET Compact Framework 开发常见问题解答

msdn上的地址

http://www.microsoft.com/china/msdn/library/NetFramework/netcompactframework/debugdestime.mspx

Microsoft .NET Compact Framework 开发的常见问题解答。

这个 FAQ 有部分是通过编辑公共
.NET Compact Framework 新闻组 (microsoft.public.dotnet.framework.compactframework) 贴出的问题和解答而得到的。.NET Compact Framework 团队衷心感谢参加公共新闻组的每个人,感谢他们对本 FAQ 和整个 .NET Compact Framework 开发社区做出的贡献。

要想获得 FAQ 项,请将电子邮件发送到
netcfaq@microsoft.com

*
本页内容
1. 开发1. 开发
2. 图形2. 图形
3. 部署3. 部署
4. 图形用户界面 (GUI):窗体4. 图形用户界面 (GUI):窗体
5. 图形用户界面 (GUI):常规5. 图形用户界面 (GUI):常规
6. 互操作性和本机代码6. 互操作性和本机代码
7. 常规7. 常规
8. 通信和 Web 服务8. 通信和 Web 服务
9. SQL CE 和数据9. SQL CE 和数据
10. 其他信息10. 其他信息
11. 连接11. 连接
12. 基于 Windows Mobile 的 Smartphone12. 基于 Windows Mobile 的 Smartphone

1. 开发

1.1. 什么是 Microsoft .NET Compact Framework?

Microsoft .NET Compact Framework 是针对 Microsoft .NET 计划的智能设备开发框架,是实现 Microsoft 随时随地在任何设备上为客户提供良好体验的目标的关键所在。.NET Compact Framework 将托管代码和 Web 服务带给了智能设备,它允许安全的、可下载的应用程序在诸如个人数字助理 (PDA)、移动电话和机顶盒等设备上运行。

1.2.开发 .NET Compact Framework 应用程序需要什么工具?

Visual Studio .NET 为基于 Windows Mobile 的 Pocket PC 2000、基于 Windows Mobile 的 Pocket PC 2002 和 Windows CE .NET 4.1 设备开发基于 .NET Compact Framework 的应用程序需要 2003 Professional 或更高版本。Visual Studio .NET 2003 附带了 .NET Compact Framework。

在最新的 Windows Mobile 平台上进行开发还有其他的 SDK 可用:

本文将提供用 .NET Compact Framework 1.0 和 Visual Studio .NET 2003 开发健壮的智能客户端设备应用程序的说明。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfgetstarted.asp

1.3. 哪里可以下载最新的工具和软件?

Visual Studio .NET 2003 试用版可以通过以下地址获得:
http://msdn.microsoft.com/vstudio/productinfo/trial/default.aspx

最新的平台更新和 .NET Compact Framework service pack 可以通过以下地址获得:
http://msdn.microsoft.com/mobility/downloads/default.aspx

1.4. .NET Compact Framework 支持什么设备?

.NET Compact Framework 支持基于 Windows Mobile 2000 的 Pocket PC、基于 Windows Mobile 2002 的 Pocket PC、基于 Windows Mobile 2003 的 Pocket PC、基于 Windows Mobile 的 Smartphone 和运行 Windows CE .NET 4.1 及更高版本的嵌入式系统。

1.5. 以后 .NET Compact Framework 将会支持什么设备?

不久以后,.NET Compact Framework 将会作为一个操作系统 (OS) 组件,在所有的 Microsoft 智能设备中提供,包括将来基于 Windows Mobile 的 Pocket PC 设备、基于 Windows Mobile 的 Pocket PC Phone Edition、基于 Windows Mobile 的 Smartphone、Windows CE for Automotive 和 MSTV。每种特定设备的具体时间选择将由各个产品发布周期确定。

Windows CE .NET 4.1 也支持 .NET Compact Framework,将其作为一个集成的 OS 组件,因此允许 OEM 使用 Platform Builder 工具将 .NET Compact Framework 嵌入到所有装备 Windows CE 的新设备中。

1.6. 调试器在与模拟器建立连接时为什么会失败(错误启动应用程序)?

1.7. .NET Framework 和 .NET Compact Framework 之间有什么区别?

请参见下面 .NET Framework 和 .NET Compact Framework 之间的比较:
http://msdn.microsoft.com/library/en-us/dv_evtuv/html/etconComparisonsWithNETFramework.asp

.NET Compact Framework 类库比较工具中的信息:
http://msdn.microsoft.com/library/en-us/dv_spchk/html/NET_Compact_Framework.htm

1.8.最新的 .NET Compact Framework Service Pack 有什么新内容?

.NET Compact Framework SP2 提供了针对大量缺陷的补丁。补丁列表可由以下链接获得:
http://www.microsoft.com/downloads/details.aspx?familyid=10600643-09b3-46d8-ba28-bc494bc20d26&displaylang=en

.NET Compact Framework SP1 提供了针对大量缺陷的补丁。补丁列表可由以下链接获得:
http://www.microsoft.com/downloads/details.aspx?familyid=1f62a2a3-7282-4ba9-b26b-2267e972501d&displaylang=en

注: Service Pack 2 (SP2) 替代了 Service Pack 1 (SP1),它包括所有 service pack 的最新更新。

1.9. 在 .NET Compact Framework 开发中,基于 Windows Mobile 的 Pocket PC 和 Windows CE .NET 之间有什么区别?

这篇文章概述了为基于 Windows Mobile 的 Pocket PC 和 Microsoft Windows CE .NET 平台开发基于 Microsoft .NET Compact Framework 的应用程序之间的区别。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfPPCtoCE.asp

1.10. 在哪里可以找到有关如何创建基于 .NET Compact Framework 的应用程序的更多信息?

下列资源有助于您了解如何创建基于 .NET Compact Framework 的应用程序:

MSDN 移动与嵌入式技术开发人员中心中的 .NET Compact Framework 白皮书:
http://msdn.microsoft.com/mobility/understanding/articles/default.aspx

1.11. .NET Compact Framework 的文档在什么位置?

1.12. 如何指定在每次调试器运行时不必复制依赖文件?

在 Visual Studio .NET 2003 中,右键单击该项目并选择 Properties。将 Build Action 更改为 None。这样就不会再复制此文件了。如果此文件已修改或者需要再复制,则将 Build Action 更改为 Content

1.13. 如何设置模拟器属性?

在 Visual Studio .NET 2003 中,从菜单中选择 Tools->Options。然后打开 Device Tools 文件夹并选择 Devices。现在应该能看到一个显示设备选择列表的对话框。选择您想要修改的设备,然后按 Configure 按钮。

现在您应该能够看到一个含有几个选项卡的对话框,它允许您访问和修改设置,例如内存和屏幕大小。

1.14. 如何调试 Microsoft .NET Compact Framework 应用程序?

Microsoft .NET Compact Framework 完全集成在 Visual Studio .NET 2003 中,它支持的调试功能与对其他 Visual Studio .NET 应用程序类型可用的调试功能相同。但是,在独立设备或在仿真器中调试运行的应用程序时,用户需要注意一些特殊事项。请参考下列建议以获得最详尽的 .NET Compact Framework 调试体验:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/CompactFXDebug.asp

1.15. 如何开始使用 Visual Basic .NET 为设备开发应用程序?

了解如何使用用于 Visual Studio .NET 的智能设备扩展 (SDE) 来为支持 .NET Compact Framework 的智能设备构建 Windows 应用程序。这篇文章包括对整个开发、调试和部署过程的检查,并探讨了 .NET Framework 和 .NET Compact Framework 之间的区别。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/sdeforvb.asp

1.16. 如何将 .NET Compact Framework 程序集安装到全局程序集缓存 (GAC) 中?

1.17. 当通过 Visual Studio .NET 2003 部署智能设备应用程序时,如何处理“共享冲突”?

这篇文章阐述了阻止通过 Visual Studio .NET 2003 部署应用程序的共享冲突产生的原因,并介绍了解决这一冲突的方法。
http://msdn.microsoft.com/library/en-us/dncfhowto/html/HOWTOSharingviolation.asp

1.18. 公共语言运行库 (CLR) 是什么?

.NET Compact Framework 提供了一个名为公共语言运行库的运行时环境,它运行代码并提供一些可以使开发过程更加轻松的服务。在这篇概述中可以了解更多内容:
http://msdn.microsoft.com/library/en-us/cpguide/html/cpconcommonlanguageruntimeoverview.asp

1.19. 全局程序集缓存 (GAC) 是什么?

每台安装公共语言运行库的计算机都有一个机器范围的代码缓存,称为全局程序集缓存。全局程序集缓存中存储的程序集是专门由计算机中的几个应用程序共享的程序集。这篇文章提供了有关 GAC 的更多信息:
http://msdn.microsoft.com/library/en-us/cpguide/html/cpconglobalassemblycache.asp

1.20. 在 .NET Compact Framework 中如何管理内存?

自动内存管理是在托管执行过程中 CLR 提供的服务之一。CLR 垃圾回收器管理应用程序的内存分配和释放,如这篇文章所描述的:
http://msdn.microsoft.com/library/en-us/cpguide/html/cpconautomaticmemorymanagement.asp

1.21. 当网络协议数超过 50 时,为什么就无法部署到设备?

请参见本 FAQ 中标题为“11.4.当网络协议数超过 50 时,为什么无法部署到设备?”的项。

1.22. 为什么有跨平台二进制?

.NET Compact Framework 和执行引擎是完整的 .NET Framework 和 CLR 的一个兼容子集实现。当不存在强名称绑定策略时,针对 .NET Compact Framework 编译的应用程序将会绑定和运行在整个 .NET Framework 上,但有一些重要的例外:

.NET Compact Framework 程序集由不同的强名称密钥对进行签名,这样 CLR 可以将它们与其全部 .NET 副本区分开来。

将来发布的完整的 .NET Framework 和 CLR 将包括绑定策略,它可以将完整的 .NET Framework 程序集替换为兼容 .NET Compact Framework 引用。然后在一些常见的情况中,它可以重复使用现有的组件而不需要重新链接。例如,如果您的组件只引用 .NET Compact Framework System 和 System.NET 类,则它可以在 .NET Compact Framework 和整个 .NET Framework 上很好地运行而不需要重新链接。

如果您的引用功能与 .NET Compact Framework 不同,例如基于 Windows Mobile 的 Pocket PC 特定的 UI 控件,则您的程序将会在与整个 .NET Framework 绑定时失败。

就像跨平台二进制兼容性简化了中间件组件的开发和部署一样,Microsoft 相信胖客户端应用程序应该利用特定于设备的功能来提高用户体验。这意味着最好的 GUI 代码可能是特定于目标的。

虽然 Microsoft 已经做出了很大的努力,将特定于设备的功能分解为离散的命名空间和程序集以避免绑定冲突,但可能存在版本 1 无法处理的不兼容分解的情况。在这些情况下,在整个 .NET Framework 中滥用特定于设备的功能将会引发运行时异常而非应用程序加载异常。

1.23. 所有这些 ARM 二进制是些什么?

XScale 支持 ARM v5 指令集,不过它也向后兼容 ARMv4 指令集。它有三个变种:

ARMv4 -> 它只支持 32 位 ARMv4 指令

ARMv4T ->“T”代表 Thumb。Thumb 是 ARM 16 位指令模式

ARMv4I ->“I”代表交互作用 (Interworking)。它允许 32 位指令和 16 位指令共存

对于其他的 ARM 处理器:

StrongARM (SA1110) -> 只支持 ARMv4 指令

ARM920T 等 -> 通常支持这三个变种

.NET Compact Framework 将提供三组用于 ARM 的二进制。

用于基于 Windows Mobile 2000 和基于 Windows Mobile 2002 的 Pocket PC 2002 的 ARMv4。它将运行 ARM 设备(包括 Xscale)的所有 ARM。部署到这些设备中的 cab 只能在名称中包含“arm”。

用于 Windows CE.NET 的 ARMv4。它将运行在通过 Platform Builder 中的 ARMv4 内核编译的 Windows CE.NET 设备中。它也是用于基于 Windows Mobile 的 Pocket PC 2003 的二进制。为这些设备部署的 cab 在名称中包含“armv4”。

用于 Windows CE.NET 的 ARMv4T 或 ARMv4I。它将运行在通过 Platform builder 中的 ARMv4T 或 ARMv4I 内核编译的 Windows CE.NET 设备中。部署到这些设备中的 cab 在名称中包含“armv4T”。

1.24. 如何写入设备的注册表中?

Visual Studio .NET 没有附带用于 Windows CE 的远程注册表编辑器。要设置注册表项,可以使用以下工具之一:

Microsoft Embedded Visual Tools Remote Registry Editor

Microsoft Windows CE Platform Builder Remote Registry Editor

基于 PHM Windows Mobile 的 Pocket PC Registry Editor(共享件,可以很容易在网上找到)

1.25. 安装完成后如何防止 .CAB 文件被删除?

将 .CAB 文件的属性设置为只读,可以防止 .CAB 文件被自动删除。

1.26. 如何确定设备中安装的 .NET Compact Framework 的版本?

发行的每个 .NET Compact Framework 版本都有一个不同的 Win32 文件版本号(它是与程序集版本相独立的版本号,对于发行的所有 .NET Compact Framework 第一版(包括 Service Pack),这两者应该是一样的)。

要查看安装的是什么版本,可以使用文件资源管理器,定位于设备的 /Windows 目录,并单击名为 CGACUTIL 的文件。将会弹出一个消息框,显示安装在设备中的 .NET Compact Framework 的 Win32 文件版本。

RTM = 1.0.2268.0SP1 = 1.0.3111.0SP2 Recall = 1.0.3226.0SP2 Beta = 1.0.3227.0SP2 Final = 1.0.3316.0

要以编程方式确定版本,可以使用 System.Environment.Version.ToString()。

要通过桌面安装程序确定版本,请参见本 FAQ 中标题为“3.10.如何通过桌面安装程序检测 .NET Compact Framework 的版本?”的项。

1.27. 如何将文件复制到模拟器中?

一种方式是在开发 PC 中创建一个文件共享,然后通过模拟器中的文件资源管理器连接到该共享。也可以将文件从共享位置复制并粘贴到模拟器的本地文件系统中。另一种方式是将文件添加到智能设备项目中,并将它们的 Build Action 属性设置为“Content”。有关“文件属性”的更多信息,请参阅 Visual Studio .NET 联机文档:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon/html/vbconfileproperties.asp

以下内容逐步介绍了如何将一个“Content”文件添加到智能设备项目中:

1.

打开或创建一个智能设备项目,

2.

在“View”菜单中,单击“Solution Explorer”,

3.

在“Solution Explorer”中,右键单击您的项目,指向“Add”并单击“Add Existing Item”。浏览至想要的文件并添加到该项目中。

4.

在“Solution Explorer”中,右键单击添加的文件并单击“Properties”,

5.

如果 Build Action 属性尚未设置,则将它设置为“Content”。

1.28. 为什么基于 Windows Mobile 的 Pocket PC 2002 SDK 安装失败?

表现在“注册组件”时,基于 Windows Mobile 的 Pocket PC 2002 SDK 安装程序挂起。

原因: 在尝试运行模拟器时,未注册的组件导致安装挂起。

解决办法: 在控制台窗口提示中键入:cd /WINNT/system32regsvr32 atl.dll

1.29. 如何调试智能设备应用程序使用的 Web 服务?

您需要将调试器附加到 ASP.NET 辅助进程中。

1.30. 什么是 .NET Compact Framework 的足迹?

.NET Compact Framework 存储大小:

在基于 Windows Mobile 的 Pocket PC 2000/2002 上有 1.55MB (ROM)

在用于 Pocket PC 2003 或 Windows CE .NET 设备的 Windows Mobile 上有 1.35MB (ROM)

运行 RAM 的要求:

.5 MB+(取决于应用程序)

典型的应用程序大小:

5 - 100 KB

1.31. 如何将 imgdecmp.dll 包括在模拟器映像中?

您必须让 OEM 将它包括在设备的映像中。如果您就是 OEM 而且您正在使用 Platform Builder 4.2,则包括 .NET 项目的 OS 依赖项会自动使 imgdecmp.dll 成为模拟器映像的一部分 — 如果这样不行则再引用 cesysgen.bat。另一种方法是设置环境变量“__SYSGEN_IMGDECMP=1”,显式强制该 DLL 包含在映像中。

1.32. 如何以编程方式替换全局程序集缓存 (GAC) 中的程序集?

可以通过以编程方式启动 cgacutil 来直接在 GAC 中安装程序集和从 GAC 中删除程序集。

使用 -u 选项可以从 GAC 删除程序集

使用 -i 选项可以在 GAC 中安装程序集

通常最安全的做法是重新安装一个程序集之前先将它删除。

1.33. 如何在台式计算机或便携式计算机中显示基于 Windows Mobile 的 Pocket PC 应用程序而不需要任何设备端配置?

从 Windows Mobile Developer Power Toys 下载 ActiveSync Remote Display:
http://www.microsoft.com/downloads/details.aspx?FamilyId=74473FD6-1DCC-47AA-AB28-6A2B006EDFE9&displaylang=en

1.34. 如何使 Activesync 能够从 Visual Studio .NET 2003 连接到模拟器会话?

请参见本 FAQ 中标题为“11.17.如何使 Activesync 能够从 Visual Studio .NET 2003 连接到模拟器会话?”的项。

1.35. 如何将文件复制到当前连接到桌面 ActiveSync 的设备中?

请参见本 FAQ 中标题为“11.18.如何将文件复制到当前连接到桌面 ActiveSync 的设备中?”的项。

1.36. 如何重点测试用户输入?

1.37. 从哪里可以获得平台生成器目标控件窗口的 UI 版本?

1.38. 从哪里可以获得用于基于 Windows Mobile 的 Pocket PC 2003 设备的命令外壳?

1.39. 如何获得当前运行进程的详细信息?

1.40. 如何从桌面远程启动基于 Windows Mobile 的 Pocket PC 上的应用程序?

1.41. 为什么不能加载具有相同名称的不同程序集?

这是设计的原因。您要么必须更改 DLL 的名称,或者如果 DLL 具有强名称,则将它们放在 GAC 中并使用具有完全强名称的 Assembly.Load。

1.42. 如何强制 Visual Studio .NET 2003 连接到较新版本的基于 Windows Mobile 的 Pocket PC 2003 模拟器?

从 Windows Mobile Developer Power Toys 下载 Emulator ActiveSync Connection Tool:
http://www.microsoft.com/downloads/details.aspx?familyid=74473FD6-1DCC-47AA-AB28-6A2B006EDFE9&displaylang=en

它允许 ActiveSync 通过 Visual Studio .NET 2003 连接到您的模拟器会话。为 4.2 模拟器创建一个 ActiveSync 会话,它可以使 Visual Studio 2003 将其视为一个真正的设备(选择 PPC 设备作为部署目标)。

1.43. 为什么我的自定义控件不能正确地显示在工具箱中?

当在 Visual Studio .NET 2003 中为智能设备自定义控件添加设计器支持时,您可能会碰到以下问题:

在设计时无法将一个图标与控件相关,以便显示在工具箱中

当添加到工具箱中时,该组件变灰

原因

使用独立于控件项目的设计项目。Visual Studio .NET 自动将项目的默认命名空间作为位图的名称。“默认命名空间”默认为项目名称。这可能会产生问题,因为设计项目的名称与运行时项目的名称略有差别。

没有设置正确的 ToolBoxItemFilterAttribute 值

解决方案

提供以下示例:Runtime VS.NET Project:MyProject类名称:MyProject.MyClass设计 VS.NET 项目名称:MyProject.DesignVS.NET 设计项目中的 BitMap 名称:Foo.bmp设计程序集中的位图名称:MyProject.Design.MyClass.bmp— 这样会产生问题,因为该位图需要以下名称:MyProject.MyClass.bmp

在以上示例中,将设计项目的默认命名空间设置为“MyProject”而非“MyProject.Design”就可以解决这个问题。

检查程序集中位图名称的最简单方法是运行 ILDASM 并打开 Manifest。该清单的尾部列出了嵌入式资源。

如果您创建了一个从 Component 类派生的自定义组件,则您的代码必须包括以下语句,这样您的组件才能出现在工具箱中:

ToolBoxItemFilterAttribute("NETCF",ToolBoxItemFilterType.Require)
ToolBoxItemFilterAttribute("System.CF.Windows.Forms", ToolBoxITemFilterType.Custom)

2. 图形

2.1. 如何创建 Graphics 对象?

图形对象根据其用途有几种创建方式:

通过 OnPaint,使用 PaintEventArgs 中提供的对象:

//C#

protected override void OnPaint(PaintEventArgs e)
{
  e.Graphics.DrawLine(...);
}

'VB

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    e.Graphics.DrawLine(...)
End Sub 'OnPaint

通过代码中的其他区域,该函数是 Control 的一个方法,可以用来创建任何控件的图形对象:

//C#

using System.Drawing;

Graphics g = this.CreateGraphics();

'VB

Imports System.Drawing

Dim g As Graphics = Me.CreateGraphics()

直接在一个位图中绘制:

//C#

using System.Drawing;

Bitmap bm = new Bitmap(10,10);
Graphics g = Graphics.FromImage(bm);

'VB

Imports System.Drawing

Dim bm As New Bitmap(10, 10)
Dim g As Graphics = Graphics.FromImage(bm)

2.2. 可以怎样优化 GDI+ 呈现?

当使用 Graphics 绘画调用时,有几种基本的编码实践可以帮助提高绘画速度:

只创建一个 Graphics 对象(或者使用来自 OnPaint 中的 PaintEventArgs 的 Graphics 对象)。

在屏幕外的位图中完成所有绘画,然后拖曳该位图,使它一次全部显示出来。

只重画图像的更改部分。

尽可能使绘制的源和目的大小相同(不要拉伸等等)。

也许最重要的实践是保留需要重画的项目的痕迹以便使出现的绘画量尽可能少。例如,如果拖曳光标跨过图像,就不需要重画整个图像。相反,只需重画前一个光标位置覆盖的图像部分。

2.3. 如何在窗体中绘制图像?

此示例显示了如何将一个图形作为窗体的背景图像显示:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/bkgndimage.aspx

2.4. 如何绘制具有透明度的图像?

绘制有透明度的图像需要一个指定透明颜色的 ImageAttributes 对象。当前,.NET Compact Framework 支持单种颜色的原色调透明度。虽然 SetColorKey 函数允许一个颜色范围,但最小和最大的颜色必须相同,否则会产生运行时 ArgumentException:

//C#

using System.Drawing.Imaging;

ImageAttributes attr = new ImageAttributes();

'VB

Imports System.Drawing.Imaging

Dim attr As New ImageAttributes()

以下代码演示了如何根据图像的左上像素设置透明色调。

//C#

attr.SetColorKey(bmp.GetPixel(0,0), bmp.GetPixel(0,0));

'VB

attr.SetColorKey(bmp.GetPixel(0,0), bmp.GetPixel(0,0))

也可以按照如下所述方法显式设置颜色:

//C#

attr.SetColorKey(Color.FromArgb(255,0,255),Color.FromArgb(255,0,255));
attr.SetColorKey(Color.Fuchsia, Color.Fuchsia);

'VB

attr.SetColorKey(Color.FromArgb(255,0,255),Color.FromArgb(255,0,255))
attr.SetColorKey(Color.Fuchsia, Color.Fuchsia)

然后可以用重载的 Graphics.DrawImage 函数(它将 ImageAttributes 对象作为一个参数)来绘制图像:

//C#

Rectangle dstRect = new Rectangle(0, 0, bmp.Width, bmp.Height);
g.DrawImage(bmp, dstRect, 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, attr);

'VB

Dim dstRect As New Rectangle(0, 0, bmp.Width, bmp.Height)
g.DrawImage(bmp, dstRect, 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, attr)

2.5. 当我在 TextBox 中调用 CreateGraphics 时它为什么会失败?

只有 Control 和 Form 类支持 Control.CreateGraphics()。

2.6. 如何确定文本的屏幕大小?

使用 Graphics 方法 MeasureString。以下代码演示了如何在一些文本周围绘制一个框:

//C#

using System.Drawing;

protected override void OnPaint(PaintEventArgs e)
{
  string s = "Hello World"

  Pen pen = new Pen(Color.Fuchsia);
  Font font = new Font("Arial", 18, FontStyle.Regular);
  Brush brush = new SolidBrush(Color.Black);

  SizeF sSize = e.Graphics.MeasureString(s, font);

  Rectangle r = new Rectangle(9, 199,(int)sSize.Width + 1, (int)sSize.Height + 1);

  e.Graphics.DrawRectangle(pen, r);
  e.Graphics.DrawString(s, font, brush, 10.0f, 200.0f);

  base.OnPaint (e);
}

'VB

Imports System.Drawing

Protected Overrides Sub OnPaint(e As PaintEventArgs)
  Dim s As String = "Hello World"
  
  Dim pen As New Pen(Color.Fuchsia)
  Dim font As New Font("Arial", 18, FontStyle.Regular)
  Dim brush = New SolidBrush(Color.Black)

  Dim sSize As SizeF = e.Graphics.MeasureString(s, font)

  Dim r As New Rectangle(9, 199, Fix(sSize.Width) + 1, Fix(sSize.Height) + 1)

  e.Graphics.DrawRectangle(pen, r)
  e.Graphics.DrawString(s, font, brush, 10F, 200F)

  MyBase.OnPaint(e)

End Sub 'OnPaint

2.7. 可以设置画笔的宽度吗?

.NET Compact Framework 中不可以设置画笔宽度。有一些替代办法,包括:

采用 Graphics.FillRectangle 方法绘制实心矩形

绘制多条并行线

采用 GAPI 编写自定义图形例程

2.8. 如何缩放图像?

虽然没有对缩放和拉伸单个图像的内在支持,但这些效果也可以很轻松地实现,方法是使用相关的 Graphics 对象创建新的 Bitmap 对象,然后将原有 Bitmap 想要的部分复制到新建对象上。以下示例创建了两个大小相同的位图,其中第二个位图包含第一个位图经放大的中心部分,并假定项目有一个名为 MyImage.bmp 的嵌入式资源。这样的技术也可以用来拉伸图像,方法是修改源和目的矩形以便它们没有保留其原有的纵横比。

//C#

using System.Drawing;
using System.Reflection;

Bitmap m_bmpOriginal;
Bitmap m_bmpZoom;

private void Form1_Load(object sender, System.EventArgs e)
{
    Assembly asm = Assembly.GetExecutingAssembly();
    m_bmpOriginal = new Bitmap(asm.GetManifestResourceStream(asm.GetName().Name
      + ".MyImage.bmp"));

    // Take the center quarter of m_bmpOriginal
    // and create stetch it into m_bmpZoom of the same size
    m_bmpZoom = new Bitmap(m_bmpOriginal.Width, m_bmpOriginal.Height);
    Graphics gZoom = Graphics.FromImage(m_bmpZoom);
    
    Rectangle srcRect = new Rectangle(m_bmpOriginal.Width / 4, m_bmpOriginal.Height / 4,
      m_bmpOriginal.Width / 2, m_bmpOriginal.Height / 2);
    Rectangle dstRect = new Rectangle(0, 0, m_bmpZoom.Width, m_bmpZoom.Height);
    gZoom.DrawImage(m_bmpOriginal, dstRect, srcRect, GraphicsUnit.Pixel);
}

protected override void OnPaint(PaintEventArgs e)
{
    e.Graphics.DrawImage(m_bmpOriginal, 0, 0);
    e.Graphics.DrawImage(m_bmpZoom, 125, 0);
    base.OnPaint (e);
}

'VB

Imports System.Drawing
Imports System.Reflection

Private m_bmpOriginal As Bitmap
Private m_bmpZoom As Bitmap

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Dim asm As [Assembly] = [Assembly].GetExecutingAssembly()
    m_bmpOriginal = New Bitmap(asm.GetManifestResourceStream((asm.GetName().Name _
      + ".MyImage.bmp")))

    ' Take the center quarter of m_bmpOriginal
    ' and create stetch it into m_bmpZoom of the same size
    m_bmpZoom = New Bitmap(m_bmpOriginal.Width, m_bmpOriginal.Height)
    Dim gZoom As Graphics = Graphics.FromImage(m_bmpZoom)

    Dim srcRect As New Rectangle(m_bmpOriginal.Width / 4, m_bmpOriginal.Height / 4, _
      m_bmpOriginal.Width / 2, m_bmpOriginal.Height / 2)
    Dim dstRect As New Rectangle(0, 0, m_bmpZoom.Width, m_bmpZoom.Height)
    gZoom.DrawImage(m_bmpOriginal, dstRect, srcRect, GraphicsUnit.Pixel)
End Sub 'Form1_Load

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    e.Graphics.DrawImage(m_bmpOriginal, 0, 0)
    e.Graphics.DrawImage(m_bmpZoom, 125, 0)
    MyBase.OnPaint(e)
End Sub 'OnPaint

2.9. 为什么我不能加载图像?

确保 imgdecmp.dll 位于设备的 Windows 目录。

有关更多信息,请参见本 FAQ 中的主题“ HYPERLINK /l "1.31" 1.31.如何将 imgdemp.dll 包括在模拟器映像中?”。

2.10. 如何使用 GAPI 创建图形引擎?

这篇文章描述了如何创建包装 GAPI (Game API) 的 DLL,使之与 .NET Compact Framework 兼容,并用它来创建和优化托管代码中的基本图形库。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/WrapGAPI1.asp

这篇文章实现了位图的加载和显示,从而扩展了“Dancing Rectangles”示例。它还实现了一些更高级的功能,例如动画位图、源和目的色调透明和 alpha 值混合处理(也就是半透明)。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/WrapGAPI2.asp

这篇文章实现了点、线和从 8 位位图转换而来的自定义 1 位字体的绘制,从而扩展了“Dancing Zombies”示例。它还实现了一个重写硬件按钮功能和跟踪按钮状态的输入系统。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/WrapGAPI3.asp

3. 部署

3.1. 如何创建基于 Windows Mobile 的 Pocket PC 安装应用程序?

这篇文章介绍了如何创建一个 .msi 文件,它可以从桌面运行,并在其他基于 Windows Mobile 的 Pocket PC 设备上安装应用程序。此开发过程是自动化的,所以它可以轻松地构建所有必需的组件并将其包装在 .msi 文件中。这篇文章提供了 C# 和 Microsoft Visual Basic .NET 的示例代码。

3.2. 应用程序安装中可以包括哪些可重新发布的文件?

您可以将最终用户可重新发布的文件随应用程序提供给您的客户,以便帮助他们更新设备。您不应该解开最终用户可重新发布的文件并将其中的内容发送给您的客户。不过您可以解开开发人员可重新发布的文件并将其中的内容发送给您的客户。

.NET Compact Framework 可重新发布的包可以在这里下载:
http://msdn.microsoft.com/mobility/downloads/updates/default.aspx

3.3. 如何在桌面上创建不需要 .NET Framework 的安装程序?

创建 INF 文件

基于 Windows Mobile 的 Pocket PC 安装程序的主要组件之一是 INF 文件。此文件描述了需要复制哪些文件以及需要为不同的目标平台创建哪些注册表项。

此 INF 文件是一个文本文件,它分成几节。每节都有一个节头,位于方括号中,它可以有几个仅与特定目标硬件类型相关的子节。

只要有子节,子节的优先级就高于父节,所以如果一个值在节及其子节中都进行了设置,则会使用子节中的值。基于这个原因,应该将共享设置放在父节中,而将特定于每个硬件配置的设置放在子节中。

请按照以下步骤创建 INF 文件:

在 [Version] 节中,将 Provider 值设置为您公司的名称。

在 [CEStrings] 节中,将. AppName 和 CompanyName 变量设置为应用程序名称和公司名称(不带空格)。

在 [Strings] 节中,您可能不需要进行任何更改,但要注意不同的 CPU 类型。

在 [DefaultInstall] 节中,列出应该调用的不同节,方法是将 CopyFiles、AddReg 和 CEShortcuts 值设置为不同的节名。如果有多个节,节名间应该以逗号分隔。

在 [SourceDiskFiles] 节中,列出要从 [SourceDiskNames] 节复制的各个文件和相应的编号。应该将这些编号看作是组编号,而非物理磁盘编号。通常文件是按类型分组的,例如“声音文件”、“图形文件”、“数据库文件”等等,但如何分组由您决定。

在 [DestinationDirs] 节中,为 [SourceDiskFiles] 节中的每种文件类型指定一个目标目录。请注意,有一些预定义的变量可供您使用,例如 CE1(指定的目标目录)和 CE2(WINDOWS 目录),所以指定诸如“%CE1%/Database”这样的目录将会在用户安装时选择的目录下创建一个子目录。

为 [DefaultInstall] 节的 CopyFiles 变量中的每一项创建节(名称必须严格匹配,所以要检查再检查)。在每一节中,指定文件的名称及用于复制文件的属性。属性可以在 eMbedded Visual Tools 文档中找到。

为 [DefaultInstall] 节的 RegSettings 变量中的每一项创建节。

为 [DefaultInstall] 节的 Shortcuts 变量中的每一项创建节。%CE17% 预定义变量表示 /WINDOWS/Start Menu 目录。

创建 CAB 文件

Cabinet 文件是高度压缩的文件,由包括在 eMbedded Visual Tools 中的 CABWIZ 实用工具构建。

CABWIZ 实用工具具有以下形式:

CABWIZ  [/err ] /cpu  

/cpu 参数后面的每一项将创建一个文件名为 YOURAPP.CPUTYPE.CAB 的独立 CAB 文件。每个 CAB 文件只包含压缩文件和用于匹配设备类型的设置。

创建 INI 文件

客户的个人计算机并没有读取 CAB 文件的内容;它只是将适当的 CAB 文件复制到基于 Windows Mobile 的 Pocket PC 中,然后由后者读取该 CAB 文件并使用 WCELOAD.EXE 应用程序提取内容。在这台个人计算机中,有一个名为 CEAPPMGR.EXE 的实用工具,它协调设备的安装、将设备类型与每个可用的 CAB 文件类型相匹配、将 CAB 文件发送到设备,并调用基于 Windows Mobile 的 Pocket PC 设备中的 WCELOAD.EXE 应用程序。

请注意,可以指定多个 INI 文件,这样将安装多个组件。这将允许使用基于 Windows Mobile 的 Pocket PC 中的 Remove Programs(位于“System”选项卡的 Settings 下)来删除个别组件。

小结

一旦您使用 CAB 文件、SETUP.INI 文件和一个 SETUP.EXE 文件来调用 CE Application Manager,您只需将这些文件放在光盘、磁盘或 ZIP 文件中,将它们分发给您的客户。理解获取这些文件的所需步骤可能会占用您第一次安装的大量时间,但以后的安装将会很快,因为您只需更改任何前面安装的源文件中的少量值即可。

3.4. 如何创建与基于 Windows Mobile 的 Pocket PC 平台无关的 CAB 文件?

您可以创建一个 .inf 文件,用它生成适合在任何基于 Windows Mobile 的 Pocket PC 设备上安装应用程序的 CAB 文件。具体做法请参见这个示例:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/cabfile.aspx

3.5. 为什么每种处理器类型都有不同的 CAB 文件?

每个 CAB 都包含一个小型本机可执行文件,它在设备中执行框架依赖项检测。此实用工具特定于处理器/平台,每种处理器类型都必须有单独的 CAB。

3.6. 如何创建一个桌面安装程序来检测目标设备中是否存在 .NET Compact Framework,并在需要时安装它?

MSDN Library 中标题为“Creating an MSI Package that Detects and Updates the .NET Compact Framework”的文章描述了一种可能会用到的技术:
http://msdn.microsoft.com/mobility/understanding/articles/default.aspx?pull=/library/en-us/dnnetcomp/html/netcfdepl.asp

3.7. 如何将 .NET Compact Framework Service Pack 部署到模拟器中?

从以下地址下载“Developer”版本的 service pack 并安装到您的桌面开发 PC 中(下载的标题如下所示:“Microsoft® .NET Compact Framework 1.0 SPx Developer Redistributable”)
http://msdn.microsoft.com/mobility/downloads/updates/default.aspx

然后将适当的 .NET Compact Framework cab 文件复制到模拟器中(按照下一段)。在模拟器中通过 File Explorer 指向 PC 中的一个共享,然后将该 cab 复制并粘贴到模拟器文件系统中的某个位置。现在从 File Explorer 启动该 cab 文件,如果询问是否覆盖所有项,则回答“是”。

模拟器CAB 文件

Windows Mobile 2002 for Pocket PC

netcf.core.ppc3.x86.cab

Windows Mobile 2003 for Pocket PC

netcf.core.wce4.x86.cab

Windows Mobile 2003 for Smartphone

不支持 RAM 安装

3.8. 如何将 SQL Server CE 包含在我的应用程序安装中?

要随一个应用程序安装 SQL Server CE,只需将恰当的 SQL Server CE CAB 文件作为应用程序安装的一部分加以安装。有两组与 SQL Server CE 相关的 cab。

开发人员 CAB 包括查询分析器和错误字符串。此 CAB 不应该包括在应用程序部署中。它包含在两个实际的文件中,一个用于基于 Windows Mobile 的 Pocket PC,一个用于 Windows CE 4.x 设备:

sqlce.dev.ppc3..cabsqlce.dev.wce4..cab

SQL Server CE CAB,它包括引擎、客户端代理和托管扩展,因为利用 System.Data.SqlServerCe 组件的应用程序需要客户端代理。此 CAB 也包含在两个实际的文件中,一个用于基于 Windows Mobile 的 Pocket PC,一个用于 Windows CE 4.x 设备:

sqlce.ppc3..cabsqlce.wce4..cab

访问 SQL Server 的应用程序,也就是利用 System.Data.SqlClient 组件的应用程序应该部署该“sql”CAB。此 CAB 也包含在两个实际的文件中,一个用于基于 Windows Mobile 的 Pocket PC,一个用于 Windows CE 4.x 设备:

sql.ppc3..cabsql.wce4..cab

所有这些 CAB 都包含在 Visual Studio .NET 2003 Professional Edtion 安装中。默认位置为:

/Program Files/Microsoft Visual Studio .NET 2003/CompactFrameworkSDK/v1.0.5000/Windows CE/...

3.9. 如何在启动时运行我的应用程序?

在 /Windows/Startup 中安装应用程序的快捷方式

在 HKLM/Init 中添加一个注册表项

P/Invoke CeRunAppAtEvent

3.10. 如何通过桌面安装程序检测 .NET Compact Framework 的版本?

请参考文章“Creating an MSI Package that Detects and Updates the .NET Compact Framework”:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfdepl.asp

具体为:

检测 Microsoft .NET Compact Framework您需要创建一个 .dll,它可以作为自定义操作从 MSI 文件调用。

首先使用 CeRapiInitEx 连接到设备。

使用 CeFindAllFiles 搜索设备的 Windows 目录中是否存在 GAC_mscorlib_*.dll。如果存在,则设备中已经安装了某种风格的 .NET Compact Framework。

查询设备注册表并枚举 HKLM/SOFTWARE/Microsoft/.NETCompactFramework 中的值。您应该找到表示现有 .NET Compact Framework 的内部版本的注册表项。(比如 1.0.2268.00:REG_DWORD:0)。此版本是由所有 .cab 安装创建的,并包含在以后所有 ROM 安装中。最初 ROM 安装的 RTM .NET Compact Framework 缺少这一项,所以如果您没有找到它,就假定它的值为 1.0.2268.0。

有关版本号的更多信息,请参见本 FAQ 中标题为“1.26.如何确定设备中安装的 .NET Compact Framework 的版本”的项。

4. 图形用户界面 (GUI):窗体

4.1. 如何创建全屏窗体?

您需要将 WindowState 属性设置为 Maximized。要创建“不可见”窗体,如同在窗体中的一个全屏图像,您还需要将 FormBorderStyle 设置为 None、关闭 ControlBox,并删除窗体中的任何菜单实例。

//C#

this.WindowState = FormWindowState.Maximized;
this.FormBorderStyle = FormBorderStyle.None;
this.ControlBox = false;
this.Menu = null;

'VB

Me.WindowState = FormWindowState.Maximized
Me.FormBorderStyle = FormBorderStyle.None
Me.ControlBox = False
Me.Menu = Nothing

4.2. 何时应该使用窗体构造函数,何时应该使用 Load 函数?

对于任何涉及 UI 的窗体,最好使用窗体的 load 函数。在构造函数中加载和创建数据和控件的实例通常是安全的。然而,任何涉及 UI 元素的控件或窗体的初始化都应该在 load 函数中完成。例如,在构造函数中创建控件的实例并添加到窗体中是安全的,但控件位置的设置就应该在 load 函数中完成。

4.3.如何将智能最小化按钮替换为关闭按钮?

将窗口风格转换为关闭而非智能最小化可以通过 IDE 中的设计器属性来完成,或者以编程方式完成。(x) 按钮将应用程序智能最小化,而 (ok) 按钮则将其关闭。

通过设计器转换窗口风格的方法是:在 Visual Studio IDE 中,打开窗体设计器,查看 Properties。右键单击窗体并选择 Properties。在 Window Style 节中,将 MinimizeBox 设置为 False

在代码中转换窗口风格的方法是:只需将以下的代码行添加到窗体的 load 函数中:

//C#

this.MinimizeBox = false;

'VB

Me.MinimizeBox = False

4.4. 如何创建适用于 .NET Compact Framework 的 Multiple Form Application Framework?

这篇文章讨论了如何为您的基于 .NET Compact Framework 的应用程序创建有效的用户界面引擎:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfuiframework.asp

4.5. 如何提高基于 .NET Compact Framework 的应用程序窗体加载性能?

了解如何通过这些简单的优化技术降低 .NET Compact Framework Windows 窗体应用程序加载所需要的时间:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfimproveformloadperf.asp

4.6. 如何在运行时修改窗体风格?

这个快速入门描述了一些代码语句,它们用于更改基于 Windows Mobile 的 Pocket PC 应用程序中的窗体的外观和行为:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/ppcformproperties.aspx

4.7. 如何滚动窗体内容?

这个快速入门介绍了如何使用水平和垂直滚动条控件来滚动和下拉窗体中的图像:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/scrolling.aspx

4.8. 如何生成浮动窗体?它看起来像是窗体始终全屏显示。

具有边框的顶级窗体始终全屏显示,不能移动和调整大小。无边框窗体或子窗体可以移动和调整大小。

使用:Form.BorderStyle = BorderStyle.None;

4.9.如何强制一个窗体保持最小化?

请参阅本 FAQ 中的主题“6.6.如何强制一个窗体保持最小化?”。

4.10. 我有一个含有许多控件的智能设备窗体。为什么会在运行时得到“NotSupportedException”?

如果您有一个含有许多控件的窗体,当您运行应用程序时可能会得到 NotSupportedException。如果您在调试器下运行,则可能会发现异常是由调用窗体构造函数中的 InitializeComponent() 引发的。您最可能遇到的一个事实是 Compact Framework CLR 硬性限制每个方法只有 64KB JITed 代码。这意味着当 CLR 将 IL 转换为方法(在本例中为 InitializeComponent 方法)时,产生的本机代码必须少于 64KB。如果产生的本机代码大于此值,则会引发 NotSupportedException。也可能出现的情况是,当在调试器下运行(例如通过 F5)时会看到异常,但不带调试器运行(通过 Ctrl-F5)时不会有异常。当 JITed 代码的大小非常接近于 64KB 限制时就会出现这种情况,因为调试器在运行时需要生成其他一些代码。

也可能出现的情况是,在设备中会产生这种异常,但在模拟器中不会(或反之),因为处理器家族之间 JITed 代码的大小有所不同(例如,许多基于 Windows Mobile 的 Pocket PC 设备使用 ARM 指令,而模拟器使用 x86 指令)。

超出此限制之前,窗体中可含有的控件数并没有一个固定的值。这是因为设计器生成的代码量因控件而异。例如,Button 控件通常生成的代码少于 TabControl 生成的代码,这取决于控件属性的设置方式。如果您通过属性网格中的生成器为控件填充集合,则拥有集合的控件(比如 ListBox 或 TreeView 控件)会生成大量代码。另外,本地化窗体(其 Localizable 属性设置为 True)中控件生成的代码会比非本地化窗体多,因为 InitializeComponent 中生成的代码会根据来自资源文件的值设置属性,而不是使用硬编码的值。

如果您碰到这种问题,有几种不同的技术可以使用:

将一个窗体分割成多个窗体。将大量控件放在一个窗体中对窗体加载和应用程序启动时间也有不利的影响。如果可能,要将 UI 分在两个或更多的窗体中。

不要通过设计时生成器填充大的内部控件集合。例如,如果您在 TreeView 控件的 Nodes 集合中添加许多节点,就会在 InitializeComponent 方法中额外添加大量代码。如果可能,应该移动这些代码,使集合填充到构造函数或 Form.Load 事件处理程序中。这种技术的缺点是集合将无法很容易地通过设计器编辑,但有助于调整生成代码的大小。

不要将自己的代码添加到 InitializeComponent 方法中。这通常是一个很好的指导原则,因为在这个方法中修改或添加由设计器生成的代码是不受支持的。这样做也会导致设计器中出现意外行为。如果您要将自己的自定义启动代码添加到窗体中,就应该将它放在构造函数或 Form.Load 事件处理程序中。

在运行时以编程方式初始化类似的控件。例如,如果您创建了 12 个 Button 控件,它们只有 Text 和 Position 属性不同,则您可以考虑通过一个循环来创建和初始化,而不是让这 12 个控件都在设计器生成的代码中创建和初始化。另外,如果您自己编写代码实现,不要将代码放在 InitializeComponent 中。

修改 InitializeComponent 的一个令人遗憾的负面影响是:在 InitializeComponent 外的代码中实例化的任何控件都无法使用窗体设计器编辑。另外,如果您手动编辑 InitializeComponent 中的代码,则会发现设计器可能无法正确解释您修改过的代码,从而不再呈现窗体。基于这些原因,最好选择上面列出的不修改 InitializeComponent 的解决办法。

4.11. 哪种关闭窗体的方法更合适:Application.Exit 还是 Form.Close?

Application.Exit 是一种强行退出方式,就像 Win32 的 PostQuitMessage()。它意味着放弃所有消息泵,展开调用堆栈,并将执行返回给系统。

在 Windows(Win32 或 .NET)中关闭应用程序的正确方式是关闭它的主应用程序窗口(例如 Form.Close)。主消息泵结束后依然存在的任何窗口都需要手动关闭。在应用程序退出之前通过调用 Form.Close 或 Form.Dispose 来关闭窗口是清除窗口的良好做法,但这需要您有意识地去做。我们需要记住,.NET Framework 的 OnClosing() 是 Win32 的托管版本的 WM_CLOSE,而非 WM_DESTROY。

另外,如果您使用 form.Close(),通过处理 OnClosing 或 OnClosed 事件,就可以让您的应用程序清理内容、关闭文件等。如果您通过 Application.Exit 强行退出应用程序,就无法调用这些事件。

4.12. 为什么会在用于 Smartphone 的 Windows Mobile 2003 中显示一个消息框,它含有 Abort、Retry 和 Fail 组合按钮或 Yes、No、Cancel 组合按钮,或者因为默认按钮导致 NotSupportedException 而有第三种按钮?

用于 Smartphone 的 Windows Mobile 2003 只支持有 1 个或 2 个按钮的消息框。

4.13 如何获得基于 Windows Mobile 的 Pocket PC 开始菜单中最近使用过的 (MRU) 列表中的一个图标?

为您的应用程序创建一个快捷方式,放在 /windows/start menu/programs 中的某个位置。当通过这个快捷方式启动您的应用程序时,它的图标就会出现在 MRU 列表中。

4.14 如何将一个窗体放在屏幕中央?

要显示一个非全屏窗体,请确保窗体的 FormBorderStyle 属性设置为 FormBorderStyle.None。要使窗体位于中央,请将以下代码添加到窗体的 FormLoad 事件处理程序中:将 FormBorderStyle 设置为 FormBorderStyle.None,然后:

//c#

Rectangle screen = Screen.PrimaryScreen.Bounds;
this.Location = new Point((screen.Width - this.Width) / 2,
  (screen.Height - this.Height ) / 2);

'VB

Dim theScreen As Rectangle
theScreen = Screen.PrimaryScreen.Bounds()
Me.Location = New Point((theScreen.Width - Me.Width) / 2, _
  (theScreen.Height - Me.Height) / 2)

4.15 为什么我不能显示一个已经关闭了的窗体?

一旦一个窗体关闭,它就会被处置,因此可能被系统回收垃圾,所以试图显示已关闭的窗体是不安全的。一种替代的解决方案是使用 Form.Hide 和 Form.Show 来分别隐藏和显示窗体。

4.16 如何启用一个应用程序的多个实例?

.NET Compact Framework 不支持多实例。以下代码示例提供了一种解决方案,它允许当启动应用程序时(但是已有一个运行着的实例)实例化而非最大化应用程序。

注:并非所有版本的 OS(包括未来版本)都支持以下代码,也不保证在所有这些版本中都能工作。

// C#

using System.Runtime.InteropServices;
using System.Reflection;

private void Form1_Load(object sender, System.EventArgs e)
{
    this.Text = string.Format("Form {0}", new Random().Next());
}

[DllImport("CoreDll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("CoreDll")]
public static extern int SetWindowText(IntPtr hWnd, string lpString);

protected override void OnResize(EventArgs e)
{
    Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
    IntPtr hWnd = FindWindow("#NETCF_AGL_PARK_",
      asm.GetModules()[0].FullyQualifiedName);

    if (hWnd != IntPtr.Zero)
        SetWindowText(hWnd, "#42");

    base.OnResize (e);
}

'VB
Imports System.Runtime.InteropServices
Imports System.Reflection

Private Sub Form1_Load(ByVal sender As Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    Me.Text = String.Format("Form {0}", New Random().Next())
End Sub 'Form1_Load

 _
Public Shared Function FindWindow(ByVal lpClassName As String, _
  ByVal lpWindowName As String) As IntPtr
End Function

 _
Public Shared Function SetWindowText(ByVal hWnd As IntPtr, _
  ByVal lpString As String) As Integer
End Function

Protected Overrides Sub OnResize(ByVal e As EventArgs)
    Dim asm As [Assembly] = System.Reflection.Assembly.GetExecutingAssembly()
    Dim hWnd As IntPtr = FindWindow("#NETCF_AGL_PARK_", _
      asm.GetModules()(0).FullyQualifiedName)
    If hWnd.ToInt32() <> IntPtr.Zero.ToInt32() Then
        SetWindowText(hWnd, "#42")
    End If
    MyBase.OnResize(e)
End Sub 'OnResize

5. 图形用户界面 (GUI):常规

5.1. 如何创建图像按钮或多行文本按钮?

创建图像按钮或多行文本按钮需要一个自定义控件。自定义控件可以用必需的自定义数据来进行按钮的绘制。有关创建自定义控件的更多信息,请参见以下链接:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/customctrlscompactfx.asp

这个快速入门描述了如何创建图像按钮:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/picturebutton.aspx

5.2. TextBox.AcceptsReturn 如何在 .NET Compact Framework 中工作?

虽然您可以将 AcceptsReturn 设置为 false,但它始终是按 true 来操作的。如果您想用 ENTER 键来激活特定按钮,您可以从 TextBox 派生一个类,当 KeyPress 事件发生时,提供 ENTER 的事件处理代码。

5.3. 当数据加载到 ComboBox 中时,为什么没有触发 SelectedIndexChanged 事件?

这是一个已知的问题,将在 .NET Compact Framework 今后的版本中解决。

5.4. Show 和 ShowDialog 之间有什么区别?

ShowDialog 以模式方式显示窗体,这意味着它是一个模块化的函数调用,只有当窗体关闭时才返回。此方法返回一个 DialogResult 枚举类型,它指明关闭情况。

Show 是一个显示控件的非模块化函数,它会立即返回而且没有返回结果。显示控件等同于将 Visible 属性设置为 true。当调用 Show 方法后,Visible 属性会返回 true 值直到 Hide 方法被调用。

5.5. 为什么我不能创建 ContextMenu 分隔符?

这是一个已知的 bug,即当在 ContextMenu 控件中添加一个 ContextMenu 项后,将它设为分隔符就会引发 NotSupportedException。问题在于 WinCE 有一个限制,即当 ContextMenu 的父级是一个控件时,向该 ContextMenu 添加一个菜单项后就不允许将该菜单项标记(更改)为分隔符。在 Visual Studio 2003 中,设计器分割设备项目的代码与在桌面上类似,都会产生这种问题。可以这样来解决:在一个独立于 InitilizeComponent 的方法中添加 ContextMenu。

5.6. 当我在运行时向 ToolBar 分配一个 ImageList 时,为什么图像没有显示?

您可以将 ImageList 分配给 ToolBar,通常在窗体的 load 函数中进行,但要确保再次应用 ToolBar 按钮的图像索引。在本机 ToolBar 控件中,不支持在设置 ToolBar 的 ImageList 之前设置 ToolBar 按钮的图像索引。

5.7. 如何设置等待光标?

这是将光标更改为等待光标的代码:

//C#

Cursor.Current = Cursors.WaitCursor;

'VB

Cursor.Current = Cursors.WaitCursor

这是将光标重新更改为默认光标的代码:

//C#

Cursor.Current = Cursors.Default;

'VB

Cursor.Current = Cursors.Default

5.8. 如何在菜单项中显示“and”符 (&)?

.NET Compact Framework 当前不支持这种能力。使用“&&”不会在菜单项的文本中出现“and”符。

5.9. 如何创建基于 Microsoft .NET Compact Framework 的动画控件?

这篇文章将向您介绍如何构建基于 .NET Compact Framework 的动画控件:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/animationcontrol.asp

5.10. 如何为 .NET Compact Framework 创建自定义控件?

了解如何为 .NET Compact Framework 创建控件会提高您在 .NET Framework 中创建控件的能力。这篇文章还包括一个创建自定义控件的代码示例:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/customctrlscompactfx.asp

5.11. 如何创建基于 Microsoft .NET Compact Framework 的图像按钮?

这篇文章讨论了创建基于 .NET Compact Framework 的图像按钮的方法:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/ImageButton.asp

5.12. 如何使用 Microsoft .NET Compact Framework MessageWindow 类?

了解如何使用 .NET Compact Framework MessageWindow 类来创建 NotifyIcon:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/messagewindow.asp

鼠标指针单击定义为 Rectangle 结构的自定义控件时,或者当鼠标单击 Panel 控件时,这个快速入门使用 MessageWindow 来向窗体发送消息:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/messagewindow.aspx

5.13. 如何向 DataGrid 添加行和列?

这个快速入门介绍了如何在运行时向 DataGrid 控件添加或删除行和列:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/datagridadding.aspx

5.14. 如何在 DataGrid 中实现单元格编辑?

.NET Compact Framework 中的 DataGrid 控件提供了完整的 .NET Framework 中的 DataGrid 控件的几乎全部功能。.NET Compact Framework 版本的 DataGrid 的一个主要区别在于它无法在运行时在 DataGrid 中编辑单元格。这个快速入门介绍了一种以编程方式编辑单元格的方法:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/datagridediting.aspx

5.15. 如何设置 DataGrid 的 DataSource?

.NET Compact Framework 中的 DataGrid 控件提供了完整 .NET Framework 中 DataGrid 控件的几乎全部功能。.NET Compact Framework 版本的一个主要区别在于它无法将控件的 DataSource 设置为 DataSet。
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/datagrid.aspx

5.16. 如何对 DataGrid 的列进行排序?

.NET Compact Framework 中的 DataGrid 控件提供了完整 .NET Framework 中 DataGrid 控件的几乎全部功能。.NET Compact Framework 版本的 DataGrid 的一个主要区别在于它无法在运行时对列排序。
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/datagridsorting.aspx

5.17. 如何对 ListView 项目进行排序?

.NET Compact Framework 不支持 ListView.Sort 方法,但您仍然可以对项目排序。这个快速入门定义了 IComparable 接口的实现,可以将它用于 ArrayList.Sort 方法:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/listviewsort.aspx

5.18. 如何使用 InputPanel Component (SIP)?

这个快速入门介绍了如何启动和禁用基于 Windows Mobile 的 Pocket PC 中的软输入面板 (SIP),以及当 SIP 显示时如何调整选项卡控件的大小使之适应窗体区域的改变:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/inputpanel.aspx

多个窗体应该共享同一个 InputPanel 对象。可以这样实现:在主窗体中创建 InputPanel,再将它传递给后续窗体或向其他需要利用该 SIP 的窗体公开一些 SIP 控件方法或属性。

5.19. 如何创建自定义事件?

这个快速入门描述了如何派生 Button 类及重写方法来实现双击事件。当在 SystemInformation.DoubleClickTime 值(它是在双击有效的鼠标点击期间允许的毫秒数)的范围内点击一次后又点击一次时就会引发此自定义事件。
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/btndclick.aspx

.NET Compact Framework 不支持控件(包括 Windows.Forms.Control 基类)的 OnEnter 和 OnLeave 方法。然而,因为支持 Control.OnMouseMove 方法,所以您可以使用它和 Control.Capture 属性来确定鼠标进入或离开控件的时间。
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/enterleave.aspx

5.20. 如何创建所有者描述的列表框?

您可以使用 .NET Compact Framework 来创建所有者描述的列表框。.NET Compact Framework 不支持列表框和其他控件的 DrawMode、DrawItem 及其他绘制成员,但您可以编程实现该功能。这个快速入门提供了一个自定义控件类来创建所有者描述的列表框,并将控件实现为列表框来选择字体。
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/ownerdrawnlistbox.aspx

5.21. 如何创建 true/false 复选框?

这个快速入门扩展了 Windows.Forms.CheckBox 控件的功能,从而可以创建 True/False 复选框:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/tfcheckbox.aspx

5.22. 当我设置 InputPanel.Enabled = true 时,为什么会产生异常?

InputPanel 组件要求 MainMenu 控件位于窗体中,并且要求窗体在屏幕上可见。

5.23. 为什么我的自定义控件不会自动继承其父级的字体?

.NET Compact Framework 中不支持这种行为。

5.24. 当我手动键入新文本时,为什么 NumericUpDown 和 DomainUpDown 控件不会激发它们的 ValueChanged 和 SelectedItemChanged 事件?

只有当用户在代码中更改控件的值时才会激发 ValueChanged 和 SelectedItemChanged 事件,或者通过向上/向下箭头的方式激发。用户在控件中输入文本时不会激发这些事件。

5.25. 为什么 NumericUpDown 控件增加的值不是设置的增加值?

当按下向上或向下箭头之后,如果下一个值不是增量的倍数,则它会在这个方向上继续往前(向上或向下),直到发现增量的下一个倍数为止。

5.26. 为什么我无法将 StatusBar 控件放在窗体的任何位置?它好像总要跑到底部?

StatusBar 控件始终停靠在窗体的底部。它的大小不能更改。

5.27. 为什么我的自定义控件不会自动继承其父级的背景颜色?

.NET Compact Framework 中不支持这种行为。解决办法是重写 OnParentChanged 方法并手动设置颜色:

//C#

protected override void OnParentChanged(EventArgs e)
{
  base.OnParentChanged(e);
  this.BackColor = Parent.BackColor;
}

'VB

Protected Overrides Sub OnParentChanged(ByVal e As EventArgs)
  MyBase.OnParentChanged(e)
  Me.BackColor = Parent.BackColor
End Sub 'OnParentChanged

5.28. 为什么 NumericUpDown 控件虽然能够接受小数值,但好像不能使用任何大于 2^16 的数值?

虽然数值形式的 NumericUpDown 控件接受小数值,但 .NET Compact Framework 将此控件的值视为整数。例如,指定值 10.23,则控件会将它计算为 10。另外,当此控件在基于 Windows Mobile 的 Pocket PC 上实现时,它不接受大于 16 位有符号整数的值。

5.29. 为什么我不能在 DomainUpDown 控件中键入文本以及让它选择相关的项目?

DomainUpDown 控件不对键入控件的文本执行检验(不像完整的 .NET Framework)。当您键入一些新的文本并按下向上/向下箭头时,它在更改前就移到了下一项。

5.30. 为什么 OpenFileDialog 限制为“My Documents”文件夹?

OpenFileDialog 的初始目录限制为“My Documents”文件夹或其任何子文件夹。基于 Windows Mobile 的 Pocket PC 操作系统强加的这个限制是为了帮助用户将文件组织在标准目录中。

5.31. 如何能够不通过菜单激活 SIP (InputPanel)?

SIP 可以通过 P/Invoke 函数“SipShowIM”来激活,如下所示。

//C#

using System.Runtime.InteropServices;

const uint SIPF_OFF = 0x0;
const uint SIPF_ON = 0x1;

[DllImport("coredll.dll")]
private extern static void SipShowIM(uint dwFlag);

'VB

Imports System.Runtime.InteropServices

Const SIPF_OFF As Integer = &H0
Const SIPF_ON As Integer = &H1

 _
Private Shared Function SipShowIM(ByVal dwFlag As Integer) As Integer
End Function

5.32. 如何向 TreeView 中的每个节点添加一个子节点?

向所有节点添加子节点是这样实现的:循环访问 TreeView 中的所有节点,并向每个节点添加新节点。

//C#

foreach (TreeNode node in treeView1.Nodes)
{
    node.Nodes.Add(new TreeNode("SubNode"));
}

'VB

Dim node As TreeNode
For Each node In  treeView1.Nodes
    node.Nodes.Add(New TreeNode("SubNode"))
Next node

5.33. 如何确定 DataGrid 中的行或列的数目?

DataGrid 中的行数和列数可以通过数据源本身确定。例如:

//C#

DataSet ds = new DataSet();

int numRows = ds.Tables[0].Rows.Count;
int numCols = ds.Tables[0].Columns.Count;

'VB

Dim ds As New DataSet()

Dim numRows As Integer = ds.Tables(0).Rows.Count
Dim numCols As Integer = ds.Tables(0).Columns.Count

如果 DataGrid 绑定到 DataView,您还可以使用 DataView.Count。

5.34. 如何创建所有者描述的 Listbox?

请参见 .NET Compact Framework QuickStarts、Implementing Events 主题:
http://samples.gotdotnet.com/quickstart/compactframework/doc/btndclick.aspx

5.35. 如何在 .NET Compact Framework 中实现 Control.GetNextControl?

.NET Compact Framework 中的控件的 Tab 键顺序与 Form.Controls 集合中的控件的顺序直接对应。因此,可以这样实现 GetNextControl:确定指定控件的索引,并确定它在集合中的邻居。

//C#

public Control GetNextControl(Control ctl, bool forward)
{
    int curIndex = this.Controls.IndexOf(ctl);

    if (forward)
    {
        if (curIndex < this.Controls.Count)
            curIndex++;
        else
            curIndex = 0;
    }
    else
    {
        if (curIndex > 0)
            curIndex--;
        else
            curIndex = this.Controls.Count - 1;
    }

    return this.Controls[curIndex];
}

'VB

Public Function GetNextControl(ByVal ctl As Control, _
  ByVal forward As Boolean) As Control
    Dim curIndex As Integer = Me.Controls.IndexOf(ctl)

    If forward Then
        If curIndex < Me.Controls.Count Then
            curIndex += 1
        Else
            curIndex = 0
        End If
    Else
        If curIndex > 0 Then
            curIndex -= 1
        Else
            curIndex = Me.Controls.Count - 1
        End If
    End If
    
    Return Me.Controls(curIndex)

End Function 'GetNextControl

5.36. 当用户单击树视图节点时,如何才能得到通知?

TreeView 不支持 Click 事件,不过有一个解决办法,那就是使用 AfterSelect 事件来代替。

5.37. 如何设置全屏多行编辑控件窗口的标题?

这在当前版本的 .NET Compact Framework 中不支持。

5.38. 当我将 ComboBox.SelectedValue 设置为 validItemInCollection 时,为什么不能看到选定的有效项?

只有当控件被数据绑定时,设置 SelectedValue 属性才有效。

5.39. 如何检测发生“tap & hold”的位置,以便在自定义控件中生成上下文菜单?

处理 ContextMenu.Popup 事件,然后使用“Control.MousePosition”查询当前鼠标坐标。

5.40. 为什么滚动条的值永远不会被设置为最大值?

与 NumericUpDown 控件类似,可达的最大值是缩略图上面的第一个空行。更具体地说,根据编辑器属性,它等于:

Maximum - (LargeChange + 1)。

5.41. 如何通过 Tab 键从自定义控件回到前一个控件?

当检测到 Keys.Up 键时,在 KeyDown 事件处理程序中调用 this.Parent.Controls(this.Parent.GetChildIndex(customcontrol) - 1).Focus()。

5.42. 如何添加具有透明度的工具栏按钮?

图标支持透明,不过在 Visual Studio .NET 2003 设计器中有一个已知的 bug,它会创建不正确的代码并使得图标不透明。一种解决办法是在 InitializeComponent 外面的 ImageList 中添加一个图标文件,并将图标文件作为内容或嵌入式资源添加到项目中。以下代码演示了这种做法:

//C#

using System.Drawing;
using System.IO;
using System.Reflection;

// Loaded as content example
private void Form1_Load(object sender, System.EventArgs e)
{
    this.imageList1.Images.Add(new Icon(File.Open("fullFileName.ico",
      FileMode.Open)));

    this.toolBar1.Buttons[0].ImageIndex = 0;
}

// Loaded as a resource example
private void Form1_Load(object sender, System.EventArgs e)
{
    this.imageList1.Images.Add(new
      Icon(Assembly.GetExecutingAssembly().GetManifestResourceStream(
      ".filename.ico")));

    this.toolBar1.Buttons[0].ImageIndex = 0;
}

'VB

Imports System.Drawing
Imports System.IO
Imports System.Reflection

' Loaded as content example
Private Sub Form1_Load1(ByVal sender As Object, ByVal e As System.EventArgs)

    Me.imageList1.Images.Add(New Icon(File.Open("fullFileName.ico", _
      FileMode.Open)))

    Me.toolBar1.Buttons(0).ImageIndex = 0

End Sub 'Form1_Load1

' Loaded as a resource example
Private Sub Form1_Load2(ByVal sender As Object, ByVal e As System.EventArgs)

    Me.imageList1.Images.Add(New _
      Icon([Assembly].GetExecutingAssembly().GetManifestResourceStream( _
      ".filename.ico")))

    Me.toolBar1.Buttons(0).ImageIndex = 0

End Sub 'Form1_Load2

5.43. 如何允许在 ListView 控件中有多重选择?

将 ListView 控件的 CheckBoxes 属性设置为 True,这样可以在列表中的每一项旁边显示一个 CheckBox,从而允许用户选择多个项。

也可以使用 P/Invoke 来设置 ListView 的功能,以便接受多重选择。有关这一点的示例,请参见本 FAQ 中标题为“6.24.如何允许一个控件接受多重选择?”的项。

6. 互操作性和本机代码

6.1. 如何调用位于本机 DLL 中的函数?

本机 DLL 函数可以通过平台调用 (P/Invoke) 来调用。这些文章将提供有关这种技术的说明和更多信息:

了解如何使用 .NET Compact Framework 的平台调用 (P/Invoke) 功能:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfintrointerp.asp

探讨 .NET Compact Framework 中的高级互操作性。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfadvinterop.asp

学习如何创建可通过平台调用在智能设备应用程序中使用的非托管函数。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/unmanagedfuncs.asp

了解如何使用 .NET Compact Framework 在托管和非托管代码之间封送数据。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfmarshallingtypes.asp

了解如何使用 dumpbin.exe 作为在基于 Microsoft .NET Compact Framework 的应用程序中声明 P/Invoke 的手段。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfdumpbinpinvoke.asp

6.2. 如何将 dumpbin.exe 作为声明 P/Invoke 的手段?

请参见本 FAQ 中的主题“6.1.如何调用位于本机 DLL 中的函数?”。

6.3. 如何编写基于 Microsoft .NET Compact Framework 的应用程序的非托管函数?

请参见本 FAQ 中的主题“6.1.如何调用位于本机 DLL 中的函数?”。

6.4. 如何进行 P/Invoke GetTickCount?

不需要 P/Invoke GetTickCount 函数,原因是 Environment.TickCount 就可以实现。有关更多信息,请参见本 FAQ 中的主题“7.2.如何计算准确的时间间隔?”。

6.5. 如何确定一个设备有多少可用内存?

可以 P/Invoke GetSystemMemoryDivision 和 GlobalMemorySystem 函数来确定内存如何在程序和存储中划分与分配。

可以在 API 引用文档中找到参数定义。

//C#

using System.Runtime.InteropServices;

public class MEMORYSTATUS
{
  public uint dwLength;
  public uint dwMemoryLoad;
  public uint dwTotalPhys;
  public uint dwAvailPhys;
  public uint dwTotalPageFile;
  public uint dwAvailPageFile;
  public uint dwTotalVirtual;
  public uint dwAvailVirtual;
}

[DllImport("CoreDll.dll")]
public static extern void GlobalMemoryStatus
(
  MEMORYSTATUS lpBuffer
);

[DllImport("CoreDll.dll")]
public static extern int GetSystemMemoryDivision
(
  ref uint lpdwStorePages,
  ref uint lpdwRamPages,
  ref uint lpdwPageSize
);

public void Test()
{
  uint storePages = 0;
  uint ramPages = 0;
  uint pageSize = 0;
  int res = GetSystemMemoryDivision(ref storePages, ref ramPages, ref pageSize);

  MEMORYSTATUS memStatus = new MEMORYSTATUS();
  GlobalMemoryStatus(memStatus);
}

'VB

Imports System.Runtime.InteropServices

Public Structure MEMORYSTATUS
  Public dwLength As UInt32
  Public dwMemoryLoad As UInt32
  Public dwTotalPhys As UInt32
  Public dwAvailPhys As UInt32
  Public dwTotalPageFile As UInt32
  Public dwAvailPageFile As UInt32
  Public dwTotalVirtual As UInt32
  Public dwAvailVirtual As UInt32
End Structure 'MEMORYSTATUS

 _
Private Shared Sub GlobalMemoryStatus(ByRef ms As MEMORYSTATUS)
End Sub

 _
Public Shared Function GetSystemMemoryDivision( _
  ByRef lpdwStorePages As UInt32, _
  ByRef lpdwRamPages As UInt32, _
  ByRef lpdwPageSize As UInt32) As Integer
End Function

Public Shared Sub Test()
  Dim storePages As UInt32
  Dim ramPages As UInt32
  Dim pageSize As UInt32
  Dim res As Integer = GetSystemMemoryDivision(storePages, ramPages, pageSize)

  Dim memStatus As New MEMORYSTATUS
  GlobalMemoryStatus(memStatus)
End Sub 'Test

6.6. 如何强制一个窗体保持最小化?

1.

重写窗体的 OnGotFocus 方法。

2.

查找窗体的窗口。

3.

调用 ShowWindow(hwnd, SW_MINIMIZE) 来强制窗体最小化。

//C#

using System.Runtime.InteropServices;

[DllImport("CoreDll")]
public static extern IntPtr FindWindow(string className,string WindowsName);

[DllImport("CoreDll")]
public static extern bool ShowWindow(IntPtr hwnd,int nCmdShow);

const int SW_MINIMIZE = 6;

protected override void OnGotFocus(EventArgs e)
{
  IntPtr hwnd = FindWindow(null, this.Text);
  ShowWindow(hwnd, SW_MINIMIZE);
  base.OnGotFocus(e);
}

'VB

Imports System.Runtime.InteropServices

 _
Public Shared Function FindWindow(ByVal className As String, _
  ByVal WindowsName As String) As IntPtr
End Function

 _
Public Shared Function ShowWindow(ByVal hwnd As IntPtr, _
  ByVal nCmdShow As Integer) As Boolean
End Function

Private Const SW_MINIMIZE As Integer = 6

Protected Overrides Sub OnGotFocus(ByVal e As EventArgs)
  Dim hwnd As IntPtr = FindWindow(Nothing, Me.Text)
  ShowWindow(hwnd, SW_MINIMIZE)
  MyBase.OnGotFocus(e)
End Sub 'OnGotFocus

6.7. 如何在 .NET Compact Framework 中,在平台调用 (P/Invoke) 期间封送处理类型?

请参见本 FAQ 中的主题“6.1.如何调用位于本机 DLL 中的函数?”。

6.8. 如何获得一个控件或窗体的句柄 (HWND)?

实际上有几种使用 P/Invoke 的方法可以访问控件的 HWND 句柄。下面显示了其中两种方法,一种使用 GetCapture,另一种使用 FindWindow。

//C#

[DllImport("coredll.dll"]
public static extern IntPtr GetCapture();

[DllImport("coredll.dll")]
public static extern IntPtr FindWindow(String lpClassName, String lpWindowName);

this.Text = "FindMe";
IntPtr hwnd1 = FindWindow(null, "FindMe");

this.Capture = true;
IntPtr hwnd2 = GetCapture();
this.Capture = false;

'VB

_
Public Shared Function GetCapture() As IntPtr
End Function

 _
Public Shared Function FindWindow(ByVal lpClassName As String, _
  ByVal lpWindowName As String) As IntPtr
End Function

Me.Text = "FindMe"
Dim deskWin As IntPtr = FindWindow(Nothing, "FindMe")

Me.Capture = True
Dim hwnd As IntPtr = GetCapture()
Me.Capture = False

6.9. 如何使用性能计数器函数?

可以使用 QueryPerformanceFrequency 和 QueryPerformanceCounter 函数来创建高分辨率的计时方案。这些函数是 OEM 专有的,在本例中没有实现,它们通常默认为 GetTickCount 函数。因此,使用这些函数将始终确保计时器(如果实现)尽可能准确。当实现时,这种功能提供的准确性比 GetTickCount 或 Environment.TickCount 要高,实际上后者调用的是前者。

在这种情况下性能计数器是 GetTickCount 的一个实例,QueryPerformanceFrequency 会将计时器的频率设置为 1000。如果这些函数都没有实现,则它们会返回 0。以下代码演示了如何使用这些函数。

//C#

[DllImport("CoreDll.dll")]
public static extern int QueryPerformanceFrequency(ref Int64 lpFrequency);

[DllImport("CoreDll.dll")]
public static extern int QueryPerformanceCounter(ref Int64 lpPerformanceCount);

private void TestTimer()
{
  System.Int64 freq = 0;
  if (QueryPerformanceFrequency(ref freq) != 0)
  {
    System.Int64 count1 = 0;
    System.Int64 count2 = 0;

    if (QueryPerformanceCounter(ref count1) != 0)
    {
      System.Threading.Thread.Sleep(1200);
      QueryPerformanceCounter(ref count2);
      System.Int64 time_ms = (count2 - count1) * 1000 / freq;
    }
  }
}

'VB

_
Public Shared Function QueryPerformanceFrequency(ByRef lpFrequency As Int64) As Integer
End Function

 _
Public Shared Function QueryPerformanceCounter(ByRef lpPerformanceCount As Int64) As Integer
End Function

Private Sub TestTimer()
    Dim freq As System.Int64 = 0

    If QueryPerformanceFrequency(freq) <> 0 Then
        Dim count1 As System.Int64 = 0
        Dim count2 As System.Int64 = 0

        If QueryPerformanceCounter(count1) <> 0 Then
            System.Threading.Thread.Sleep(1200)
            QueryPerformanceCounter(count2)
            Dim time_ms As System.Int64 = (count2 - count1) * 1000 / freq
        End If
    End If
End Sub 'TestTimer

6.10. 通过 P/Invoke 封送处理类型有哪些限制?

返回值

只能是小于或等于 32 位的值类型

没有浮点

参数

只支持封送处理直接复制到本机结构中的类型

可直接复制到本机结构中的类型 -> 托管和本机两种结构在内存中有相同的表示

非直接复制到本机结构中的类型 -> 需要进行内存转换

由于只有直接复制到本机结构中的类型,所以所有对象都是固定的,不会被复制

例外:在 VB.NET 中传递 String ByVal

这意味着您不能封送处理嵌套对象,因为它需要进行内存转换(非直接复制到本机结构中)

只能是小于或等于 32 位的值类型

值在堆栈中传递

例外:float32

引用

传递可直接复制到本机结构中的引用类型

将引用传递给值类型

这是传递 float32 的方法

可以传递值类型数组

在本机获得指向第一个对象的指针,对象按照您期望的那样顺序排列

String 较特殊,它传递字符数组 -> 不可变

StringBuilder 较特殊,它传递字符数组 -> 可变(需要单独传递长度)

注:C# bool 只有 8 位,不等于 Win32 BOOL

对齐方式:默认编译器对齐(4 字节)

Marshal.GetLastWin32Error 支持 GetLastError() 语义

不受支持:

MarshalAs:不支持非直接复制到本机结构中的类型

StructLayout:无法更改布局

委托 (Delegate)

DateTime

只支持默认调用约定

6.11. 当我对 GetLastError 进行 P/Invoke 时,为什么得到奇数个返回节点?

通常对 Windows GetLastError() API 进行 P/Invoke 是不好的做法,因为 CLR 产生的本机调用可能已经更改了最近的错误代码。相反,应该标记 P/Invoke 声明来保存返回的错误代码并调用 System.Runtime.InteropServices.Marshal.GetLastWin32Error() 方法来检索该错误代码。

//C#

using System.Runtime.InteropServices;
using System.ComponentModel;

[DllImport("coredll.dll", SetLastError=true)]
public static extern uint Foo();

if (Foo() == 0)
{
    throw new Win32Exception(Marshal.GetLastWin32Error(), "Foo failed");
}

'VB

Imports System.Runtime.InteropServices
Imports System.ComponentModel

 _
Public Shared Function Foo() As Integer
End Function

If (Foo() = 0) Then
    Throw New Win32Exception(Marshal.GetLastWin32Error(), "Foo failed")
End If

6.12. 可以传递给本机函数的参数数目是否有限制?

是。.NET Compact Framework 第 1 版限制为 12 个。

6.13. 当我对本机函数进行 P/Invoke 时,为什么会产生“NotSupportedException”?

有三种常见的可能:

托管代码中的声明不正确

.NET Compact Framework 不支持您试图要做的事情

在导出时改变 dll 名称

请检查以下情况:

是否违背任何 .NET Compact Framework P/Invoke 限制?

是否有参数需要预分配内存(即,参数是指针)?如果是这样,您就应该将一个引用传递给现有的变量。

导出函数的名称是否正确?可以通过 DUMPBIN.EXE 来验证

您是否试图传递过多参数?

例如,在以上的第二种情况,RegOpenKey API 需要一个指向 HKEY 的指针作为它的最后一个参数。您可以按照以下方式声明和调用它:

//C#

[DllImport("coredll.dll", SetLastError=true)]
public static extern long RegOpenKey(
    IntPtr hkey,
    string lpSubKey,
    ref IntPtr hkeyResult
);

public long OpenMySubKey()
{
    IntPtr hkey = IntPtr.Zero;
    return RegOpenKey(HKEY_CLASSES_ROOT, "MySubKey", ref hkey);
}

'VB

 _
Public Shared Function RegOpenKey(ByVal hkey As IntPtr, ByVal lpSubKey As String, _
  ByRef hkeyResult As IntPtr) As Long
End Function

Public Function OpenMySubKey() As Long
    Dim hkey As IntPtr = IntPtr.Zero
    Return RegOpenKey(HKEY_CLASSES_ROOT, "MySubKey", hkey)
End Function 'OpenMySubKey

6.14. 如何将 byte[] 转换为 IntPtr?

有几种方法可以访问与字节数组相对应的 IntPtr。

第一种是使用不安全的代码块来访问直接指向字节数组的指针。

//C#

unsafe
{
    byte[] test = new byte[5];
    fixed (byte* p = &test[0])
    {
        *p = 0xff;
    }
}

也可以使用 GCHandle 来获得对象。

//C#

using System.Runtime.InteropServices;

byte[] test = new byte[5];
GCHandle hObject = GCHandle.Alloc(test, GCHandleType.Pinned);
IntPtr pObject = hObject.AddrOfPinnedObject();

if(hObject.IsAllocated)
    hObject.Free();

'VB

Imports System.Runtime.InteropServices

Dim test(4) As Byte
Dim hObject As GCHandle = GCHandle.Alloc(test, GCHandleType.Pinned)
Dim pObject As IntPtr = hObject.AddrOfPinnedObject()
If hObject.IsAllocated Then
    hObject.Free()
End If

最后,可以这样实现:通过 LocalAlloc 创建内存块并将数据封送处理到该内存块。

//C#

[DllImport("coredll.dll",SetLastError=true)]
public static extern IntPtr LocalAlloc(uint uFlags, uint uBytes);

[DllImport("coredll.dll",SetLastError=true)]
public static extern IntPtr LocalFree(IntPtr hMem);

[DllImport("coredll.dll",SetLastError=true)]
public static extern IntPtr LocalReAlloc(IntPtr hMem, uint uBytes, uint fuFlags);

public const uint LMEM_FIXED = 0;
public const uint LMEM_MOVEABLE = 2;
public const uint LMEM_ZEROINIT = 0x0040;

byte[] test = new byte[5];
IntPtr p = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, (uint)test.Length);

if (p == IntPtr.Zero)
{
    throw new OutOfMemoryException();
}
else
{
    Marshal.Copy(test, 0, p, test.Length);
}

'VB

 _
Public Shared Function LocalAlloc(ByVal uFlags As UInt32, _
  ByVal uBytes As UInt32) As IntPtr
End Function

 _
Public Shared Function LocalFree(ByVal hMem As IntPtr) As IntPtr
End Function

 _
Public Shared Function LocalReAlloc(ByVal hMem As IntPtr, _
  ByVal uBytes As UInt32, ByVal fuFlags As UInt32) As IntPtr
End Function

Public Const LMEM_FIXED As Integer = 0
Public Const LMEM_MOVEABLE As Integer = 2
Public Const LMEM_ZEROINIT As Integer = &H40

Dim test(4) As Byte
Dim p As IntPtr = LocalAlloc(Convert.ToUInt32(LMEM_FIXED Or LMEM_ZEROINIT), _
  Convert.ToUInt32(test.Length))
If p.Equals(IntPtr.Zero) Then
    Throw New OutOfMemoryException
Else
    Marshal.Copy(test, 0, p, test.Length)
End If

6.15. 当我调用来自本机 DLL 的函数时,为什么会产生 MissingMethodException?

当确定 MissingMethodException 的情况时,有几个问题需要考虑。当产生异常时,应该验证以下几点:

面向基于 Windows Mobile 的 Pocket PC 2003 是否使用 Microsoft eMbedded Visual C++ 4.0。

面向基于 Windows Mobile 的 Pocket PC 2000 或 2002 是否使用 Microsoft eMbedded Visual Tools 3.0

验证函数的参数是否与原来的那些相匹配

本机代码中的 long 通常是 32 位,而在 .NET Compact Framework 中是 64 位

要注意 bool

在 MSDN 文档的一个 Microsoft 特定章节中,bool 大小被描述为因用于构建二进制的 Visual C++ 版本而异:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dv_vccelng4/html/ellrfbool.asp

BOOL 被定义为 int(32 位值)

非直接复制到本机结构中的类型和封送处理支持:
http://msdn.microsoft.com/library/en-us/dv_evtuv/html/etconNonBlittableTypesMarshalingSupport.asp

确保函数名称拼写正确

验证 DLL 的所在位置正确 — Windows 或目标设备的执行文件夹

使用 DUMPBIN 验证函数的名称没有在导出 DLL 时改变(extern "C" 修复这个问题)。有关这个主题的更多信息,可以在本 FAQ 中的“6.1.如何调用位于本机 DLL 中的函数?”小节找到。

验证导入的 DLL 没有依赖于其他 DLL。缺少依赖的 DLL 会导致 MissingMethodException。

有关最新的 Microsoft eMbedded Visual Tools 和 SDK 下载,请访问 MSDN 移动与嵌入式技术开发人员中心“产品与更新”下载页面,位于:
http://msdn.microsoft.com/mobility/downloads/updates/default.aspx

6.16. 如何设置系统时间?

可以通过 P/Invoke SetSystemTime 函数来设置系统时间。

//C#

using System.Runtime.InteropServices;

public struct SYSTEMTIME 
{
    public ushort wYear;
    public ushort wMonth; 
    public ushort wDayOfWeek; 
    public ushort wDay; 
    public ushort wHour; 
    public ushort wMinute; 
    public ushort wSecond; 
    public ushort wMilliseconds; 
}

[DllImport("coredll.dll")]
public extern static void GetSystemTime(ref SYSTEMTIME lpSystemTime);

[DllImport("coredll.dll")]
public extern static uint SetSystemTime(ref SYSTEMTIME lpSystemTime);

// Set the clock ahead one hour
SYSTEMTIME st = new SYSTEMTIME();
GetSystemTime(ref st);
st.wHour = (ushort)(st.wHour + 1 % 24);
SetSystemTime(ref st);

'VB

Imports System.Runtime.InteropServices

Public Structure SYSTEMTIME
    Public wYear As UInt16
    Public wMonth As UInt16
    Public wDayOfWeek As UInt16
    Public wDay As UInt16
    Public wHour As UInt16
    Public wMinute As UInt16
    Public wSecond As UInt16
    Public wMilliseconds As UInt16
End Structure

 _
Public Shared Sub GetSystemTime(ByRef lpSystemTime As SYSTEMTIME)
End Sub

 _
Public Shared Function SetSystemTime(ByRef lpSystemTime As SYSTEMTIME) As UInt32
End Function

'Set the clock ahead one hour
Dim st As New SYSTEMTIME
GetSystemTime(st)
st.wHour = Convert.ToUInt16(((Convert.ToInt32(st.wHour) + 1)) Mod 24)
SetSystemTime(st)

6.17. 如何以编程方式软重置设备?

可以通过 P/Invoke KernelIoControl 函数来软重置设备,如以下代码所示。有关如何使用该函数及扩展该示例功能的更多信息,请参考 Visual Studio .NET 帮助。

注:在基于 Windows Mobile 的 Smartphone 设备中,只有当您签署一个有特权的证书时,它才会生效。

//C#

using System.Runtime.InteropServices;

public const uint FILE_DEVICE_HAL = 0x00000101;
public const uint METHOD_BUFFERED = 0;
public const uint FILE_ANY_ACCESS = 0;

public uint CTL_CODE(uint DeviceType, uint Function, uint Method, uint Access)
{
    return ((DeviceType << 16) | (Access << 14) | (Function << 2) | Method);
}

[DllImport("Coredll.dll")]
public extern static uint KernelIoControl
(
    uint dwIoControlCode,
    IntPtr lpInBuf,
    uint nInBufSize,
    IntPtr lpOutBuf,
    uint nOutBufSize,
    ref uint lpBytesReturned
);

uint ResetPocketPC()
{
    uint bytesReturned = 0;
    uint IOCTL_HAL_REBOOT = CTL_CODE(FILE_DEVICE_HAL, 15, 
      METHOD_BUFFERED, FILE_ANY_ACCESS);
    return KernelIoControl(IOCTL_HAL_REBOOT, IntPtr.Zero, 0, 
      IntPtr.Zero, 0, ref bytesReturned);
}

private void Form1_Load(object sender, System.EventArgs e)
{
    DialogResult r = MessageBox.Show
    (
        "Are you sure you want to reset?",
        "Test",
        MessageBoxButtons.YesNo,
        MessageBoxIcon.Question,
        MessageBoxDefaultButton.Button2
    );

    if (r == DialogResult.Yes)
    {
        ResetPocketPC();
    }
}

'VB

Public Const FILE_DEVICE_HAL As Integer = &H101
Public Const METHOD_BUFFERED As Integer = 0
Public Const FILE_ANY_ACCESS As Integer = 0

Public Function CTL_CODE( _
  ByVal DeviceType As Integer, _
  ByVal Func As Integer, _
  ByVal Method As Integer, _
  ByVal Access As Integer) As Integer

    Return (DeviceType << 16) Or (Access << 14) Or (Func << 2) Or Method

End Function 'CTL_CODE

 _
Public Shared Function KernelIoControl _
( _
    ByVal dwIoControlCode As Integer, _
    ByVal lpInBuf As IntPtr, _
    ByVal nInBufSize As Integer, _
    ByVal lpOutBuf As IntPtr, _
    ByVal nOutBufSize As Integer, _
    ByRef lpBytesReturned As Integer _
) As Integer
End Function

Function ResetPocketPC() As Integer
    Dim bytesReturned As Integer = 0
    Dim IOCTL_HAL_REBOOT As Integer = CTL_CODE(FILE_DEVICE_HAL, _
      15, METHOD_BUFFERED, FILE_ANY_ACCESS)
    Return KernelIoControl(IOCTL_HAL_REBOOT, IntPtr.Zero, 0, _
      IntPtr.Zero, 0, bytesReturned)
End Function 'ResetPocketPC

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
  Handles MyBase.Load
    Dim r As DialogResult = MessageBox.Show( _
        "Are you sure you want to reset?", _
        "Test", _
        MessageBoxButtons.YesNo, _
        MessageBoxIcon.Question, _
        MessageBoxDefaultButton.Button2)

    If r = DialogResult.Yes Then
        ResetPocketPC()
    End If

End Sub 'Form1_Load

6.18. 如何将一个图标放在标题栏上,而不管哪个窗体是活动的?

在当前版本的 .NET Compact Framework 中不支持这项功能。不过您可以 P/Invoke 基于 Windows Mobile 的 Pocket PC 的通知系统来做到这一点。有关更多信息,请参考以下内容:

本机 Notification API 为:SHNotificationAdd、SHNotificationRemove、SHNotificationGetData 和 SHNotificationUpdate。

6.19. 如何禁用和捕获硬件按钮?

6.20. 如何隐藏开始图标?

可以使用 SHFullScreen API 来隐藏开始图标。

//C#

const uint SHFS_SHOWTASKBAR = 0x0001;
const uint SHFS_HIDETASKBAR = 0x0002;
const uint SHFS_SHOWSIPBUTTON = 0x0004;
const uint SHFS_HIDESIPBUTTON = 0x0008;
const uint SHFS_SHOWSTARTICON = 0x0010;
const uint SHFS_HIDESTARTICON = 0x0020;

[DllImport("aygshell.dll")]
static extern uint SHFullScreen(IntPtr hwndRequester, uint dwState);

[DllImport("coredll.dll")]
public static extern IntPtr GetCapture();

private void Form1_Load(object sender, System.EventArgs e)
{
    Capture = true;
    IntPtr hwnd = GetCapture();
    Capture = false;
    SHFullScreen(hwnd, SHFS_HIDESTARTICON);
}

'VB

Const SHFS_SHOWTASKBAR As Integer = &H1
Const SHFS_HIDETASKBAR As Integer = &H2
Const SHFS_SHOWSIPBUTTON As Integer = &H4
Const SHFS_HIDESIPBUTTON As Integer = &H8
Const SHFS_SHOWSTARTICON As Integer = &H10
Const SHFS_HIDESTARTICON As Integer = &H20

  _
Shared Function SHFullScreen(ByVal hwndRequester As IntPtr, ByVal dwState As Integer) As Integer
End Function

  _
Public Shared Function GetCapture() As IntPtr
End Function

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs)

   Capture = True
   Dim hwnd As IntPtr = GetCapture()
   Capture = False

   SHFullScreen(hwnd, SHFS_HIDESTARTICON)

End Sub 'Form1_Load

6.22. 在哪可以找到 P/Invoke 示例的集中库?

此示例演示了如何 P/Invoke 许多有用的本机函数,它们无法直接通过 .NET Compact Framework 使用。它提供了一个测试窗体,此窗体枚举所有可用的测试过程并允许用户从中选择和运行:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/PInvokeLib.asp

6.23. 如何通过 Waveform Audio 界面播放和录制 .WAV 音频文件?

了解如何使用 Waveform Audio Interface 来录制和播放“.wav”文件:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/WaveInOut.asp

6.24. 如何允许一个控件接受多重选择?

有些控件可以使用以下代码来实现:

//C#

using System.Runtime.InteropServices;

private const int GWL_STYLE = -16;
private const int LVS_SINGLESEL = 0x0004;

[DllImport("coredll")]
private static extern IntPtr GetCapture();

[DllImport("coredll")]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);

[DllImport("coredll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex,
  int dwNewLong);

// Toggle off single selection
this.listView1.Capture = true;
IntPtr hWnd = GetCapture();
this.listView1.Capture = false;
int style = GetWindowLong(hWnd, GWL_STYLE);
SetWindowLong(hWnd, GWL_STYLE, (style & ~LVS_SINGLESEL));

'VB

Imports System.Runtime.InteropServices

Private Const GWL_STYLE As Integer = -16
Private Const LVS_SINGLESEL As Integer = &H4

 _
Private Shared Function GetCapture() As IntPtr
End Function

 _
Private Shared Function GetWindowLong(ByVal hWnd As IntPtr, _
  ByVal nIndex As Integer) As Integer
End Function

 _
Private Shared Function SetWindowLong(ByVal hWnd As IntPtr, _
  ByVal nIndex As Integer, ByVal dwNewLong As Integer) As Integer
End Function

' Toggle off single selection
Me.ListView1.Capture = True
Dim hWnd As IntPtr = GetCapture()
Me.ListView1.Capture = False
Dim style As Integer = GetWindowLong(hWnd, GWL_STYLE)
SetWindowLong(hWnd, GWL_STYLE, (style And (Not LVS_SINGLESEL)))

7. 常规

7.1. 如何确定应用程序的根目录?

应用程序可以使用 Reflection 来确定它运行所在的目录,并且可以使用 IO.Path 命名空间轻松地修改该目录。

//C#

using System.Reflection;
using System.IO;

// This is the full directory and exe name
String fullAppName = Assembly.GetExecutingAssembly().GetName().CodeBase;

// This strips off the exe name
String fullAppPath = Path.GetDirectoryName(fullAppName);

// This adds a file name to the path
String fullFileName = Path.Combine(fullAppPath, "myfile.txt");

'VB

Imports System.IO
Imports System.Reflection

' This is the full directory and exe name
Dim fullAppName As String = [Assembly].GetExecutingAssembly().GetName().CodeBase

' This strips off the exe name
Dim fullAppPath As String = Path.GetDirectoryName(fullAppName)

' This adds a file name to the path
Dim fullFileName As String = Path.Combine(fullAppPath, "myfile.txt")

了解如何确定当前执行的应用程序的路径。在 Embedded Visual Basic 中,执行的应用程序路径可以通过 App.Path 属性来获得。可以通过检验执行程序集的 AssemblyName 对象(它充分描述了程序集的标识)来确定执行应用程序的路径:
http://msdn.microsoft.com/library/en-us/dncfhowto/html/HOWTOExecutingAppPath.asp

这个快速入门获得包含程序集及相关数据文件的目录的路径说明。Windows CE .NET 并没有一开始就为应用程序提供一个当前的目录设置:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/getappdir.aspx

7.2. 如何计算准确的时间间隔?

应用程序有四个主要的方法可以确定时间间隔:

System.Environment.TickCount维护一个有符号整数值,代表自打开设备以来经过的毫秒数。不过 .NET Compact Framework 是不确定的,所以不能保证这个值比 500 毫秒分辨率还准确,虽然通常它会比这个值还准确。

GetTickCount()Environment.TickCount 属性已经调用了 GetTickCount 函数,所以 P/Invoke 这个函数并没有什么用处。

性能监视器可以用于分析目的,但在发布的应用程序中并不打算使用。有关更多信息,请参见本 FAQ 中的主题“7.5.如何使用性能监视器?”。

计时器应用程序可以提供一个委托给使用 System.Threading.Timer 来设置特定间隔的线程计时器。

性能计数器如果由 OEM 实现,则保证 QueryPerformanceCounter 函数可以提供最高分辨率的可用计时器。有关如何使用性能计数器的信息,请参见本 FAQ 中的主题“6.9.如何使用性能计数器函数?”。

7.3. 如何以流的形式访问嵌入式资源?

要访问嵌入式资源,应用程序只需访问相关的程序集并调用 GetManifestResourceStream 函数。以下是从嵌入式资源创建 Bitmap 的示例:

//C#

using System.Reflection;

Assembly asm = Assembly.GetExecutingAssembly();
Bitmap bmpSprite = new Bitmap(asm.GetManifestResourceStream("AssemblyName.FileName"));

'VB

Imports System.Reflection

Dim asm As [Assembly] = [Assembly].GetExecutingAssembly()
Dim bmpSprite As New Bitmap(asm.GetManifestResourceStream("AssemblyName.FileName"))

在以上的示例中,字符串的 AssemblyName 部分可以在运行时通过调用 asm.GetName().Name 来获得。

注:如果程序集名称中有空格,则会替换为下划线“_”,并且必须这样访问。

7.4. 为什么我会得到这样的消息:“An unhandled exception of type 'System.Net.Sockets.SocketException' occurred in System.dll?”

这是 .NET Compact Framework 中的一个 bug。它是由于 Windows CE 中的底层安全套接字层 (SSL) 实现的局限性造成的。不过有一个解决办法。如果您将 req.AllowWriteStreamBuffering 设置为 true,并且不设置 req.ContentLength,则不会看到这种异常。

7.5. 如何使用性能监视器?

性能计数器是通过修改设备的注册表创建的:

1.

创建注册表项:“HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/.NETCompactFramework/PerfMonitor”

2.

在这个项中创建一个名为 Counters 的 DWORD 类型值。

3.

将 Counters 的值设置为 1 来启用性能计数器,或者设置为 0 来禁用它们。

启用性能计数器将会在应用程序关闭时创建一个名为“mscoree.stat”的文本文件。此文件可以在设备的根目录中找到。它是一个按“固定宽度”分隔的文件,所以可以直接导入 Excel。

注: 在分析期间只能有一个托管应用程序在运行。

注: 当使用计数器时,性能会下降 30%。

7.6. 如何阻止应用程序关闭?

应用程序可以重写 OnClosing 方法,当您将 CancelEventArgs.Cancel 设置为 true 时就可以取消关闭操作。

//C#

protected override void OnClosing(CancelEventArgs e)
{
  e.Cancel = true;
}

'VB

Protected Overrides Sub OnClosing(ByVal e As CancelEventArgs)
  e.Cancel = True
End Sub 'OnClosing

7.7. 如何在我的应用程序中启动另一个应用程序?

您可以调用本机函数 CreateProcess 来启动第二个应用程序,然后调用本机函数 WaitForSingleObject 来暂停调用的应用程序直到第二个应用程序完成。这个快速入门使用基于 Windows Mobile 的 Pocket PC 计算器来演示这个过程:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/waitforsingleobject.aspx

7.8. .NET Compact Framework 应用程序创建了什么线程?

.NET Compact Framework 应用程序创建的线程达到四个:

一个主应用程序线程。

一个用于控制各种周期计时器和可由系统或应用程序安排的超时的线程。

一个用于跟踪 TCP/IP 活动接口(模拟在 Windows XP 中存在,但在 Windows CE 中不存在的媒体感知行为 (media sense behavior))变化的线程。

一个用于运行 Finalizer 对象的线程。它是在对第一个Finalizable 的对象进行垃圾回收时创建的。

7.9. 如何将一个字节数组转换为结构?

虽然在 C++ 中可以方便快捷地将一个类或结构内存映射保存成文件,并通过类型转换直接重新加载到类中,但托管代码的性质决定它不可能这样做。然而,可以创建一个类,将内存映射作为其数据加以存储并利用属性来访问和设置这些数据。如以下示例所示:

//C#

public class MyClass
{
  protected byte[] m_data = null;
  // uint uiDummy
  // short sDummy
 
  // This is a bit unsafe so you should throw an
  // exception or assert if the byte array length is
  // not 6. A safer but less memory efficient approach
  // would be to set m_data = new byte[6] and then copy
  //bytes to m_data.
  public MyClass(byte[] bytes) {m_data = bytes;}

  // Get/Set the uint
  public uint uiDummy
  {
    get {return BitConverter.ToUInt32(m_data, 0);}
    set
    {
      Buffer.BlockCopy(BitConverter.GetBytes(value), 0,
  m_data, 0, BitConverter.GetBytes(value).Length);
    }
  }

  // Get/Set the short
  public short sDummy
  {
    get {return BitConverter.ToInt16(m_data, 4);}
    set
    {
      Buffer.BlockCopy(BitConverter.GetBytes(value),
  0, m_data, 4, BitConverter.GetBytes(value).Length);
    }
  }
}

byte[] fromFile = {1,1,1,1,2,2};
MyClass myClass = new MyClass(fromFile);

uint test1 = myClass.uiDummy; // 0x1010101
short test2 = myClass.sDummy; // 0x202

myClass.sDummy = 0x0505;      // Test setting the short
uint test4 = myClass.uiDummy; // 0x1010101
short test5 = myClass.sDummy; // 0x505

'VB

Public Class ByteClass
  Protected m_data As Byte() = Nothing

  ' uint uiDummy
  ' short sDummy
  ' This is a bit unsafe so you should throw an exception
  ' or assert if the byte array length is not 6. A safer
  ' but less memory efficient approach would be to set
  ' m_data = new byte[6] and then copy bytes to m_data.
  Public Sub New(ByVal bytes() As Byte)
    m_data = bytes
  End Sub 'New

  ' Get/Set the uint
  Public Property uiDummy() As UInt32
    Get
      Return BitConverter.ToUInt32(m_data, 0)
    End Get
    Set(ByVal Value As System.UInt32)
      Buffer.BlockCopy(BitConverter.GetBytes(Value), _
  0, m_data, 0, BitConverter.GetBytes(Value).Length)
    End Set
  End Property

  ' Get/Set the short
  Public Property sDummy() As Short
    Get
      Return BitConverter.ToInt16(m_data, 4)
    End Get
    Set(ByVal Value As Short)
      Buffer.BlockCopy(BitConverter.GetBytes(Value), _
  0, m_data, 4, BitConverter.GetBytes(Value).Length)
   End Set
  End Property
End Class 'ByteClass

Dim fromFile As Byte() = {1, 1, 1, 1, 2, 2}
Dim testClass As New ByteClass(fromFile)

Dim test1 As System.UInt32 = testClass.uiDummy ' 0x1010101
Dim test2 As Short = testClass.sDummy          ' 0x202
testClass.sDummy = &H505                       ' Test short
Dim test4 As System.UInt32 = testClass.uiDummy ' 0x1010101
Dim test5 As Short = testClass.sDummy          ' 0x505

7.10. 我是否可以创建一个自定义委托来传递给调用?

不可以,在 .NET Compact Framework 中只能够调用 EventHandler 方法。实现它的正确方法如下所述:

//C#

public void HandleMe(object o, EventArgs e) {...}
form.Invoke(new EventHandler(form.HandleMe));

'VB

Public Sub HandleMe(o As Object, e As EventArgs)
End Sub 'HandleMe
form.Invoke(New EventHandler(AddressOf form.HandleMe))

虽然以下代码能够编译,但不能正确工作:

//C#

public delegate void MyHandler();
public void HandleMeBadly() {...}
form.Invoke(new MyHandler(form.HandleMeBadly));

'VB

Delegate Sub MyHandler()
Public Sub HandleMeBadly()
End Sub 'HandleMeBadly
form.Invoke(New MyHandler(form.HandleMeBadly))

7.11. 如何通过 Microsoft .NET Compact Framework 访问 Phone API?

请参阅以下文章来了解如何通过基于 .NET Compact Framework 的应用程序访问 Phone API:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfphoneapi.asp

7.12. 如何在基于 Windows Mobile 的 Pocket PC 上生成 GUID?

Guid.NewGuid 方法生成新的 GUID,但在 .NET Compact Framework 中不支持。阅读这篇文章来了解如何为基于 Windows Mobile 的 Pocket PC 应用程序生成符合文档化的 GUID 规范的 GUID 对象:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/PPCGuidGen.asp

7.13. 如何将 Pocket Outlook 数据合并到基于 .NET Compact Framework 的应用程序中?

这篇文章讨论了如何使用 InTheHand Pocket Outlook .NET 组件:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/inthehandpoom.asp

7.14. 我是否可以确定 .NET Compact Framework 中文件的版本?

Visual Studio .NET 2003 帮助为 .NET Compact Framework 提供了一个不安全的代码示例,它 P/Invoke C# 程序员参考 (Programmer's Reference) 中的 GetFileVersionInfo。此示例的一个问题是它需要 OEM 提供这种功能,不能始终有保证。

要查看程序集的版本可以使用 Reflection,如下所示:

//C#

using System.Reflection;

String ver = Assembly.GetExecutingAssembly().GetName().Version.ToString();

'VB

Imports System.Reflection;

Dim ver As String = [Assembly].GetExecutingAssembly().GetName().Version.ToString()

7.15. 有哪些技术可用于进行 .NET Compact Framework 后台处理?

执行后台处理牵涉到很多方面,要求非常仔细地进行设计。本文提供了一些有关充分利用后台处理的建议,并提出了很多必须解决的问题:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/BackgroundProcess.asp

7.16. .NET Compact Framework 多线程有什么技巧?

了解如何通过 .NET Compact Framework 在 Windows 窗体应用程序中使用多线程。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfmultithreadedapp.asp

7.17. 如何在智能设备应用程序中使用模糊处理?

了解如何使用 PreEmptive Dotfuscator 混淆器 (obfuscator) 来保护源代码。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfobfuscation.asp

7.18. 如何使用加密服务来加密和解密数据?

请参见本 FAQ 中的主题“10.5.编写基于 Windows Mobile 的 Pocket PC 签名捕获应用程序。”

7.19. 如何通过 .NET Compact Framework 检索唯一的 Windows CE 设备 ID?

了解如何使用 .NET Compact Framework 从 Windows CE 设备中检索设备 ID。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/retrievedeviceid.asp

您可以使用平台调用来获得基于 Windows Mobile 的 Pocket PC 的设备 ID(也就是所谓的序列号)。当应用程序运行时,这个快速入门在消息框中显示该 ID。
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/deviceid.aspx

7.20. 如何通过基于 Microsoft .NET Compact Framework 的应用程序发送短消息服务 (SMS) 消息?

这篇文章将向您介绍如何通过基于 .NET Compact Framework 的应用程序发送短消息服务 (SMS) 消息:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfsendsms.asp

7.21. 如何不使用 Control.Name 属性确定事件的发送方?

这篇文章将讨论假设 .NET Compact Framework 控件不支持 name 属性,如何确定事件的发送方:
http://msdn.microsoft.com/library/en-us/dncfhowto/html/HOWTOsenderevent.asp

7.22. 如何在线程中调用方法参数?

可以在应用程序中使用线程来更新用户界面,从而提高性能。Control 基类提供 Invoke、BeginInvoke 和 EndInvoke 方法以便在控件的创建线程中调用方法。不过 .NET Compact Framework 不支持异步 BeginInvoke 和 EndInvoke 调用。另外,对于同步调用,当前不支持将参数传递给 Invoke。这个快速入门通过一个自定义类 (ControlInvoker) 来提供后一种功能,它允许将参数传递给 Invoke 方法:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/controlinvoker.aspx

7.23. 如何创建通知和定时应用程序?

这个快速入门包含用于设置用户通知和定时应用程序的平台调用声明和方法。您可以安排一个通知对话框在指定时间出现或响应通知。您也可以设置一个应用程序在指定时间运行或响应通知。
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/notifications.aspx

7.24. 如何播放声音?

这个快速入门演示了使用平台调用来播放两个 WAV 文件,一个用作嵌入式资源,另一个用作内容。此窗体包含两个按钮,一个播放作为嵌入式资源的 Chimes.wav,另一个播放作为独立文件的 Chord.wav。
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/playsounds.aspx

7.25. 如何使用命令行参数?

要在代码中访问命令行参数,只需修改 main 的定义,使之接受适当的参数。以下代码演示了这种方法:

//C#

static void Main(string[] args)
{
  // Do your thing here
}

'VB

Shared Sub Main(ByVal args() As String)
  'Do your thing here
End Sub

为了在 Microsoft Visual Studio 2003 中进行调试,可以通过 IDE 设置命令行参数。从主菜单中选择 Project->Project Name Properties。在 Property Pages 对话框中,选择 Configuration Properties->Debugging。在 Command Line Arguments: 文本框中键入一个字符串。

7.26. 当使用被处置对象上的属性或方法时,为什么会产生异常?

完整版的 .NET Framework 并不保证访问被处置对象上的属性或方法始终能成功。然而,在 .NET Framework 中访问某些属性(如 Text)通常能成功。由于 .NET Framework 和 .NET Compact Framework 在实现上的区别,在 .NET Compact Framework 中访问被处置的对象上的方法或属性几乎都会失败。

7.27. 如何获得“Enter”、“TAB”和“Arrow”按键事件?

要获得非字符键(例如箭头键),应使用 KeyDown 和 KeyUp 事件。

以前,.NET Compact Framework 只支持一些选定控件(窗体、面板、文本框和自定义控件)上的按键事件。Service Pack 2 (SP2) 及更高版本支持所有控件的按键事件。

注:要按 Tab 键浏览控件,请将焦点设为第一个控件,否则按 Tab 键就无效。

由于一个已知的 bug,在键盘上按击 Tab 键当前还无法在模拟器上使用,不过,要是模拟器使用了 SIP 就可以。

模拟器的这个问题似乎是由 Visual Studio 带来的,而非 .NET Compact Framework 的问题。当 .NET Compact Framework 从 OS 接收到相应的本机 WM_KEY* 消息时,它就会激发 Key 事件。当您在模拟器下运行本机应用程序时,当您按下键盘上的 Tab 键时并无法获得 WM_KEYDOWN。如果您在模拟器或实际的设备中使用 SIP,就不会有这样的问题。

要下载最新的 Service Pack,请参见本 FAQ 中标题为“1.3.哪里可以下载最新的工具和软件?”的项。

7.28. 如何删除只读文件?

使用 System.IO.FileInfo 类来访问文件属性。

//C#

System.IO.FileInfo fi = new System.IO.FileInfo("filename");

// remove readonly attribute
if ((fi.Attributes & System.IO.FileAttributes.ReadOnly) != 0)
    fi.Attributes -= System.IO.FileAttributes.ReadOnly;

System.IO.File.Delete("filename");

'VB

Dim fi As New System.IO.FileInfo("filename")

'remove readonly attribute
If(fi.Attributes And System.IO.FileAttributes.ReadOnly) <> 0 Then
    fi.Attributes -= System.IO.FileAttributes.ReadOnly

System.IO.File.Delete("filename")

7.29. 如何在运行时确定一个成员是否存在?

可以在运行时使用 Reflection 来确定一个成员是否存在。以下代码演示了如何使用 Reflection 来访问 Bitmap 对象的“Width”属性和“GetPixel”方法。对于“Width”属性,该代码用一个“get”组件来枚举所有“public”属性,然后从中搜索一个名为“Width”的属性。

“GetPixel”示例演示了当一个已知函数的参数顺序未知时,如何使用 Reflection 来调用它。此示例的建立是假设作者知道有一个名为“GetPixel”的方法,它带有 x、y 像素位置,但不知道它们在参数列表中出现的顺序。此示例枚举了各个方法,从中搜索名为“GetPixel”的方法,然后枚举参数列表,确定是第一个还是第二个参数名为“X”。请记住,由于硬件像素格式的不同,在本示例中 GetPixel 的返回值可能与 SetPixel 设置的值不同。

Reflection 提供了许多在运行时确定功能的强大工具,要了解相关信息,请参考 System.Type 和 System.Reflection 命名空间的有关文档。

//C#
using System.Reflection;
using System.Drawing;

Bitmap bm = new Bitmap(200, 100);
int width = 0;

// Explicitly set one pixel for testing
int x = 199;
int y = 20;
Color pixColor = Color.Black;
bm.SetPixel(x,y,Color.Magenta);

// Get the "Width" property
PropertyInfo[] propInfo =
  bm.GetType().GetProperties(BindingFlags.GetProperty | 
  BindingFlags.Public | BindingFlags.Instance);

for (int i = 0; i < propInfo.Length; i++)
{
    if (propInfo[i].Name == "Width")
    {
        width = (int)propInfo[i].GetValue(bm, null);
        break;
    }
}

// Call the GetPixel method
MethodInfo[] methInfo = bm.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < methInfo.Length; i++)
{
    if (methInfo[i].Name == "GetPixel")
    {
        ParameterInfo[] paramInfo = methInfo[i].GetParameters();
        if (paramInfo.Length == 2)
    {

    object[] xy = new object[2];
    if (paramInfo[0].Name == "x")
    {
        xy[0] = x;
        xy[1] = y;
    }
    else
    {
        xy[1] = x;
        xy[0] = y;
    }

    pixColor = (Color)methInfo[i].Invoke(bm, xy);
    break;
}

'VB
Imports System.Reflection
Imports System.Drawing

Dim bm As New Bitmap(200, 100)
Dim width As Integer = 0

' Explicitly set one pixel for testing
Dim x As Integer = 199
Dim y As Integer = 20
Dim pixColor As Color = Color.Black
bm.SetPixel(x, y, Color.Magenta)

' Get the "Width" property
Dim propInfo As PropertyInfo() = _
  bm.GetType().GetProperties((BindingFlags.GetProperty Or _
  BindingFlags.Public Or BindingFlags.Instance))

Dim i As Integer
For i = 0 To propInfo.Length - 1
    If propInfo(i).Name = "Width" Then
        width = Fix(propInfo(i).GetValue(bm, Nothing))
        Exit For
    End If
Next i

' Call the SetPixel method
Dim methInfo As MethodInfo() = bm.GetType().GetMethods((BindingFlags.Public _
  Or BindingFlags.Instance))
For i = 0 To methInfo.Length - 1
    If methInfo(i).Name = "GetPixel" Then
        Dim paramInfo As ParameterInfo() = methInfo(i).GetParameters()
        If paramInfo.Length = 2 Then
            Dim xy(1) As Object

            If paramInfo(0).Name = "x" Then
                xy(0) = x
                xy(1) = y
            Else
                xy(1) = x
                xy(0) = y
            End If

            pixColor = CType(methInfo(i).Invoke(bm, xy), Color)
            Exit For
        End If
    End If
Next i

7.30. 如何以编程方式确定设备名称?

设备名称可以通过 System.Net 命名空间来访问,如以下代码所示。

//C#

String devName = System.Net.Dns.GetHostName();

'VB

Dim devName As String = System.Net.Dns.GetHostName()

7.31. 如何通过命令行构建 C# 智能设备项目?

逐行键入以下命令(为了显示清晰,每个命令分为两行):

set CFPath=%SystemDrive%/Program Files/Microsoft Visual Studio .NET 2003/
  CompactFrameworkSDK/v1.0.5000/Windows CE
csc Form1.cs /noconfig /nostdlib /lib:"%CFPath%"  /r:"%CFPath%/system.dll";"%CFPath%/
  system.drawing.dll";"%CFPath%/system.windows.forms.dll";"%CFPath%/mscorlib.dll"

7.32. 如何中止执行的线程?

在 .NET Compact Framework 中,Thread 类没有 Abort 方法,所以必须通过从执行过程返回来中止线程。通常,应用程序会通过设置全局变量来通知线程有一个关闭事件。然后主线程等待辅助线程完成处理,完成后再关闭应用程序。以下 HOWTO 文章演示了如何实现。
http://msdn.microsoft.com/library/en-us/dncfhowto/html/stopmt.asp

7.33. 为什么我不能在基于 Windows Mobile 的 Pocket PC 模拟器上播放影片?

Windows Media Player 只能在基于 Windows Mobile 的 Pocket PC 2003 模拟器上使用。Windows Media Player 安装包只能在通过 ActiveSync 连接的硬件设备上安装,不能安装在模拟器上。

7.34. 如何阻止窗体标题显示在活动应用程序列表中?

基于 Windows Mobile 的 Pocket PC 中的 Active Programs 列表枚举了所有打开的窗体。要阻止一个窗体显示在列表中,只需将该窗体的标题设置为空字符串。以下示例显示了如何只在列表中保留应用程序名称,而窗体则通过另一个窗体显示:

//C#

string AppName = "MyApp";

Form1 form1 = new Form1();
this.Text = "";
form1.Text = AppName;
form1.ShowDialog();
this.Text = AppName;

'VB

Dim AppName As String = "MyApp"

Dim form1 As New Form1()
Me.Text = ""
form1.Text = AppName
form1.ShowDialog()
Me.Text = AppName

7.35. 如何通过控件的实例名称访问一个控件?

您可以使用 Reflection,通过控件的名称来查找它的实例。下面是一些示例代码:

//C#

private void Form1_Load(object sender, System.EventArgs e)
{
    ComboBox c = (ComboBox)this.ControlFromName("combobox1");
    c.Items.Add("1");
    this.GetControls();
}

private Control ControlFromName(string name)
{
    object o = this.GetType().GetField(name,
      System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance |
      System.Reflection.BindingFlags.IgnoreCase).GetValue(this);

    return((Control)o);
}

private void GetControls()
{
    System.Reflection.FieldInfo[] fis = this.GetType().GetFields
    (
        System.Reflection.BindingFlags.NonPublic | 
        System.Reflection.BindingFlags.Instance |
        System.Reflection.BindingFlags.IgnoreCase
    );

    foreach(System.Reflection.FieldInfo fi in fis)
    {
        if (fi.GetValue(this) is Control)
            MessageBox.Show(fi.Name);
    }
}

'VB

Private Function ControlFromName(ByVal name As String) As Control
    Dim o As ObjectDim o As Object
    o = Me.GetType().GetField(name, Reflection.BindingFlags.NonPublic Or _
      Reflection.BindingFlags.Instance Or _
      Reflection.BindingFlags.IgnoreCase).GetValue(Me)
   
    Return (CType(o, Control))
End Function

Private Sub Form1_Load(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles MyBase.Load
    Dim c As ComboBox
    c = CType(ControlFromName("_combobox1"), ComboBox)
    c.Items.Add("1")
    Me.GetControls()e.GetControls()
End Sub

Private Sub GetControls()
    Dim fis As System.Reflection.FieldInfo()

    fis = Me.GetType().GetFields(Reflection.BindingFlags.NonPublic Or _
      Reflection.BindingFlags.Instance Or _
      Reflection.BindingFlags.IgnoreCase)

    For Each fi As Reflection.FieldInfo In fis
        If TypeOf (fi.GetValue(Me)) Is Control Then
            MessageBox.Show(fi.Name)
        End Ifnd If
    Next
End Sub

7.36. 如何检测硬件定向填充按钮?

硬件定向填充按钮可以通过标准按键事件来检测,方法是比较 KeyEventArgs 参数中的 KeyCode。

//C#

protected override void OnKeyDown(KeyEventArgs e)
{
    switch (e.KeyCode)
    {
        case Keys.Up:
            MessageBox.Show("Up Key Pressed");
            break;
        case Keys.Down:
            MessageBox.Show("Down Key Pressed");
            break;
        case Keys.Left:
            MessageBox.Show("Left Key Pressed");
            break;
        case Keys.Right:
            MessageBox.Show("Right Key Pressed");
            break;
    }
    base.OnKeyDown (e);
}

'VB

Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
    Select Case e.KeyCode
        Case Keys.Up
            MessageBox.Show("Up Key Pressed")
        Case Keys.Down
            MessageBox.Show("Down Key Pressed")
        Case Keys.Left
            MessageBox.Show("Left Key Pressed")
        Case Keys.Right
            MessageBox.Show("Right Key Pressed")
    End Select
     
    MyBase.OnKeyDown(e)

End Sub 'OnKeyDown

7.37. 当只支持 Click 事件时,如何获得双击事件?

请参见 .NET Compact Framework QuickStarts、Implementing Events 主题:
http://samples.gotdotnet.com/quickstart/compactframework/doc/btndclick.aspx

7.38. 如何获得 onenter/onleave 风格的通知?

请参考 .NET Compact Framework QuickStarts¡¢OnEnter/OnLeave 功能:
http://samples.gotdotnet.com/quickstart/compactframework/doc/enterleave.aspx

7.39. 如何在不挂起应用程序的模式对话框中使用线程处理?

创建一个行为像模式对话框的非模式对话框。

//C#

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
using System.ComponentModel;

public interface IModelessDialogCallback
{
    void DialogResultCallback(DialogResult result);
}

public class Test : System.Windows.Forms.Form, IModelessDialogCallback
{
    ModelessDialog dlg;
    Button bShow;
    int counter = 0;

    protected override void OnLoad(EventArgs e)
    {
        this.Text = "Modal(less) Dialog Example";

        this.bShow = new Button();
        this.bShow.Parent = this;
        this.bShow.Bounds = new Rectangle (10, 10, 150, 30);
        this.bShow.Text = "Show Dialog";
        this.bShow.Click += new EventHandler(this._Click);

        this.dlg = new ModelessDialog(this);
    }


    private void _Click(object o, EventArgs e)
    {
        this.Enabled = false;
        this.bShow.Text = "waiting for dlg";
        dlg.Show();
    }

    public void DialogResultCallback(DialogResult result)
    {
        MessageBox.Show("dialog returned: " + (result == DialogResult.OK ? "OK" : "Cancel"));
        this.Enabled = true;
        this.bShow.Text = "Show Dialog:" + ++counter;
    }

    public static void Main()
    {
        Application.Run(new Test());
    }
}

public class ModelessDialog : Form
{
    IModelessDialogCallback parent;
    Button bOK, bCancel;

    public ModelessDialog(IModelessDialogCallback parent)
    {
        this.parent = parent;
        this.Text = "Modeless Dialog";

        this.bOK = new Button();
        this.bOK.Parent = this;
        this.bOK.Bounds = new Rectangle (10, 10, 150, 30);
        this.bOK.Text = "OK";
        this.bOK.Click += new EventHandler(this._Click);

        this.bCancel = new Button();
        this.bCancel.Parent = this;
        this.bCancel.Bounds = new Rectangle (10, 50, 150, 30);
        this.bCancel.Text = "Cancel";
        this.bCancel.Click += new EventHandler(this._Click);
    }

    private void _Click(object o, EventArgs e)
    {
        this.Hide();
        this.parent.DialogResultCallback(o == this.bOK ? DialogResult.OK : DialogResult.Cancel);
    }

    protected override void OnClosing(CancelEventArgs e)
    {
        e.Cancel = true;
        this.Hide();
        this.parent.DialogResultCallback(DialogResult.Cancel);
    }
}

'VB

Imports System
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Collections
Imports System.ComponentModel

Public Interface IModelessDialogCallback
   Sub DialogResultCallback(result As DialogResult)
End Interface IModelessDialogCallback'


Public Class Test
   Inherits System.Windows.Forms.Form
   Implements IModelessDialogCallback
   Private dlg As ModelessDialog
   Private bShow As Button
   Private counter As Integer = 0
   
   Protected Overrides Sub OnLoad(e As EventArgs)
      Me.Text = "Modal(less) Dialog Example"
      
      Me.bShow = New Button()
      Me.bShow.Parent = Me
      Me.bShow.Bounds = New Rectangle(10, 10, 150, 30)
      Me.bShow.Text = "Show Dialog"
      AddHandler Me.bShow.Click, AddressOf Me._Click
      
      Me.dlg = New ModelessDialog(Me)
   End Sub 'OnLoad
   
   Private Sub _Click(o As Object, e As EventArgs)
      Me.Enabled = False
      Me.bShow.Text = "waiting for dlg"
      dlg.Show()
   End Sub '_Click
   
   Public Sub DialogResultCallback(ByVal result As DialogResult) Implements _
     IModelessDialogCallback.DialogResultCallback
      MessageBox.Show(("dialog returned: " + IIf(result = DialogResult.OK, "OK", "Cancel")))
      Me.Enabled = True
      counter += 1
      Me.bShow.Text = String.Format("Show Dialog: {0}", counter)
   End Sub 'DialogResultCallback

   Public Shared Sub Main()
      Application.Run(New Test)
   End Sub 'Main
End Class 'Test

Public Class ModelessDialog
   Inherits Form
   Private myParent As IModelessDialogCallback
   Private bOK, bCancel As Button
   
   Public Sub New(parent As IModelessDialogCallback)
      Me.myParent = parent
      Me.Text = "Modeless Dialog"
      
      Me.bOK = New Button()
      Me.bOK.Parent = Me
      Me.bOK.Bounds = New Rectangle(10, 10, 150, 30)
      Me.bOK.Text = "OK"
      AddHandler Me.bOK.Click, AddressOf Me._Click
      
      Me.bCancel = New Button()
      Me.bCancel.Parent = Me
      Me.bCancel.Bounds = New Rectangle(10, 50, 150, 30)
      Me.bCancel.Text = "Cancel"
      AddHandler Me.bCancel.Click, AddressOf Me._Click
   End Sub 'New
   
   Private Sub _Click(o As Object, e As EventArgs)
      Me.Hide()
      Me.myParent.DialogResultCallback(IIf(o Is Me.bOK, DialogResult.OK, DialogResult.Cancel))
   End Sub '_Click
   
   Protected Overrides Sub OnClosing(e As CancelEventArgs)
      e.Cancel = True
      Me.Hide()
      Me.myParent.DialogResultCallback(DialogResult.Cancel)
   End Sub 'OnClosing
End Class 'ModelessDialog

7.40. 如何有效地对浮点数进行取整?

对数字取整有两个主要的方法:

Convert.ToInt32

Cast 或 Fix(C# 或 VB)

Convert.ToInt32 会自动处理取整,其中,余数为 .5 或更大会使数值向上取整。强制转换或使用 Fix 需要将数值加 .5 以确保取整正确,因为这些方法只是删除余数。

在模拟器和一个 Compaq iPAQ H3600 序列的设备上对每种方法进行 100 万次操作得到了以下的分析结果,其中 num 是一个浮点数,值设置为 3.6F:

Emulator

iPAQ

Operation

Debug (ms)

Release (ms)

Debug (ms)

Release (ms)

C#: Convert.ToInt32(num)

1321

1109

6264

6283

C#: (int)(num + .5F)

170

49

1479

59

VB: Convert.ToInt32(num)

1218

1232

6531

6517

VB: Fix(num + .5F)

3873

3677

18144

17955

因此,通过检验设备的发布构建结果,可以得出结论:对于当前推出的设备,在 C# 中使用强制转换最有效,而在 VB 中使用 Convert.ToInt32 最有效。在 C# 中,已证明强制转换快 106 倍以上,而在 VB 中,Convert.ToInt32 快近 3 倍。

//C#

float temp = 3.6f;
int rounded1 = (int)(temp + .5f);
int rounded2 = Convert.ToInt32(temp);

'VB

Dim temp As Single = 3.6F
Dim rounded1 As Integer = Fix(temp + .5F)
Dim rounded2 As Integer = Convert.ToInt32(temp)

7.41. 当显示模式对话框时,为什么 Control.Invoke 用了那么长时间?

当通过 ShowDialog 显示一个模式对话框时,使用 Control.Invoke 会有一个已知的 bug。请下载最新的 .NET Compact Framework Service Pack 来修复这个问题:

最新的平台更新和 .NET Compact Framework service pack 可以通过以下地址获得:
http://msdn.microsoft.com/mobility/downloads/default.aspx

7.42. 是否有办法检索程序集的区域性信息?

区域性信息可以通过 Reflection 来检索:

//C#

using System.Reflection;
using System.Globalization;

Assembly asm = Assembly.GetExecutingAssembly();
CultureInfo ci = asm.GetName().CultureInfo;

'VB

Imports System.Reflection
Imports System.Globalization

Dim asm As Assembly = Assembly.GetExecutingAssembly()
Dim ci As CultureInfo = asm.GetName().CultureInfo

7.43. 当调用一些 System.Math 方法时,为什么会产生 NotSupportedException?

并非所有平台都支持全部 Math 方法,不过它们包含在该 API 中以便能够兼容。这些方法的实现可以用以下使用等效函数的实现来替代:

//C#

double angle = 1.2;

// sinh(double angle): (e^x - e^-x) / 2
double res = (Math.Exp(angle) - Math.Exp(-angle)) / 2.0;

// cosh(double angle): (e^x + e^-x) / 2
res = (Math.Exp(angle) + Math.Exp(-angle)) / 2.0;

// tanh(double angle): sinh / cosh
res = (Math.Exp(angle) - Math.Exp(-angle)) / 
   (Math.Exp(angle) + Math.Exp(-angle));

'VB

Dim angle As Double = 1.2

' sinh(double angle): (e^x - e^-x) / 2
Dim res As Double = (Math.Exp(angle) - Math.Exp(-angle)) / 2.0

' cosh(double angle): (e^x + e^-x) / 2
res = (Math.Exp(angle) + Math.Exp(-angle)) / 2.0

' tanh(double angle): sinh / cosh
res = (Math.Exp(angle) - Math.Exp(-angle)) / _
   (Math.Exp(angle) + Math.Exp(-angle))

8. 通信和 Web 服务

8.1. 如何通过 TCP 套接字向桌面服务器发送数据?

请参见本 FAQ 中的主题“10.5.编写基于 Windows Mobile 的 Pocket PC 签名捕获应用程序。”。

8.2. 为什么我不能访问本地主机上的 Web 服务?

不要在设备中使用本地主机来创建 Web 服务,因为本地主机指的是运行应用程序的设备,就像在设备本身中创建一样。相反,应该使用机器名称或 IP 地址。

8.3. 如何通过 Microsoft .NET Compact Framework 使用 Web 服务?

这篇文章说明了如何通过使用 .NET Compact Framework 的智能设备应用程序,以同步和异步方式调用 Web 服务:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfwebservices.asp

8.4. 如何通过代理提交 HTTP GET 请求?

要通过或不通过指定代理提交 HTTP GET 请求,应该: 使用 StreamReader,将 HTML 响应读到字符数组缓冲中。然后在 ListBox 中显示该响应,如以下屏幕快照所示:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/httpgetwithproxy.aspx

8.5. 如何使用 Web 服务?

这个快速入门描述了如何使用能为简单运算提供结果的 Web 服务。此 Web 服务在完整版的 .NET Framework 快速入门中标题为“Write a Simple Web service”的文章中有所描述。这个快速入门为该 Web 服务提供了一个客户端应用程序:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/xmlwebservice.aspx

8.6. 如何通过 Microsoft MapPoint 3.0 获得映射?

这个快速入门描述了如何对 MapPoint Web 服务进行编程来获得基于 Windows Mobile 的 Pocket PC 上指定位置的映射,如以下屏幕快照所示:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/mappointfindmap.aspx

8.7. 如何通过 MapPoint 3.0 获得映射和方向?

这个快速入门描述了如何使用 MapPoint Web 服务来获得指定位置之间带有方向的映射,如以下屏幕快照所示:
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/mappointdirections.aspx

8.8. .NET Compact Framework 是否支持 NTLM 身份验证?

推荐的解决方案是使用摘要式身份验证来代替基本或 NTLM 身份验证。 .NET Compact Framework 支持摘要式身份验证。

8.9. 当使用阻塞套接字时,当 blocking 设置为 false 时为什么 Send 会引发 InvalidOperationException?

当在阻塞套接字中将 Blocking 设置为 false 时会出现一个已知的问题。当进行套接字编程(托管和本机)时有三个范例,强烈建议开发人员使用异步 I/O 函数来作为这个问题的解决方案。在托管代码中 Select 效率很低,而普通的 case exception 代价也很高。下面详细介绍了这三个范例,其中最后一个是最佳的方法。

在阻塞套接字上使用阻塞调用(即,send()、recv() 等)。这是最简单的方法,也可能是使用最普遍的。这些函数以同步方式执行操作,阻塞当前执行的线程。这是客户端和多线程服务器最可接受的方式(Microsoft 不建议每个客户端使用一个线程,因为 Windows 中线程是很宝贵的)。请注意,虽然在一段未定时间内操作可能会阻塞(即,直到操作能够完成),但大多数情况下它们会立即完成。

非阻塞套接字与 select 或 poll 一起使用。使用相同的“阻塞”函数,但将套接字置为一种特殊模式,这种模式可以阻止套接字阻塞。当出现函数要阻塞的情况时,它就会返回一个错误代码(在托管代码中,将会引发一个异常)。然后您可以使用 poll 或 select 来等待,直到某个时刻一个操作完成(select 允许您管理多个套接字,所以您只能在一个线程中处理多个客户端),再执行操作。当创建这些调用时,操作系统中已没有线程,所以这是唯一可行的方法。到目前为止,使用这种机制导致在套接字编程中可能会使性能最严重地恶化。在托管代码中这个问题更加突出,因为它会引发异常,这样会使性能下降得更厉害。

在套接字中使用异步 I/O 函数。这是所有解决方案中最好的一种。它允许您以异步方式执行操作,并且会通过回调通知您。通常其性能会比使用带 select 的非阻塞 I/O 更好,建议采用这种方法来实现(至少在本机代码中)。

8.10. 如何创建 http-server?

获得关于实现移动 Web 服务器体系结构的技术内涵。了解该体系结构中的用例、Web 服务器框架及未来的开发:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/NETCFMA.asp

8.11. 当通过 ReuseAddress 集调用 SetSocketOption 时为什么会产生异常?

这是 .NET Compact Framework 版本 1 中的一个已知问题。要解决这个问题,您可以 P/Invoke setsockopt,将 SO_REUSEADDR 设置为 true。

8.12. 当通过 WebRequest 命名空间,使用 SSL 与服务器通信时,为什么会产生套接字错误?

这是 .NET Compact Framework 中的一个已知问题。这个问题可以这样解决:将 HttpWebRequest.AllowStreamWriteBuffering 设置为 True,并注释掉对 ContentLength 的设置。

8.13. 当执行 HTTPWebRequest 时,为什么会产生无效块长度错误呢?

这是一个已知问题,在 .NET Compact Framework Service Pack 第 2 版及更高版本中已修复。

要下载最新的 Service Pack,请参见本 FAQ 中标题为“1.3.哪里可以下载最新的工具和软件?”的项。

9. SQL CE 和数据

9.1. 采用 .NET Compact Framework、SQL Server CE 和复制开发应用程序时有哪些开发注意事项?

9.2. 如何将合并复制与 .NET Compact Framework 一起使用?

这篇文章将讨论如何在基于 .NET Compact Framework 的应用程序中以编程方式调用合并复制:
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfmergereplication.asp

SQL Server CE 是一个管理设备数据的强大工具,但该数据必须保留在设备中,除非有一种方式可以将它复制到桌面 SQL Server 中。可以使用合并复制来管理位于设备本地的数据,然后当设备连接到 SQL Server 的实例时再将它合并。.NET Compact Framework 提供了 SqlCeReplication 对象,所以合并复制可以用编程方式执行。
http://samples.gotdotnet.com/quickstart/CompactFramework/doc/sqlcemergereplication.aspx

9.3. 如何在 XML 中使用 ADO.NET 数据绑定?

按下列步骤进行:

1.

通过提供的 XML 文件创建一个 XML 架构文件 (.xsd)。

2.

加载 XML 数据及其架构以执行绑定。

3.

将该 XML 重新保存成一个文件。

9.4. 如何创建 SQL Server CE 数据库?

您可以使用 .NET Compact Framework 来创建和管理 Windows CE .NET 设备中的 SQL Server CE 数据库。这个快速入门介绍了如何实现:

创建一个 SQL CE 数据库。

连接到 SQL CE 数据库。

在 SQL CE 数据库中创建一张表。

在 SQL CE 数据库中插入一行。

9.5. 如何提交 SQL Server CE 参数化查询?

您可以使用 .NET Compact Framework,通过用户输入在 SQL Server CE 数据库中插入值。此示例显示了如何实现:

设计一个 UI,以便用户设置查询值。

连接到一个 SQL CE 数据库。

在 SQL CE 数据库中插入含有用户值的一行

9.6. 为什么使用 RDA 和 push 方法会因 27750 错误而失败?

表现当进行以下调用时:

rda = new SqlCeRemoteDataAccess();

会产生一个本机错误代码为 27750 的 SQLCeException 类型的错误。这个错误对应于“Cannot load sscemw20.dll or ssceca20.dll is missing or not registered.”(不能加载 sscemw20.dll,或 ssceca20.dll 丢失或没有注册。)

解决方案

在应用程序启动时加载该库,即使您不需要用到它。要加载该库,您可以加载 SqlCeEngine 对象。

SqlCeEngine eng = new SqlCeEngine();

10. 其他信息

10.1. Pocket TaskVision 应用程序。

本文档概述了 Pocket TaskVision 示例应用程序所特有的开发任务。与桌面系统相比,基于 Windows Mobile 的 Pocket PC 设备的存储容量、内存更少,处理器也较为低端 — 本文档将提到存储和性能的一些注意事项。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/wnf_pkttaskvision.asp

10.2. 创建基于 .NET Compact Framework 的进程管理器。

在这篇文章中,可以了解到如何将结构作为字节数组加以封送。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/ProcessManager.asp

10.3. 使用 Microsoft .NET Compact Framework 开发移动游戏。

了解如何创建基于 .NET Compact Framework 的游戏。了解编写面向小型设备游戏的主要需求,以及了解 .NET Compact Framework 如何轻松地处理它们。包括可以用来解除游戏限制的高级性能调优技术。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfgaming.asp

10.4. 创建基于 .NET Compact Framework 的 DateTimePicker 控件。

了解如何在基于 Windows Mobile 的 Pocket PC 项目中使用 DateTimePicker 托管类。.NET Compact Framework 为大多数控件提供托管类,但不支持 DateTimePicker 类。P/Invoke 本机控件的 Wrapper 类可以在网上找到,但此示例提供了一个纯托管的 DateTimePicker 类,您可以在基于 Windows Mobile 的 Pocket PC 项目中使用它。该类和示例应用程序用 C# 和 Visual Basic .NET 两种语言提供。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/netcfdatetimepicker.asp

10.5. 编写基于 Windows Mobile 的 Pocket PC 签名捕获应用程序。

这篇文章讨论了基于 Windows Mobile 的 Pocket PC 签名示例应用程序。此示例包括一个在基于 Windows Mobile 的 Pocket PC 上运行的客户端,它将签名数据通过 TCP 套接字发送给在桌面上运行的服务器。数据是通过加密服务加密和解密的。
http://msdn.microsoft.com/library/en-us/dnnetcomp/html/PPCSignatureApp.asp

10.6. 是否有非 Microsoft 网站提供有关开发基于 .NET Compact Framework 的应用程序的信息?

请参见 MSDN 移动与嵌入式技术开发人员中心的“Related Sites”部分:
http://msdn.microsoft.com/mobility/Community/related/default.aspx

11. 连接

11.1. 为什么我无法通过 ActiveSync 部署到 Windows CE 设备?

表现:当通过 ActiveSync 连接 Windows CE 设备时,会出现部署和调试(F5 和 Ctrl-F5)失败,通常错误消息为“There were deployment errors.Continue?”

原因:由于 ActiveSync 3.5 和 3.6 不提供有关设备指令集的准确信息,所以 ConMan(用于设备连接的 Visual Studio .NET 2003 组件)无法使用 ActiveSync 返回的信息来将 Windows CE 设备映射到正确的 .NET Compact Framework 目标(MIPSII、ARMV4 等)。

解决办法:安装并运行单独提供的 Windows CE 配置外接程序(下面提供链接)。对于 ARMV4 设备,一种替代办法是在部署开始时选择基于 Windows Mobile 的 Pocket PC 设备。

11.2. 为什么我无法通过 ActiveSync 调试 CEPC 或其他任何基于 x86 的 Windows CE 设备?

表现:运行 Windows CE 设备配置外接程序之后,用户就可以部署(不带调试 (Ctrl-F5))到连接 CEPC 的 ActiveSync(或任何基于 x86 的 Windows CE 设备),但不能调试 (F5)。非基于 x86 的 Windows CE 设备不会有这个问题。

原因:调试 Windows CE 模拟器(也基于 x86)使用的 TCP 端口与 ActiveSync 用于调试基于 x86 的设备的端口相冲突。要支持模拟器调试,默认情况下必须禁用调试 CEPC 和其他 x86 设备。

解决办法:以下解决办法将允许调试 x86 设备,但禁用在 Windows CE 模拟器下调试。这种解决办法需要 WinCEx86Device.reg 和 ProxyPorts.reg 文件,它们可以在 SDK utilities 目录中找到:“/CompactFrameworkSDK/WinCE Utilities/WinCE Proxy Ports Reg”。

将 WinCEx86Device.reg 导入注册表。现在您可以调试 x86 设备,但在 Windows CE 模拟器下调试会失败。

要还原模拟器调试,可以删除注册表项“HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows CE Services/ProxyPorts”,然后导入 ProxyPorts.reg。

11.3. 当设备中缺少 Toolhelp.dll 时为什么会调试失败?

表现:按下 F5 后,包括应用程序可执行文件和 .NET Compact Framework cab 文件在内的所有文件都会复制到设备中,IDE 报告应用程序启动成功,但在设备上什么都没启动。如果您检查设备的 /windows 文件夹,toolhelp.dll 不存在。

原因: 一些设备映像可能不包含 SDE 调试所需要的 toolhelp.dll。

解决办法:将 toolhelp.dll 从 Windows CE SDK 复制到设备的 /windows 文件夹中。对于每个设备,可以在相应的目标目录中找到此文件。

例如,对于 ARM 设备:“/CompactFrameworkSDK/WinCE Utilities/ToolHelp/WinCE4/armv4”。

11.4. 当网络协议数超过 50 时,为什么无法部署到设备?

表现:当按下 Ctrl-F5 或 F5 时会出现“deployment error”消息框。运行 EnumProtocols.exe 列出 50 多个协议。(EnumProtocols.exe 可以在 WinCE utilites 目录:“/CompactFrameworkSDK/WinCE Utilities/EnumProtocols”中找到)。如果您没有此文件,可以通过以下链接下载:
http://www.microsoft.com/downloads/details.aspx?FamilyID=7ec99ca6-2095-4086-b0cc-7c6c39b28762&DisplayLang=en#filelist

原因:这是一个已知的问题,即当前机器中安装的网络协议数超过 50 时,.NET Framework 的 System.Net.Dns.GetHostName 方法会引发异常。这个方法是由 ConMan 调用的,而异常会导致 ConMan 传输初始化失败。

解决办法:卸载实际上不需要的网络协议。一种方式是进入设备管理器(右键单击“My Computer”,选择 Properties->Hardware,按“Device Manager”按钮)并删除“Network adapters”下的未使用项。另一种方式是卸载已安装了协议的应用程序。这些应用程序可以从 EnumProtocles.exe 的输出中找到。

11.5. 卸载 ActiveSync 之后为什么无法通过 ActiveSync 部署到设备?

表现:当卸载 ActiveSync 之后,按下 Ctrl-F5 或 F5 会出现“deployment error”消息框。卸载 ActiveSync 通常发生在用户升级 ActiveSync 时,比如从 3.5 版升级到 3.6 版。

原因:ConMan 利用“HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows CE Services/ProxyPorts”下的一些注册表值进行部署和通过 ActiveSync 调试。当 ActiveSync 卸载时,这些注册表值会删除。

解决办法:重新安装或修复 Visual Studio .NET 2003。一个比较简单的修复办法是导入 ProxyPorts.reg,可以在 WinCE utilities 文件夹中找到:“/CompactFrameworkSDK/WinCE Utilities/WinCE Proxy Ports Reg”。如果您没有此文件,可以通过以下链接下载:
http://www.microsoft.com/downloads/details.aspx?FamilyID=7ec99ca6-2095-4086-b0cc-7c6c39b28762&DisplayLang=en#filelist

可能需要 un-cradle/re-cradle 或软重启设备才能使修复生效。建议用户永远都不要卸载 ActiveSync。要升级 ActiveSync,应该直接安装新版本,它会正确重写旧版本。

11.6. 本地化机器名称或用户名为什么可能导致设备部署失败?

表现:当机器名称或登录用户名包含当前代码页中没有的字符或 ASCII 值低于 0x20 的字符时,部署到设备会失败,而部署到模拟器则工作正常。

原因: ConMan 内部的安全身份验证以 ASCII 形式使用机器名和用户名,它无法处理上述范围中的字符。

解决办法:不要在机器名和用户名中使用上述字符。如果用户对什么字符属于这种范围不清楚,只使用英文字母和数字总是安全的。

11.7. 如果登录用户不属于 Administrators 组,为什么部署到设备会失败?

表现:如果当前登录用户不在 Administrators 组中,部署到设备总会失败,即使用户在 Visual Studio Developers 组和 Debugger Users 组中。另一方面,部署到模拟器并进行调试则工作正常。

原因: 在设备部署期间,ConMan 代码在内部打开文件的模式需要有管理员权限。

解决办法: 将当前用户添加到 Administrators 组,或者将部署目标只限制为模拟器。

11.8. 为什么部署到基于 Windows Mobile 的 Pocket PC 设备或模拟器会因共享冲突错误而失败?

表现:当将一个应用程序部署到基于 Windows Mobile 的 Pocket PC 2002/2003 设备或模拟器时,部署会失败,输出消息类似于“Could not write to output file 'SmartDeviceApplication1.exe' - Sharing violation”。当同一个应用程序以前已经部署到同一台设备或模拟器时,通常会出现这种问题。

原因:默认情况下,用于基于 Windows Mobile 的 Pocket PC 2002 或 2003(非 2000)的应用程序中的 X 按钮并没有关闭进程。它只是“最小化”应用程序窗口。当用户试图再次部署该应用程序时,“最小化”的实例会导致共享冲突并使部署失败。另一种可能的原因是用户中途强行终止调试会话。

解决办法:确保当再次部署时,设备或模拟器中的应用程序已经真正关闭。在基于 Windows Mobile 的 Pocket PC 中,要查看正在运行的进程,请转到 Start->Settings->Memory->Running Programs。如果想要有一个按钮能真正关闭应用程序,请显式创建这样的按钮,或者将 Windows 窗体的 Minimize Box 属性设置为 False,这样X 按钮就成了能关闭应用程序的 OK 按钮。如果原因是手动终止调试会话,则可能需要软重启设备或模拟器。

11.9. 当模拟器第一次在 Windows Server 2003 机器上使用时,为什么会产生安全警告消息?

表现:当在 Windows Server 2003 机器上安装 Visual Studio .NET 2003 之后,第一次使用模拟器时会弹出一个标题为“Security Alert — Driver Installation”的消息框。消息文本的开头为“The driver software you are installing for:Virtual PC Application Services has been signed with Authenticode(TM) technology.The publisher's identity has been identified, and the driver has not been modified since it was published”。文本结束部分写到“However, this driver cannot be tested by Microsoft Windows Hardware Quality Labs to verify its compatibility with this version of Windows. Do you trust this publisher and want to install the driver?”

原因:Visual Studio .NET 2003 安装了一个 Virtual PC Application Services Emulator 驱动程序,它并没有传递 Windows Server 2003 的 Driver Validation 系统的验证结果。

解决办法:当出现这种警告消息时,单击 Yes 按钮。否则模拟器将不工作。

11.10. 当模拟器在启动时,为什么会产生关于丢失文件的错误消息?

表现:当模拟器在启动时,会出现一个标题为“Emulator for Windows CE”的消息框,消息文本为“One or more files from the Emulator for Windows CE installation is missing.Please reinstall Emulator for Windows CE and try again.” 出现这个消息框之后,连接或部署到模拟器会失败。

原因: 一个典型原因是模拟器驱动程序没有安装或已损坏。

解决办法:转到“Device Manager”(右键单击“My Computer”,选择“Properties->Hardware”并按“Device Manager”按钮),检查 System Devices 组中是否安装“Virtual PC Application Services”。如果该驱动程序没有安装,则通过运行“/CompactFrameworkSDK/ConnectionManager/Bin/DriverInstall/Smart Devices Emulator.msi”来安装。

注:运行 msi 文件之后确认出现“Virtual PC Application Services”。如果没有出现,则重启 PC 并尝试再次安装。

11.11. 为什么有时第一次模拟器部署尝试会失败?

表现:模拟器启动缓慢,最终部署失败并出现一条连接错误消息。启动主要包括启动模拟器映像和下载 .NET Compact Framework 文件。在低端机器上,当第一次使用模拟器,或者前一次使用“turn off”而非“saving state”来关闭模拟器时更容易发生这种失败。

原因: 由于模拟器的某些性能问题,它的启动需要很长时间,在 ConMan 超时时间内无法完成。

解决办法:如果可能,当关闭模拟器时,始终选择“saving state”而非“turn off”,这样模拟器下一次启动时启动速度就会更快。或者在部署之前通过单击“Connect to Device”按钮来预启动模拟器,该按钮位于菜单栏中模拟器名称的旁边。请注意,当预启动之后,第一次部署仍然会失败。(请参阅关于这个问题的详细信息。)另外,鼠标悬停在模拟器映像上可能加速模拟器启动。

11.12. 当通过 Connect to Device 按钮启动模拟器之后,为什么第一次部署尝试失败?

表现:当通过“Connect to Device”按钮启动模拟器时,第一次部署尝试总是失败。

解决办法:通过此按钮启动模拟器之后第一次部署失败这个问题目前还无法解决,但以后的部署尝试就会成功。

11.13. 如果机器没有网络连接,为什么模拟器调试会失败?

表现: 当机器没有活动的网络连接时,用模拟器调试 (F5) 无法工作,但不带调试的部署 (Ctrl-F5) 却能正常工作。

原因: 模拟器调试依赖于 TCP 传输。

解决办法:在机器上安装 Microsoft Loopback Adapter。

在 Microsoft Windows XP 中安装 Microsoft Loopback Adapter

1.

打开“Control Panel”,选择“Add Hardware”,然后选择“Next”。

2.

选择“Yes”,我已经连接了硬件,然后选择“Next”。

3.

从 Installed hardware 列表中选择“Add a new hardware device”,然后选择“Next”。

4.

选择“Install the hardware that I manually select from a list (Advanced) ”,然后选择“Next”。

5.

从 Common hardware types 列表中选择“Network adapters”,然后选择“Next”。

6.

从 Manufacturer 列表中选择“Microsoft”。

7.

从 Network Adapter 列表中选择“Microsoft Loopback Adapter”,然后选择“Next”。

8.

选择“Next”,然后选择“Finish”。

在 Microsoft Windows 2000 中安装 Microsoft Loopback Adapter

1.

打开“Control Panel”,选择“Add/Remove Hardware”,然后选择“Next”。

2.

选择“Add/Troubleshoot a device”,然后选择“Next”。

3.

从 Devices 列表中选择“Add a new device”,然后选择“Next”。

4.

选择“No”,我想从列表中选择该硬件,然后选择“Next。

5.

从 Hardware types 列表中选择“Network adapters”,然后选择“Next”。

6.

从 Manufacturers 列表中选择“Microsoft”。

7.

从 Network Adapter 列表中选择“Microsoft Loopback Adapter”,然后选择“Next”。

8.

选择“Next”,然后选择“Finish”。

11.14. 如果模拟器使用 COM 端口,为什么无法引导(黑屏)?

表现:当模拟器映像启动时,它始终保持黑屏。当模拟器配置为至少使用一个 COM 端口时出现这个问题。

原因:默认情况下,模拟器没有分配 COM 端口,但用户可能会更改配置,为该模拟器分配一个 COM 端口。当为模拟器分配的 COM 端口被 ActiveSync 或其他任何正在运行的应用程序使用时,模拟器映像就会加载失败。

解决办法:删除所有为模拟器分配的 COM 端口,或者将它们更改为没有被 ActiveSync 或其他任何正在运行的应用程序使用的端口。要在 Visual Studio .NET 2003 中删除或更改 COM 端口配置,请转到 Tools/Options/Devices,在 Devices 窗口中选择一个模拟器,然后单击 Configure 按钮。

11.15. 为什么 Visual Studio .NET 2003 Emulator 无法和 eMbedded Visual Tools Emulator 一起同时工作?

表现:当 eMbedded Visual Tools (Platform Builder) Emulator 运行时,Visual Studio .NET 2003 Emulator 部署失败,反之亦然。有时甚至在 Visual Studio .NET 2003 Emulator 关闭之后 eMbedded Visual Tools Emulator 仍然无法工作。

原因:Visual Studio .NET 2003 Emulator 和 eMbedded Visual Tools Emulator 在内部使用 Emulator.exe 的两个不同实例,它们互相不兼容。因为它们共享相同的可执行文件名称,而该 exe 是单实例的应用程序,这样当用于 eMbedded Visual Tools 的 Emulator.exe 运行时,用于 Visual Studio .NET 2003 的那一个就不能启动,反之亦然。另一个问题是当 Visual Studio .NET 2003 Emulator 用户接口关闭时,emulator.exe 不会关闭,直到宿主 Visual Studio IDE 实例关闭为止。在这种情况下,即使没有可见的模拟器在运行,eMbedded Visual Tools Emulator 也不能启动。

解决办法:不要同时使用两个模拟器。如果一个模拟器不能启动,请检查任务管理器,关闭任何正在运行的 emulator.exe 进程。

11.16. 为什么我无法通过 TCP 连接(而非 ActiveSync 连接)部署到设备?

表现:当设备与 Visual Studio 机器没有 ActiveSync 连接,但有一个有效的 TCP 连接时,部署无效。

原因:ConMan 利用 ActiveSync 连接在 Visual Studio 机器和设备之间执行一些初始设置。如果没有可用的 ActiveSync 连接,则必须手动执行该设置。

解决办法:使用单独提供的 Keyman 工具来执行初始的设备设置,步骤如下:

选择正确风格的 Keyman.exe(比如 ARMV4 或 SH4 等),将它放在设备中。启动 Keyman 并选择“FILE->CONNECT”。现在 Keyman 等待 VS 机器通过 TCP/IP 来连接它。当 VS 机器连接之后断开连接,这样设备中的 Keyman 就会自动退出。

在 VS 机器中,将 CryptoAPIWrapper.dll 从 / CompactFrameworkSDK/ConnectionManager/bin 复制到 KeymanDesktop.exe 的目录。启动 KeymanDesktop.exe。

单击“Save”,将设置保存在 KeymanDesktop.exe 正在运行的当前目录中。下一次 exe 启动时就会加载这个保存的默认值。

键入设备的 IP 地址

使用 Device Port 的推荐值。

键入 ConmanClient 位(例如 ConManClient.exe 驻留位)所在的“Target”目录的路径。典型值为 / CompactFrameworkSDK/ConnectionManager/Target。

使用 Connect 按钮来进行连接。

连接之后单击“prep device”。如果成功,会弹出一个对话框,告知您一切正常。

使用 Disconnect 按钮来断开连接。当您断开连接时,设备端位会自动退出。

启动设备中的 ConManClient.exe。首先,必须选择某种风格的目标设备。另外,还必须选择 Visual Studio 中“Target”目录的位置。

11.17. 如何使 Activesync 能够从 Visual Studio .NET 2003 连接到模拟器会话?

从 Windows Mobile Developer Power Toys 下载 Emulator ActiveSync Connection Tool:
http://www.microsoft.com/downloads/details.aspx?FamilyId=74473FD6-1DCC-47AA-AB28-6A2B006EDFE9&displaylang=en

11.18. 如何将文件复制到当前连接到桌面 ActiveSync 的设备中?

11.19. 为什么 Visual Studio 调试器连接到 ARMV4I 设备失败?

原因选择 ARMV4I CPU 类型后连接工具存在的 bug

解决办法配置用于 ARMV4T CPU 类型的工具:

1.

在 Visual Studio 中,在“Tools”菜单中单击“Select Windows CE Device CPU”。注:此菜单项是由用于 Visual Studio .NET 2003 Add-on Pack 的 Windows CE Utilities 安装的

2.

将设备体系结构更改为 ARMV4T

3.

单击“Configure”,然后单击“Close”。如果提示重启,则重启 Visual Studio

12. 基于 Windows Mobile 的 Smartphone

12.1. 从哪里可以获得最新的基于 Windows Mobile 的 Smartphone SDK?

12.2. 基于 Windows Mobile 的 Smartphone 菜单的规则是什么?

基于 Windows Mobile 的 Smartphone 菜单有若干规则,例如:

第一个菜单项(对应于左边的软按钮)不能有任何子项

第二个菜单项(对应于右边的软按钮)可以有子项

其他任何顶级菜单项都不使用

如果第一个菜单项有不止一个项,则会产生 NotSupportedException。

要获得完整的需求列表,请参考
基于 Windows Mobile 的 Smartphone 帮助文档

12.3. 当创建基于 Windows Mobile 的 Smartphone 项目时,为什么在工具箱会有禁用的控件?

基于 Windows Mobile 的 Smartphone 平台只支持一小组控件。不支持的控件则禁用。

有关更多信息,请参考下面的
基于 Windows Mobile 的 Smartphone 帮助文档

12.4. 基于 Windows Mobile 的 Smartphone 是否支持 .NET Compact Framework 的 RAM 安装?

不支持。.NET Compact Framework 是通过操作系统更新提供的,这取决于运营商 /OEM 的决定。因此,.NET Compact Framework 通常会作为更大的用于 Smartphone 的 Windows Mobile 操作系统更新的一部分提供。

12.5. 如何设置全屏多行编辑控件窗口的标题?

当前版本的 .NET Compact Framework 中不支持这项功能。

12.6. 如何通过现有的基于 Windows Mobile 的 Pocket PC CAB 文件生成基于 Windows Mobile 的 Smartphone CABWizSP XML 文档?

从 Windows Mobile Developer Power Toys 下载 Convert PPC DAT to SP XML:
http://www.microsoft.com/downloads/details.aspx?FamilyId=74473FD6-1DCC-47AA-AB28-6A2B006EDFE9&displaylang=en

12.7. 如何通过 ActiveSync 向基于 Windows Mobile 的 Smartphone 2003 Emulator 发送字符和字符串?

12.8. 如何以编程方式设置基于 Windows Mobile 的 Smartphone 输入模式?

可以根据以下代码,使用 GetFocus 和 SendMessage API 来设置输入模式:

'VB

Imports System.Runtime.InteropServices

Public Const EM_SETINPUTMODE As Integer = &HDE
Public Const EIM_SPELL As Integer = 0
Public Const EIM_AMBIG As Integer = 1
Public Const EIM_NUMBERS As Integer = 2

 _
Public Shared Function GetFocus() As IntPtr
End Function

 _
Public Shared Function SendMessage(ByVal hWnd As IntPtr, _
  ByVal Message As Integer, ByVal wParam As Integer, _
  ByVal lParam As Integer) As Integer
End Function

'Sample use setting TextBox to number input
Private Sub txtAmount_GotFocus(ByVal sender As Object, _
  ByVal e As System.EventArgs) Handles txtAmount.GotFocus
  
    Dim hWnd As IntPtr
    hWnd = Me.GetFocus()
    SendMessage(hWnd, EM_SETINPUTMODE, 0, EIM_NUMBERS)
    txtAmount.SelectionStart = txtAmount.Text.Length

End Sub

//C#

using System.Runtime.InteropServices;

public const uint EM_SETINPUTMODE = 0xDE;
public const uint EIM_SPELL = 0;
public const uint EIM_AMBIG = 1;
public const uint EIM_NUMBERS = 2;

[DllImport("coredll.dll")]
public static extern IntPtr GetFocus();

[DllImport("coredll.dll")]
public static extern int SendMessage(IntPtr hWnd,
  uint Message, uint wParam, uint lParam);

// Sample use setting TextBox to number input
private void Form1_Load(object sender, System.EventArgs e)
{
    txtAmount.GotFocus +=
      new System.EventHandler(txtAmount_GotFocus);
}

private void txtAmount_GotFocus(object sender, System.EventArgs e)
{
    IntPtr hWnd;
    hWnd = GetFocus();
    SendMessage(hWnd, EM_SETINPUTMODE, 0, EIM_NUMBERS);
    txtAmount.SelectionStart = txtAmount.Text.Length;
}


展开阅读全文

没有更多推荐了,返回首页