65.9K
CodeProject 正在变化。 阅读更多。
Home

Pocket PC 版货币转换器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (15投票s)

2005年3月4日

CPOL

19分钟阅读

viewsIcon

142003

downloadIcon

915

了解如何使用 Visual Studio .NET 2003 为 Pocket PC 2002/2003 编写一个实用的智能货币转换器工具,该工具支持离线和在线模式。

目录

图 1. 应用程序的主页(转换)和选项页

简介与背景

几周前,我工作的公司淘汰了所有老式手机,换成了运行 Microsoft Windows Mobile 2003 版本的新款手机。因此,我很好奇也很受启发,想知道如何为 Pocket PC 编写真实的应用程序代码,同时考虑到从构思到最终平稳运行在物理设备上的完整安装包过程中可能遇到的所有陷阱。在本文中,我将引导您完成完成此任务所需的所有必要步骤,并与您分享我在试验这项技术过程中学到的整体经验。

我开始学习时,使用了一本薄薄但非常有用的书,作者是 Wei-Meng Lee - .NET Compact Framework。该书作者介绍了一个有限的货币转换器示例,该示例仅使用一个 Currency - Web Service 来处理三种货币。我决定完成这个示例,并将其扩展到 Currency - Web Service 提供的所有可用货币。我的目标是提供一个经过测试且易于部署的软件包,使用方便标准的 Windows 安装程序方式。另一篇有用的文章来自 Code Project 网站“Developing and Deploying Pocket PC Setup Applications”。撰写本文并分享我的经验的想法,是在我为 Pocket PC 进行部署时,执行正确操作的过程中,弹出了以下消息框:“您安装的程序可能无法正常显示,因为它适用于旧版本的 Windows Mobile 软件。”(图 2)。让我们来研究一下。

图 2. Pocket PC 2003 SE 上的部署时间消息

您将需要的工具列表

要为基于 Pocket PC 2002、Pocket PC 2003、Pocket PC Phone Edition 或 Windows CE .NET 4.1 的设备开发基于 .NET Compact Framework 的应用程序,需要 Visual Studio .NET 2003 Professional 或更高版本。但请记住,Pocket PC 系列只是 Windows CE .NET 的一个特殊版本,它的“小兄弟”,Windows CE .NET 是一个更通用的平台,运行在各种设备上。如果您想充分利用 Windows CE .NET 平台的强大功能,并希望获得完整无限的控制权,您必须开始使用 eMbedded Visual C++ 4.0(以及 eMbedded Visual C++ 4.0 SP3)来开发您的应用程序。MSDN 中的文章 Differences in Microsoft .NET Compact Framework Development between the Pocket PC and Windows CE .NET 详细描述了 Pocket PC 和 Windows CE .NET 之间确切差异的全面概述。要使用 C# 为 Pocket PC 设备(包括运行 Pocket PC 2003 Second Edition 的设备)进行开发,您需要下载并安装以下软件包:

限制

也许您已经知道,Windows Mobile 2003 Second Edition 支持以下模式、分辨率和 DPI 实现:

  • 纵向/横向 QVGA(240x320,96 DPI)
  • 纵向/横向 VGA(480x640,192 DPI)
  • 方形屏幕(240x240,96 DPI)
  • 方形屏幕 VGA(480x480,192 DPI)

坏消息是,Visual Studio .NET 2003 开发环境仅支持纵向 QVGA 模式。开发人员甚至无法捕获发送到全尺寸主窗口的 WM_SIZE 事件,该事件在屏幕方向从纵向变为横向或反之时发送。这是因为 .NET Compact Framework 1.0 不支持 WndProc 方法,因此无法覆盖它(例如,对于主 Windows.Forms.Form 类)以对其做出响应。

好消息是,可以找到一个解决方法。我们可以安全地确定屏幕的当前分辨率,从而也确定纵向/横向模式;使用 Form 类的 BottomTopLeftRight 属性(例如,在 GetFocus 事件处理程序中,如图 3 所示),描绘并重新排列任何控件的大小和位置。唯一行为有些不典型的控件是应用程序的底层主窗口(根控件 - 在本例中是 TabControl)。该控件的宽度会根据纵向/横向模式自动调整为 240 或 320。然而,高度的尺寸可调范围有限,并且除非启用并显示软输入面板 (SIP) 且在屏幕方向更改之前和期间可见,否则无法进行任何更改尝试。换句话说,只有当 SIP 出现在屏幕上时,您才能调整根控件的高度。

解决纵向/横向困境的最佳方法似乎是最简单的(尽管不总是可行),即设计一个适合方形屏幕(240x240)的应用程序。如果您对此感兴趣,可以阅读 MSDN 上的文章:Developing Screen Orientation-Aware Applications,这也是 Windows Mobile 2003 Second Edition Developer Resources 包的一部分。

private void Form1_GotFocus(object sender, System.EventArgs e)
{
    int screenHeight = this.Bottom + this.Top;
    int screenWidth = this.Left + this.Right;

    if (screenHeight == 320 && screenWidth == 240)
    {
        // Portrait
        ...
    }
    else if (screenHeight == 240 && screenWidth == 320)
    {
        // Landscape
        ...
    }
}

图 3. GetFocus 事件处理程序

货币转换器应用程序的解决方案

C# 解决方案包含三个项目。主项目是 SmartDeviceCurrency,它包含了整个应用程序。另外两个项目(InstallerService 和 Setup)用于部署目的,正如 Developing and Deploying Pocket PC Setup Applications 文章中所解释的那样。Setup 项目打包了所有二进制文件,并完成了构建过程,创建了 setup.exe 引导程序和 PocketCurrencySetup.msi Windows 安装程序包,而 InstallerService 项目负责创建安装程序的自定义操作程序集 (DLL),该程序集将在物理 Pocket 设备上启动部署。但现在不要纠结于这两个项目,我将在本文后面详细解释它们。

图 4. 解决方案资源管理器

SmartDeviceCurrency 项目

该应用程序的要求可以总结如下:

  • 应用程序应处理来自所涉及的 Currency - Web Service 提供的 152 种货币列表中的任何货币。
  • 离线模式访问最后下载和保存的货币汇率(在 CurrencyRates.xml 中)。
  • 用户应拥有自己有限的偏好列表(不太可能有人想要所有 152 种货币)。
  • 第一次启动应用程序时,货币列表为空,用户必须被重定向到选项页(参见图 1)以配置自己的货币偏好。

该项目是一个 Smart Device Application C# 项目。我向项目中添加了一个 Web 引用,其位置是 http://www.webservicex.net/CurrencyConvertor.asmx,并将类型重命名为 WebServiceX(参见图 4)。WebServiceX 提供了一个“Currency”枚举类型,这是一个包含 152 种货币的静态列表,此外,还有一个 CurrencyConvertor 类,其中有一个“ConversionRate”方法,该方法可以同步和异步执行。此应用程序仅使用同步调用。尽管在 WSDL XSD 架构中,“Currency”枚举定义为基于字符串的限制类型,长度限制为三个字符,但 Visual Studio 的 Web 引用将其包装成一个基于整数列表的常规枚举。

<s:simpleType name="Currency">
    <s:restriction base="s:string">
              <s:enumeration value="AFA" />
              <s:enumeration value="ALL" />
                    ...
              <s:enumeration value="BHD" />
              <s:enumeration value="ZWD" />
    </s:restriction>
</s:simpleType>

图 5. WSDL 中的架构定义(部分)

[System.Xml.Serialization.XmlTypeAttribute(Namespace
                                             ="http://www.webserviceX.NET/")]
public enum Currency {       
   AFA,
   ALL,
   ...
   BHD,
   ZWD
}

图 6. Currency 枚举

在代码中,您需要处理整数枚举列表(图 6),它与原始字符串表示(图 5)没有明显的关系。当然,我可以修改 Visual Studio 生成的 Web 服务包装类,但这似乎不是一个好主意。因此,我不得不寻找另一种更简单的方法来在三个字符的符号及其整数值之间进行切换。幸运的是,我有一个 FieldInfo 类,它提供了对枚举元数据的访问,并建立了从整数值到字符串的关系。此外,我可以使用 HashTable 类来快速切换回字符串到整数表示。

更仔细地查看货币枚举,您会发现另一个障碍:枚举列表未排序。这尤其会造成麻烦,当货币符号添加到 ComboBox 实例时,用户会开始按字母顺序寻找他们的货币。不幸的是,.NET Compact Framework 1.0 不支持 ComboBoxHashTable 的排序功能,因此我必须使用一个额外的 ArrayList 类来排序符号。如您在图 7 中所示,描述的初始化应该只在应用程序启动时进行一次。

void initializeAvailableCurrencies()
{
    ArrayList al = new ArrayList(164);
    ht = new Hashtable(164);

    Cursor.Current = Cursors.WaitCursor;    
    comboBoxCurr.Items.Clear();

    Type currency = typeof(WebServiceX.Currency);

    int key = 0;
    foreach(FieldInfo s in currency.GetFields())
    {
        string str = s.Name;
        if (str.Length == 3)
        {
            al.Add(str);        
            ht.Add(str, key);
            key++;
        }
    }

    al.Sort();    // sort all of them and just thereafter add the 
                  // items to the ComboBox
    for (int i = 0; i < al.Count; i++)
        comboBoxCurr.Items.Add(al[i]);

    comboBoxCurr.SelectedItem = comboBoxCurr.Items[0];
    comboBoxCurr.Refresh();
    Cursor.Current = Cursors.Default;    
}

图 7. 初始化可用货币列表

请注意,ArrayList 是临时使用的,而 HashTable 应该在 Forms 类中声明为“全局”变量,并且必须可用,以便从字符串符号切换到整数,如图 8 所示。

private WebServiceX.Currency getCurrency (string symbol)
{
    int i = 0;
    if (ht.Contains(symbol))
        i = (int)ht[symbol];

    return (WebServiceX.Currency)i;
}

图 8. 将字符串符号转换回 Currency 枚举类型

应用程序的视觉外观基于一个 TabControl,该控件包含两个页面:转换和选项。选项页上的 ComboBox 包含了我上面描述的所有可用货币。用户可以在 ComboBox 中选择货币符号,然后点击“添加”按钮,该按钮会将符号添加到首选货币列表(ListBox)中。当添加第一个符号时,它将自动被选为所有转换汇率的基础货币,因此其基础货币汇率为“1”。之后,用户可以通过点击“设置基础”按钮覆盖并选择其他基础货币。如果 ListBox 中出现新货币,则会显示一个黄色感叹号图标和一条消息,警告用户更新离线存储以获取 Web 服务中的最新货币汇率。通过点击“更新列表”按钮,用户可以决定是在线、离线(在这种情况下,为新货币汇率分配 0)还是取消操作。

现在,切换到转换页面。用户的偏好以“Currency”的两列表形式存储在 DataSet 实例中。该表包含名为“Sym”和“Rate”的列。有趣的是,至少需要三个控件共享存储在表中的数据,并且显然这三个实例对同一数据的视图不同。提到的控件是:

  • ListBox(选项页)
  • From - ComboBox(转换页)
  • To - ComboBox(转换页)

这三个控件必须使用 DataView 类的不同实例,这些实例基于 DataSet 的“Currency”表创建,如图 9 的代码所示。ListBoxComboBox 控件显示货币符号,检索符号值将自动发送相应的货币汇率。请注意,我在 Form1 构造函数中为该表创建了一个主键,这应该可以防止多次添加相同的符号。

public Form1()
{
    InitializeComponent();

    // Adjust personal/privat folder usage for offline store
    checkPrivateFolders();

    // Creating the empty DataSet, the table and their columns
    ds = new DataSet("MyCurrency");
    DataTable dt = new DataTable(CURRENCY);
    dt.Columns.Add(SYMBOL, typeof(string));
    dt.Columns.Add(RATE, typeof(double));

    // Set primary key on the table
    DataColumn[] key = new DataColumn[1];
    key[0] = dt.Columns[SYMBOL];
    dt.PrimaryKey = key;

    // Add/Integrate the table into the DataSet
    ds.Tables.Add(dt);
}

private void Form1_Load(object sender, System.EventArgs e)
{
    // Initialize list of all available currencies
    initializeAvailableCurrencies();

    // Create needed Data-Views based on DataSet
    initializeViews();
    ...
}
        
private void initializeViews()
{
    viewFrom = new DataView(ds.Tables[CURRENCY]);
    viewTo = new DataView(ds.Tables[CURRENCY]);
    viewBox = new DataView(ds.Tables[CURRENCY]);

    comboBoxFrom.Items.Clear();
    comboBoxFrom.DataSource = viewFrom;
    comboBoxFrom.DisplayMember = SYMBOL;
    comboBoxFrom.ValueMember = RATE;

    comboBoxTo.Items.Clear();
    comboBoxTo.DataSource = viewTo;
    comboBoxTo.DisplayMember = SYMBOL;
    comboBoxTo.ValueMember = RATE;

    listBoxCurr.Items.Clear();
    listBoxCurr.DataSource = viewBox;
    listBoxCurr.DisplayMember = SYMBOL;
    listBoxCurr.ValueMember = RATE;
}

图 9. 创建 DataSet 和 DataView 实例

现在让我们讨论一些关于用户友好应用程序行为的话题。对于 Pocket PC 而言,这首先意味着在适当的时候尝试显示 SIP(InputPanel 控件),并在用户不再需要它时自动隐藏它。您可以在图 1 中看到,转换面板很可能会使用 SIP,而选项面板则不会。SIP 的启用是通过将其单个属性“Enabled”设置为 truefalse 来激活的。更改属性值将触发一个“EnabledChanged”事件,该事件可以被捕获(图 10),以便调整根控件(TabControl)的高度。如我之前在本文中所述,这在纵向模式下(Pocket PC 2003 SE 上)效果很好,但在切换到横向模式时,如果 SIP 未启用,可能会失败。但是,您可以安全地忽略这一点,因为切换到横向模式时,TabControl 的高度保持为纵向模式的大小,用户将在屏幕的右侧(或左侧)看到滚动条。

private void inputPanel1_EnabledChanged(object sender, System.EventArgs e)
{
    int currentHeight = this.Bounds.Height;
    tabControl1.Height = (inputPanel1.Enabled) ? 
                   currentHeight - inputPanel1.Bounds.Height : currentHeight;
}

private void inputPanel1_EnabledChanged(object sender, System.EventArgs e)
{
    tabControl1.Height = (inputPanel1.Enabled) ?  
                                     272 - inputPanel1.Bounds.Height : 272;
}

图 10. EnabledChanged 事件处理程序 - 两个版本

在图 10 中,EnabledChanged 事件处理程序有两个变体。第二个变体假定 TabControl 在纵向模式下的垂直高度,这似乎是一个不错的猜测,尽管我从不满意在尺寸会动态变化的环境中使用固定值,例如切换到横向模式。因此,我更喜欢第一个变体,它捕获当前的 Form 高度。在我的测试中,它在纵向模式下返回 268,在横向模式下返回 188。

当用户在转换和选项页面之间切换时,SIP 应该出现和消失。可以通过捕获 TabControl 的“SelectedIndexChanged”事件来实现此行为,如图 11 所示。代码中实现了几个事件处理程序,通过在选项页 ComboBox 中搜索条目等方式为用户提供便利。应用程序可以被重新激活(在屏幕上可见)的事实也应该被考虑在内。通过实现主 FormGetFocus 事件处理程序可以捕获此时刻。

private void tabControl1_SelectedIndexChanged(object sender, 
                                              System.EventArgs e)
{
    switch (tabControl1.SelectedIndex)
    {
        case 0:
            inputPanel1.Enabled = true;
            textBoxValue.Focus();
            break;
        case 1:
            inputPanel1.Enabled = false;
            break;
        default: // Wonder - should never come here!
            break;
    }
}

图 11. SelectedIndexChanged 事件处理程序

部署准备

现在应用程序已编译并在 Pocket PC 模拟器中运行。部署问题留待解决。首选场景在“Developing and Deploying Pocket PC Setup Applications”中进行了讨论,我在这里仅关注于使创建 Windows 安装程序包的过程更加清晰透明的步骤。要开始此操作,不可避免地需要完成与 SmartDeviceCurrency 项目直接相关的另外两个步骤,它们是:

  • 手动创建 cabinet 文件(一次性)
  • 创建自定义 cabinet 构建批处理文件和“inf”文件

这些步骤的结果将用作 InstallerService 的预构建事件的起点。老实说,InstallerService 项目在任何情况下都不需要这些结果。让我解释一下!我们解决方案中的最后一个(第三个)项目是 Setup 项目,它打包了您的应用程序所需的所有内容,因此需要所有 cabinet 文件以及 InstallerService 的程序集。预构建步骤仅构建 cabinet。首选解决方案是在 SmartDeviceCurrency 项目中通过后构建事件完成此操作。不幸的是,SmartDeviceCurrency 项目不提供预/后构建事件之类的功能,这促使我们将此任务转移到任何其他具有预/后构建事件可能性并且构建顺序在 SmartDeviceCurrency 项目之后但在 Setup 项目之前的项目中。是的,正如您所见,只剩下一个项目满足这些要求。

您应该手动创建 cabinet 文件,切换到解决方案的 Release 版本,重新构建它,然后点击菜单“Build/Build Cab File...”。这将在项目文件夹中创建一个名为“\cab\Release”的新目录,其中包含 cabinet 文件。此外,您会看到项目“\obj\Release”目录中出现了一些新文件,其中最重要的是 BuildCab.batSmartDeviceCurrency_PPC.inf 文件。现在您可以安全地忽略 cabinet 文件,甚至可以删除它们,因为我们将创建自己的位置和构建批处理文件来以自动化的方式再次完成此任务,这将在宣布的预构建事件中完成。

想知道这些 cabinet 文件有什么用处,您可以开始以下实验。确定您的 Pocket PC 的处理器类型。如果您使用的是 Pocket PC 2002 或 2003,那么您很可能拥有 ARMV4 处理器。在这种情况下,将相应的 SmartDeviceCurrency_PPC.ARMV4 cab 文件复制到 Pocket PC 上的某个目录,然后点击 cab 文件。cabinet 文件将被提取,并且应用程序将根据 .inf 文件中的定义进行部署。当然,这种方法很不方便。每次部署之前,都应该确定处理器类型并手动复制到设备上。

在 SmartDeviceCurrency 项目文件夹中,创建一个名为“BuildCab”的新目录,并在其中创建一个名为“cabs”的子目录(参见图 4)。将 BuildCab.batSmartDeviceCurrency_PPC.inf 这两个重要文件从源文件夹“\obj\Release”复制到新的“BuildCab”文件夹中。在 Visual Studio IDE 中,将这两个新文件添加到您的 SmartDeviceCurrency 项目中。打开 BuildCab.bat 文件,并确保 cabwiz.exe 实用程序使用指向新的 SmartDeviceCurrency_PPC.inf 文件的正确绝对路径,并且 /dest 开关指向新的“BuildCab\cabs”文件夹。保存 BuildCab.bat 文件。

现在打开 SmartDeviceCurrency_PPC.inf 文件并仔细查看其内容。该文件分为几个部分。您可以访问 Microsoft Windows CE .NET: Creating an .inf File,以获取有关 .inf 文件的更多信息。请遵循以下步骤:

  • 第一个部分 [Version] 包含一个“Provider”参数,该参数描述了 .inf 文件的创建者,通常是制造商。您可以安全地将其更改为您喜欢的任何名称。
  • [CEStrings] 部分包含 AppName 参数;更改部署的目标文件夹名称也是安全的。
  • [CEDevice] 部分描述了应用程序的目标设备平台。在原始 .inf 文件中,该部分只有两个参数:VersionMinVersionMax。现在是时候提醒您图 2 中的消息了。如果您不更改此部分,在 Pocket PC 2003 SE 上,您将遇到这种警告,这实际上意味着检测到一个为仅纵向模式编写的旧应用程序。由于该应用程序被设计为处理横向和方形屏幕,因此应插入第三个参数 BuildMax,如图 12 所示。
    [CEDevice]
    VersionMin=3.00
    VersionMax=4.99
    BuildMax=0xE0000000

    图 12. CEDevice 部分

  • 最后一个部分 [Shortcuts] 也可以安全地进行修改。这描述了目标设备上的快捷方式。将长快捷方式名称拆分,插入一些空格,使其在“程序”视图中更易于阅读,这是一个很好的做法,就像图 13 中所示的“Terminal Services Client”快捷方式一样。

    图 13. 程序视图

  • 在试验各种 .inf 文件内容时,我发现,如果移除 .inf 文件中所有引用 vsd_setup.dll 的行以及所有引用 *_PPC.inf 本身的引用,生成的 Windows 安装程序包将以最可靠的方式工作。CESetupDLL 组件用于版本检查,如果 Pocket PC 设备没有正确版本的 .NET Compact Framework,则会报告信息。不幸的是,检查似乎不可靠。在安装然后卸载 .NET CF 的 Pocket PC 2002 设备上,vss_setup.dll 无法识别丢失的 .NET Framework。另一方面,在 Pocket PC 2003 SE 设备上,会弹出设置错误消息,可以通过删除所有 vss_setup.dll 引用来修复。vss_setup.dll 用于确定 .NET CF 的存在和版本的 [方法] 未公开。如果您想检测目标设备上 .NET Framework 的升级(这是个好主意,因为在撰写本文时,.NET CF SP3 已发布),建议您编写自己的版本,根据文章 Creating an MSI Package that Detects and Updates the .NET Compact Framework 进行检测。不幸的是,这是 C++ 专家的领域,您必须使用 eMbedded Visual C++ 4.0。
  • 在此阶段的最后一个操作是创建一个自定义的 setup.ini 文本文件,并将其添加到 SmartDeviceCurrency 项目中。从文章“Developing and Deploying Pocket PC Setup Applications”中复制此文件,保留其结构,并仅根据当前项目中生成的名称修改 cabinet 文件名。

现在我们准备将 BuilCab.bat 批处理文件集成到 InstallerService 项目的预构建事件中。

InstallerService 项目

启动和进行此操作的方法如下:

  • 向解决方案添加一个新的“类库”类型项目。
  • 将默认的 Class1 类重命名为 InstallerService 类,并让它继承自 System.Configuration.Install.Installer 类。
  • 添加对 System.Configuration.Install.dll 的项目引用。
  • 添加对 System.ServiceProcess 命名空间的命名空间引用。
  • 添加对 System.Windows.Forms.dll 的项目引用。
  • 添加对 System.Windows.Forms 命名空间的命名空间引用。
  • InstallerService 类添加 [RunInstaller(true)] 属性。
  • 现在您应该可以编译而没有错误。
  • 切换到设计视图;现在在属性窗口中,应该已经出现了闪电符号。切换到事件,并为 AfterInstallAfterUninstall 事件添加两个新的事件处理程序。

从文章 Developing and Deploying Pocket PC Setup Applications 中获取其余内容。在项目没有错误地编译之前,请勿继续。

Setup 项目

开始填写 Setup 的 Properties 窗格中的一些属性,如图 14 所示。

图 14. Setup 属性

切换到文件系统视图,并确保应用程序文件夹的 DefaultLocation 属性默认为“[ProgramFilesFolder][Manufacturer]\[ProductName]”。或者,您可以创建一个子文件夹,例如 Setup,然后手动添加所有文件,如图 4 所示。之后,切换到自定义操作视图,并添加一个安装自定义操作和一个卸载自定义操作,其中 Name 属性引用 InstallerService 项目中创建的 InstallerService.dll 程序集(这是 Project Output/Primary output)。重新构建解决方案。生成的 Windows 安装程序文件位于项目“Setup\Release”文件夹中。为避免混淆,请注意 dotnetfx.exesetup.exe 使用,用于在连接到 Pocket PC 设备的台式机或笔记本电脑上部署 .NET Framework。还记得吗?.NET Framework 需要在那里启动由 InstallerService.dll 程序集提供的自定义操作,并且该自定义操作必须启动 CeAppMgr.exe。因此,dotnetfx.exe 绝对不是 .NET CF 所在的包。最后,请不要忘记;在 Pocket PC 2002 及更早版本上,您必须手动部署 .NET Compact Framework 1.0 SP3 可再发行组件

结论

本出版物验证了早期关于 Pocket PC 应用程序的文章中提出的方法和技术,通过 Windows 安装程序服务以常规方式构建一个可安装的真实 Pocket PC 应用程序。您学会了如何处理长枚举列表,在整数值和字符串格式的字段名之间进行切换以及反之。您学会了如何通过处理新的 Pocket PC 2003 SE 功能来应对支持纵向和横向模式的解决方案,并且您了解了为完成的 Pocket PC 应用程序创建 Windows 安装程序包时遇到的障碍。

参考文章

© . All rights reserved.