设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

构建多语言的WPF应用

2014-7-9 10:25| 发布者: joejoe0332| 查看: 5974| 评论: 0|原作者: 徐继开, TimBao, chasehong, 无若, 0x0bject, 请叫我益达张, 卖茄子, 地狱星星|来自: oschina

摘要: 在WPF应用程序中搭建多语言支持(Multilingual Support)是我最近在做的一件事,对于不使用英语的人士而言,此举提高了程序的可用性。实现起来要完成以下目标:…… ...


视图模型

这里出现了一个MVVM实现方法,它不同于WPF和Silverlight应用程序中的Model-View-Presenter(MVP).在MVP模式中,我们需要一个Presenter把当前语言的定义(或单个的本地化后的文本)传给视图(View),由视图负责UI中文本的显示与更新。考虑到我们在使用WPF,文本的更新可以很容易地通过数据绑定来实现;考虑到语言定义要在整个应用程序(组件或窗体)中使用,我们需要一个共享类来保存当前语言属性,这样当进行数据绑定时,就能使该属性在UI的任何一部分检索出来。

在MVVM模式中,这个共享类同其他视图模型(例如MainWindowViewModel)一道,将成为组成视图模型层的一部分。CommonViewModel这个类是作为单例模式(Singleton)来实现,这样静态实例属性Current就可以作为一个绑定的源属性来赋值了。非静态属性则通过绑定的Path属性来引用。还有一点很重要,ViewModel实现了INotifyPropertyChanged的接口,以致UI能在源数值发生改变时自动更新绑定。

这里是绑定到UI的CommonViewModel属性,UILanguageDefn类给出了数据的定义:

01/// <summary>
02/// Gets or sets the language definition used by the entire interface.
03/// </summary>
04/// <value>The language definition.</value>
05public UILanguageDefn LanguageDefn
06{
07    get { return _languageDefn; }
08    set
09    {
10        if (_languageDefn != value)
11        {
12            _languageDefn = value;
13            OnPropertyChanged("LanguageDefn");
14            OnPropertyChanged("HeadingFontSize");
15            OnPropertyChanged("MinFontSize");
16            OnPropertyChanged("IsRightToLeft");
17        }
18    }
19}
20 
21public double HeadingFontSize
22{
23    get 
24    {
25        if (_languageDefn != null)
26            return (double)_languageDefn.HeadingFontSize;
27 
28        return (double)UILanguageDefn.DefaultHeadingFontSize;
29    }
30}
31 
32public double MinFontSize
33{
34    get
35    {
36        if (_languageDefn != null)
37            return (double)_languageDefn.MinFontSize;
38 
39        return (double)UILanguageDefn.DefaultMinFontSize;
40    }
41}
42 
43public bool IsRightToLeft
44{
45    get
46    {
47        if (_languageDefn != null)
48            return _languageDefn.IsRightToLeft;
49 
50        return false;
51    }
52}

MainWindowViewModel处在ViewModel架构最前端, 负责在MainWindowModel值发生变化时,更新CommonViewModel中的当前语言:

01/// <summary>
02/// Refreshes the UI text to display in the current language.
03/// </summary>
04public void RefreshUILanguage()
05{
06    _model.UpdateLanguageData();
07    CommonViewModel.Current.LanguageDefn = _model.CurrentLanguage;
08 
09    //Notify any other internal logic to prompt a refresh (as necessary)
10    if (LanguageChanged != null)
11        LanguageChanged(thisnew EventArgs());
12}

视图

正如我所提到的,本地化文本通过数据绑定显示到视图中。然而WPF自身并不知道如何处理UILanguageDefn类,更不用说提取合适的本地化文本值。这也是最后一个难题。

值转换器

请记住,CommonViewModel.Current.LanguageDefn是一个UILaunguageDefn,不是TextBlock的Text属性期待的一个字符串。因此,此时需要一个值转换器来完成这项转换工作。这个值转换器使用ConverterParameter来指定创建查找关键字,用来恢复来自UILanguage实例中局部符合条件的文本。记住,当接口改变了,UILanuageDefn也改变。

这项工作的优点在于对每一段局限在接口当中的文本,符合条件的元素需要被添加到language XML文件,确保ConverterParameter和元素名称匹配。此外不需要定义任何额外的属性——不管是在视图层,UILanguageDefn,还是在模型层的其他部分。

这个converter相对简单. 只需在类级别上指定 IValueConverter (在System.Windows.Data中)的 ValueConversion 属性:

1[ValueConversion(typeof(UILanguageDefn), typeof(string))]

并且实现类似如下的函数 Convert :

01public object Convert(object value, Type targetType, 
02                      object parameter, CultureInfo culture)
03{
04    string key = parameter as string;
05    UILanguageDefn defn = value as UILanguageDefn;
06 
07    if (defn == null || key == nullreturn "";
08 
09    return defn.GetTextValue(key);
10}

绑定

现在我们获得了了一个 value converter, 我们可以将它放置在一个 Binding 表达式中:

1<TextBlock Text="{Binding Path=LanguageDefn,
2    Converter={StaticResource UIText}, ConverterParameter=ApplyLabel,
3    Source={x:Static vm:CommonViewModel.Current}}" />

如果想要它工作, 这个 XML 的 命名空间必须设置为 vm(指向 ViewModel的命名空间),并且 UIText 的资源需要被定义 (假设conv 是这个 value converter 的 XML 的命名空间):

1zlt;conv:UITextLookupConverter x:Key="UIText" />

简单明了——自定义标记扩展

如果你当前的状态(像我一样)又想要愉快的方式,在大多数的XAML文件中的长绑定表达式里,你发现它变得乏味,是同一样东西的重复。甚至不考虑重命名类或者把属性作为重构的一部分!

当然,有一种方式能使其更简洁,考虑到这些绑定之间的唯一变化就是ConverterParameter。解决方案是使用使用自定义标记扩展。

为了做到这一点,自定义标记扩展是一个简单的类,它派生自MarkupExtension(在System.Windows.Markup),按照惯例被命名为[name]Extension。在其核心处,关键点是需要重载ProvideValue方法。但是这该怎么做呢?

自定义标记拓展的重点就是在XAML中写下类似这样的代码:

1<TextBlock Text="{ext:LocalisedText Key=ApplyLabel}" />

因此,自定义拓展被称作LocalisedTextExtension,并添加一个Key,它的类型是public string.因为在后台中,绑定一直处于使用状态,所以我创建了一个private 绑定域,并从构造器中实例化它 :

1public LocalisedTextExtension()
2{
3    _lookupBinding = UITextLookupConverter.CreateBinding("");
4}

而静态的CreateBinding方法定义在值转换器(value converter)中:

01public static Binding CreateBinding(string key)
02{
03    Binding languageBinding = new Binding("LanguageDefn")
04    {
05        Source = CommonViewModel.Current,
06        Converter = _sharedConverter,
07        ConverterParameter = key,
08    };
09    return languageBinding;
10}

所以定义好了Binding后,可以通过ConverterParameter参数来获取和设置Key属性的值。这也使得ProvideValue方法可以大展身手:

1public override object ProvideValue(IServiceProvider serviceProvider)
2{
3    return _lookupBinding.ProvideValue(serviceProvider);
4}

而一个Binding是一个MarkupExtension,所以它有自己的可以调用的ProvideValue方法。


酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部