在VisualStudio中提供运行时和设计时支持的WPF本地化解决方案

内容

介绍

背景

使用的代码

自动更新ObjectDataProvider

设计时支持

增加本地化字符串

添加更多的 Cultures

列举可用 Cultures

用户控件设计解决方案

限制

介绍

本文是以简单且可维护的方式解决WPF应用程序本地化问题。

在这种情况下,我还想:

  1. 在运行时切换区域设置——可自动更新所有得本地化元素
  2. 使用现有的可以在Visual Studio中维护的资源文件(**.resx files)。
  3. 对Expression Blend(还有其他XAML设计应用程序)提供设计时支持。

示例应用程序中需要有一些字符串来展示本地化功能,我将以最简短的方式来实现。

背景

关于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自动更新

为了在更改当前区域设置时自动更新元素,我正在利用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()方法将会失败。

UserControls设计时支持解决方案

在使用用户控件(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已更新后重新获取本地化后的值,这没什么区别。

原文发布于微信公众号 - 程序你好(codinghello)

原文发表时间:2018-07-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏林德熙的博客

在 Windows 下那些好用的调试软件

在开发 Windows 程序经常需要调试软件,本文介绍 Windows Sysinternals 的好用的工具。

31730
来自专栏张善友的专栏

ASP.NET 路由

ASP.NET 路由使您可以使用不必映射到网站中特定文件的 URL。由于 URL 不必映射到文件,所以可以在 Web 应用程序中使用 URL,这些 URL 是描...

24070
来自专栏Eugene's Blog

Python执行系统命令四种方法分类目录文章标签友情链接联系我们

16230
来自专栏七夜安全博客

经典栈溢出之MS060-040漏洞分析

18380
来自专栏开发与安全

linux网络编程之socket(八):五种I/O模型和select函数简介

一、五种I/O模型 1、阻塞I/O ? 我们在前面所说的I/O模型都是阻塞I/O,即调用recv系统调用,如果没有数据则阻塞等待,当数据到来则将数据从内核空间(...

30300
来自专栏H2Cloud

Boost ASIO proactor 浅析

Boost ASIO proactor 浅析 前情提要: Boost asio 的socket的异步非阻塞模式才有的是proactor模式,当IO操作介绍后回调...

52660
来自专栏java一日一条

29 个你必须知道的 Linux 命令

虽然Linux发行版支持各种各样的饿GUI(graphical user interfaces),但在某些情况下,Linux的命令行接口(bash)仍然是简单快...

11630
来自专栏圣杰的专栏

一张图理清ASP.NET Core启动流程

1. 引言 对于ASP.NET Core应用程序来说,我们要记住非常重要的一点是:其本质上是一个独立的控制台应用,它并不是必需在IIS内部托管且并不需要IIS来...

34470
来自专栏FreeBuf

技术讨论 | Windows 10进程镂空技术(木马免杀)

在Win10 x64环境下替换正常的进程,是一个比较高超的技术。使用该技术,可以内存执行病毒、木马。在文件层面实现免杀。可以把一个木马使用DES加密,放在资源里...

14310
来自专栏bboysoul

解决sudo add-apt-repository command not found

使用第三方软件源的时候发现这个问题 sudo: add-apt-repository: command not found 其实就是少安装了一个包而已

18320

扫码关注云+社区

领取腾讯云代金券