构建 Silverlight 企业应用程序的冒险 - 第 4 部分
构建 Silverlight 企业应用程序时的冒险
又过去一周了。我们带着冒险之旅的第四部分回来了。上次,我们深入了解了使用多个 XAP 文件的来龙去脉。再次感谢 dsoltesz 对该文章提出的一些建设性意见。
这次,我们想研究一下我上周挣扎了一天左右的东西:本地化我们的应用程序。您可能还记得第一篇文章,其中一项要求是该应用程序必须同时提供英语和荷兰语版本。“嗯,这很容易。只需访问 silverlight.net,您会找到一个非常不错的教学视频”,我听到您说。这也是我们最初的想法,事实证明确实如此...
...直到我们尝试将其应用于 datagrid
。我将使用我其他文章中的汽车应用程序来描述发生了什么。
默认实现
正如许多文档所述,我们执行了以下操作来本地化我们的应用程序
- 向我们的项目添加一个名为“Resources”的文件夹
- 添加一个名为“Strings.resx”的文件,并为中性文化添加任何
string
(在我们的例子中,荷兰的荷兰语为nl-NL
) - 为您要支持的每种文化添加一个文件(在我们的例子中,只有“Strings.en.resx”),其中包含所有相同的
string
- 为生成的类向每个
UserControl
添加一个本地资源<UserControl.Resources> <cardemo:Engines x:Key="Engines" /> <local:Strings x:Key="LocalStrings" /> </UserControl.Resources>
- 将要显示的每个
string
绑定到资源中的一个条目<data:DataGridTextColumn Binding="{Binding Brand}" Header="{Binding brandLabel, Source={StaticResource LocalStrings}}"> </data:DataGridTextColumn>
一切就绪。对于任何控件,这都很好用,但对于 datagrid
则不然。运行代码,您将收到以下异常
System.Windows.Markup.XamlParseException occurred Message=
"AG_E_PARSER_BAD_TYPE [Line: 11 Position: 46]" LineNumber=11
LinePosition=46 StackTrace: at System.Windows.Application.LoadComponent(
Object component, Uri resourceLocator)
at ComboLookup.Page.InitializeComponent() at ComboLookup.Page..ctor()
我做的第一件事是在 Google 上搜索,看看是否有人已经解决了这个问题。我发现这个问题是在 RTW 版本中引入的,并且我在 silverlight.net 上找到了一些论坛帖子,讨论了这个问题。我甚至找到了一些解决方案,但我对这些解决方案不是很满意。一种解决方案最终在 LINQ 语句中使用了一些反射代码,将所有字符串条目添加到本地 Resources 实例(这可能会导致具有较大资源文件的应用程序出现性能问题)。另一种解决方案涉及构建自定义机制来填充 datagrid
的标题,这在我看来就像一场维护噩梦。
所以我认为我必须提出一些更好的解决方案。首先,我开始挖掘以找出导致问题的原因。我使用 Reflector 来挖掘实际执行的代码,发现问题实际上存在于 Silverlight .NET Framework 的核心中,XAML 实际上是在那里解析的,您无法访问该代码。这让我感到奇怪,因为您会期望 XAML 解析器是完全通用的,但仍然存在这种针对特定控件的特定情况,出了问题。
基于这一事实,我得出结论,修复程序不会存在于控件代码中,事实上我无法修复根本问题。这意味着我必须为这个问题找到一个体面的解决方法,该解决方法适用于我们的应用程序。
因此,我又回到了 Google,并开始研究其他人的解决方案,以获得一些好的修复灵感。最后,我不介意将字符串添加到本地 Resources 实例中,因为这将产生漂亮的 XAML 并且工作起来最直观。问题是我不喜欢人们获取资源文件中字符串的方式。必须有一种比实际使用反射来获取我们需要的每个属性更好的方法。
我使用了 ResourceManager
类并阅读了一些文档,结果得到了以下代码
private void LoadLocalStrings()
{
ResourceManager manager = new ResourceManager("ComboLookup.Resources.Strings",
Assembly.GetExecutingAssembly());
ResourceSet resourceSet = manager.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
IDictionaryEnumerator resourceEnum = resourceSet.GetEnumerator();
while (resourceEnum.MoveNext())
{
Resources.Add(resourceEnum.Key.ToString(), resourceEnum.Value);
}
}
此方法在构造函数中的 InitializeComponents()
之前调用。
发生的情况是在您指定的程序集中查找具有指定基本名称(“ComboLookup.Resources.Strings
”)的资源管理器。基本名称必须是资源类实例的完全限定名称。
应该注意的是,如果您在 XAML 中定义了资源,它们将覆盖之前加载的资源。如果您需要加载本地化字符串以外的其他资源,您应该从代码中进行加载,方法是将它们添加到 Resources 实例。
使用本地化字符串的 XAML 如下所示
<data:DataGridTextColumn Binding="{Binding Brand}" Header="{StaticResource brandLabel}">
</data:DataGridTextColumn>
请注意,您现在可以直接访问具有所需字符串的资源。为了防止我们在必须构建的每个模块中都实现此操作,我们定义了一个基类来为我们解决此问题。此方法中唯一改变的是我们通过在派生类中重写的属性指定了不同的程序集。资源管理器的名称也是如此。
我希望这可以为您节省很多麻烦。如果您有任何问题或意见,请在下面留下。我总是喜欢阅读和回答它们。