介绍
背景
使用的代码
自动更新ObjectDataProvider
设计时支持
增加本地化字符串
添加更多的 Cultures
列举可用 Cultures
用户控件设计解决方案
限制
本文是以简单且可维护的方式解决WPF应用程序本地化问题。
在这种情况下,我还想:
示例应用程序中需要有一些字符串来展示本地化功能,我将以最简短的方式来实现。
关于WPF本地化问题有许多其他的文章,包括使用Locbaml本地化WPF应用程序(https://www.codeproject.com/KB/WPF/Article.aspx),其中介绍了对XAML文件进行本地化的不同方法,每种方法都有各自的优缺点。
这篇文章中的第一种方法(不使用LocBaml.exe的目标本地化,https://www.codeproject.com/KB/WPF/Article.aspx)给了我很多启发,但为了在运行时自动更新元素(对于所有元素,甚至是现有的Windows),我不得不寻找其它的解决方案。
另一个较早的项目是WPF “WPF Multi-Lingual at Runtime”(https://www.codeproject.com/KB/WPF/Article.aspx),它确实为运行时自动更新提供了解决方案,但在我看来,它对语言资源文件的管理太复杂了。在本文中,通过支持使用现有资源文件( *.resx files),我们仍然可以轻松地将现有应用程序转换为新的区域设置。
我完全推荐阅读和理解上面这两篇文章,其中有很多信息,以及在其他项目中帮助我很多的有用技巧。
为了在更改当前区域设置时自动更新元素,我正在利用ObjectDataProvider提供的功能特性。
来自MSDN页面的说明:
“当您想用另一个对象替换当前绑定源对象并更新所有关联的绑定时,这个类也很有用。”
因此,我们需要做的就是替换(或刷新)ObjectDataProvider对象实例,并且ODP属性上的任何绑定都将自动更新。
这就是这个多语言支持解决方案的改进之处。
虽然可以直接绑定到自动生成的RESX designer类的属性(Resources.Designer.cs),但我们还需要为ODP获得该类的一个实例。
所有的 ResXFileCodeGenerators (默认和自定义)都将资源 Resources 构造函数标记为 internal(内部的),这意味着只能从同一程序集中访问它。(这同时也意味着无法从XAML文件中去访问构造函数——即使用ODP ObjectType)。要解决这个问题,我们可以在ODP上使用MethodName属性。(另一种选择可能是扩展现有的自定义ResXFileCodeGenerator来标记构造函数为public,但不需要这样做)
…的方法:
使用MethodName意味着ODP将成为方法返回的对象,允许我们绑定Resources类的实例。我们可以创建这个实例,因为上面对内部构造函数的调用来自同一个程序集中,而不是直接来自XAML。
这样做的一个限制是资源类必须是公共的,因为我们不能使用公共方法返回一个内部类的实例(这会导致编译错误)。这意味着我们可以使用扩展的强类型资源发生器[^]在Visual Studio 2005和2008,或附带的PublicResXFileCodeGenerator工具Visual Studio 2008。
我喜欢扩展的代码生成器,因为它生成了非常有用的字符串格式化方法。
更新当前的区域设置(Culture)非常简单,我添加了一个方法来CultureResources类更新当前资源文件和ObjectDataProvider触发一个更新,导致调用GetResourceInstance方法,更新ODP ObjectInstance,刷新任何绑定在ODP上元素——更新到新的资源文件。
在设计时,Properties.Resources。区域设置最初设置为项目中的使用默认语言集,如果没有设置默认语言,则使用当前线程的区域设置。
您想要本地化的所有字符串都需要在所有资源文件中定义,以便本地化工作,因此,在使用默认资源设置所有内容之后,通常会更容易添加更多的区域性.resx文件。否则,您将需要向所有现有的RESX文件添加每个新字符串。
然后,我们可以向所需的UI元素添加绑定:
如果我们将这个资源字符串添加到默认资源RESX文件中,在重新编译项目之后,这个默认的字符串值现在应该出现在设计器中,当然,在运行应用程序时也是如此。
如果您看到除了默认值之外为资源文件添加的字符串似乎总是显示默认值,那么请检查每个RESX文件中的资源字符串名称是否正确。如果有绑定错误,那么绑定中的路径集不匹配任何RESX文件中的任何字符串,而且它甚至不能像以前那样返回默认的RESX值。
向项目中添加另一种区域设置的简单方法是复制和粘贴默认资源文件。在Visual Studio中创建一个新文件的resx文件。从MSDN MSDN CultureInfo 列表中(http://msdn2.microsoft.com/en-us/library/system.globalization.cultureinfo.aspx)选择合适的区域文件名字。在资源文件扩展名中添加区域性代码,如在Resources.Fr-fr.resx,在编译应用程序时,Visual Studio将使用它创建本地化的DLL。
现在您已经有了一个新的RESX文件,您可以更改区域资源文件的资源值,这样新的区域(Resources.Fr-fr.resx)设置就添加完成了。
通过向这个项目添加一些区域设置,可以演示用于枚举我们实现的那些区域性的代码。我这样做是为了避免在添加新区域设置时需要重新构建应用程序。
对于现有的已安装的程序,您只需要创建一个带有新的区域设置名称的文件夹,并将新的正确命名的资源DLL放入其中。重新启动应用程序,它就能列出系统中可选择的区域设置列表。
以上是检查任何与区域性名称匹配的文件夹的application bin目录的一种相对快速的方法。如果字符串参数不匹配任何已定义的CultureInfo类型,CultureInfo.GetCultureInfo()方法将会失败。
在使用用户控件(UserControl)本地化支持时,我遇到了一个问题。如果您想要本地化的属性是在用户控件之外(作为依赖项属性添加到代码隐藏文件中)是可访问的,那么没有问题,您可以按照上面描述的那样本地化它们。但是,如果您希望本地化的属性不是外部可访问的,比如Label Content属性,那么解决方案就有点麻烦了。
当您在UserControl中添加一个绑定到一个标签时,它将在运行时被正确地显示出来,在设计时(例如在Blend中),当它被自己加载时也会被正确展示。不幸的是,当您加载包含UserControl的窗口时,它将无法展示出来。(这似乎只是Blend工具的一个问题,在这种情况下,Visual Studio 2008设计器能够正常展示。)
我理解,将UserControl作为窗口的子控件加载时的问题是,设计器创建控件的实例,然后将其添加到窗口中。运行时可用的资源不存在,因为实例不是在窗口中创建的,因此上面的绑定失败,无法呈现控件。在多次尝试解决这种情况的失败之后,我最终得出了以下结论:
使用DesignerProperties.GetIsInDesignMode()意味着该代码只在设计时执行,而它所做的只是将包含我们的Resources ObjectDataProvider的ResourceDictionary添加到设计器本身,以便在初始化UserControl时可以使用它们。这实际上是ODP的第二个实例,在运行时会很糟糕(因为只有App.xaml中包含的第一个实例会被更新),但在设计时很好,因为我们不会更新区域性。
问题解决了。
在本例中,我使用WPF绑定,这需要依赖属性来绑定。在其他情况下,您可能希望访问这些属性,但是添加绑定并不合适,也不容易实现。例如,当您希望直接从代码访问本地化的值时。为了在这种情况下保持自动更新工作,您可以在ObjectDataProvider DataChanged事件上连接一个eventhandler,该事件是在我们更新ODP后触发的。因此,当在eventhandler中重新获取值时,更新的资源值是可用的。或者,您可以确保在知道ODP已更新后重新获取本地化后的值,这没什么区别。