使用 Compact Framework 进行用户界面本地化






4.58/5 (25投票s)
Compact Framework 不支持在运行时更改 UI 的区域性。这个解决方案将允许您切换区域性,同时仍然利用完整框架中的大部分可用功能。
引言
多年来,我一直在 Windows Mobile 2003 和 5.0 上开发移动应用程序,并且在 ASP、ASP.NET 和智能客户端中开发了全球化/本地化应用程序。最近,我第一次需要将这些功能结合起来,编写一个针对 Windows Mobile 5.0 平台(使用 Microsoft Compact Framework 版本 2.0)的本地化智能客户端应用程序。与 Compact Framework 上的大多数新事物一样,它最初令人兴奋,但很快就变得令人沮丧,因为您会发现“完整” .NET Framework 中为了减小“Compact”版本的占用空间而牺牲的所有新领域。本文将介绍我面临的挑战以及我使用的解决方案。
问题空间
在完整的 .NET Framework 中,您拥有 System.Resources.ResourceManager
类,它提供了所有内置功能,您需要这些功能来根据 System.Threading.Thread.CurrentUICulture
轻松地从外部程序集加载资源。ResourceManager
足够智能,可以找到正确的程序集,查找资源,如果找不到资源,它将“自动”在父区域性中查找您的资源,直到找到匹配项,或者最终回到 InvariantCulture
(这基本上是未知或通用区域性)。更改用户界面使用的资源就像
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
Compact Framework 的第一个限制是它不支持 CurrentThread.CurrentUICulture
。这意味着您的应用程序将使用操作系统的区域性(有关更多信息,请参阅 MSDN 上的这篇文章)。但是,如果您遇到所有移动设备都设置为美式英语 (en-US) 的情况,但有些操作员可能希望使用墨西哥西班牙语 (es-MX) 或加拿大法语 (fr-CA) 的特定应用程序怎么办?除非您使用操作系统的控制面板来更改整个设备的区域性设置,否则您无法使用内置的 ResourceManager
来实现。
问题 #1 是我们需要一种方法来更改应用程序的 CultureInfo 并让 ResourceManager 使用它。
接下来,如果您在 Visual Studio 2005 中进行过任何全球化/本地化工作,您可能会发现有一个内置机制可以使您的表单“可本地化”。我不会尝试对它的工作原理进行全面论述。如果您有兴趣,我建议您去“Google Lane”走一走。简而言之,您可以使用 IDE 的表单属性以不同的区域设置查看表单,并根据每个区域设置更改某些属性值(即 Control.Text
)。在“幕后”,IDE 为您支持的每个区域设置创建本地化资源程序集,并会在运行时“自动”选择正确的资源集(类似于上面描述的 ResourceManager
)。
Compact Framework 的第二个限制是更新 UI 的机制都与当前线程的 CurrentUICulture
绑定,正如我们上面看到的,它永远不会改变(除非在设备级别进行)。
问题 #2 是我们需要在检测到区域性设置发生变化时动态更新 UI 的能力。
设计目标
除了解决上述问题之外,我对最终解决方案还有某些设计目标。
- 它需要工作(显然),但这样做,我不想重复发明轮子。它需要尽可能多地利用现有基础设施。特别是,这意味着查找/选择资源文件的逻辑以及缺失程序集或资源的后备机制。
- 它需要尽可能“自包含”和易于使用。我不想让开发人员陷入实现该机制的困境。
- 它需要支持将来添加新的资源程序集,而无需对源代码或二进制文件进行任何更改。
解决方案
我为此部署的解决方案分为两部分。第一部分是自定义 ResourceManager
(实现为 Singleton),它使应用程序能够指定在查找资源时使用的 CultureInfo
。第二部分是 LocalizedForm
(派生自 System.Windows.Forms.Form
),它被设计用作任何需要本地化支持的表单的基类。有关如何实现此功能的完整详细信息,请参阅随附的源代码和示例项目。
CompactFramework.Utilities.Localization.ResourceManager
自定义 ResourceManager
为您的应用程序提供了一种指定要使用的资源的 Assembly
和 CultureInfo
的方式。每当 CultureInfo
更改时,都会触发一个事件,让事件订阅者知道发生了更改。ResourceManager
还公开了一些重载的辅助方法,允许 LocalizedForm
更轻松地检索资源。
CompactFramework.Utilities.Localization.LocalizedForm
LocalizedForm
提供了一个通用的基类,可用于提供侦听和响应 ResourceManager.CultureChanged
事件的功能。它将遍历其所有子控件和菜单并使用适当的本地化资源更新它们的相应属性(即 Control.Text
、MenuItem.Text
、PictureBox.Image
等)。
菜单真烦人!
Compact Framework 的另一个令人讨厌的地方是处理菜单控件。问题的根源在于 MenuItem
实际上并非派生自 Control
基类,因此实际上没有可以在运行时读取的 Name
属性。我曾短暂尝试过基于反射的解决方案来解决这个问题,但最近为一个类似应用程序完成了基于反射的硬件抽象层的工作,我知道我会遇到 Compact Framework 的限制以及性能损失。
相反,我将一部分实现负担放在了开发人员身上,以提高性能。因此,LocalizedForm
基类维护一个 Dictionary<MenuItem, string>
,开发人员在每个表单的构造函数中填充它,如下所示
this.AddMenuToDictionary(this.menuItem1, "MainMenuLeft");
this.AddMenuToDictionary(this.menuItem2, "MainMenuRight");
LocalizedForm 类在资源程序集中查找 Text 值时,使用这些字符串代替 Control.Name
属性。
最后两个“陷阱”
在使用本地化时,您必须注意正确完成两个方面,否则它将无法工作(并且不会抛出任何错误)。如果您没有正确找到并命名资源文件,它将找不到它们。如果您没有正确命名资源,它将找不到它们。在任何一种情况下,ResourceManager
都只会返回其最佳匹配项,或者(最坏的情况)不会对 UI 进行任何更改。
资源文件的位置和名称
为了使底层 ResourceManager
的“内置”功能正常工作,您必须确保做最后一件事。当您将这些资源文件添加到项目中时,它们必须位于名为“Resources”的项目目录中,并且它们必须命名为 Resources.<culture-code>.resx(请参阅本文顶部的图片以获取示例)。如果您不执行这两个操作,ResourceManager
将无法找到您的资源文件,并且您将不会获得任何本地化更新。
命名您的资源
正如您在文章顶部的图片中看到的那样,此基础设施利用控件的名称与父表单的名称结合起来,以唯一标识资源。如果您在资源文件中不遵循此命名约定,将找不到匹配项,并且不会对 UI 进行任何更改(也不会抛出错误)。
剩下什么?
目前,我处理基本控件(TextBox
、Label
、Button
、CheckBox
、TabPage
、Form
、RadioButton
和 PictureBox
)。当然可以通过更新 LocalizedForm.UpdateControls()
函数来添加其他控件。
摘要
我希望这对您来说是有趣和/或有用的。我喜欢做这些,我感谢反馈(包括积极的和建设性的批评),我喜欢看到人们在他们自己的解决方案中如何使用这些。如果这对您有用或有趣,请花点时间评价这篇文章。如果您有任何问题或建议,请随时在下面发布。