视图模型这里出现了一个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类给出了数据的定义: 05 | public UILanguageDefn LanguageDefn |
07 | get { return _languageDefn; } |
10 | if (_languageDefn != value) |
12 | _languageDefn = value; |
13 | OnPropertyChanged( "LanguageDefn" ); |
14 | OnPropertyChanged( "HeadingFontSize" ); |
15 | OnPropertyChanged( "MinFontSize" ); |
16 | OnPropertyChanged( "IsRightToLeft" ); |
21 | public double HeadingFontSize |
25 | if (_languageDefn != null ) |
26 | return ( double )_languageDefn.HeadingFontSize; |
28 | return ( double )UILanguageDefn.DefaultHeadingFontSize; |
32 | public double MinFontSize |
36 | if (_languageDefn != null ) |
37 | return ( double )_languageDefn.MinFontSize; |
39 | return ( double )UILanguageDefn.DefaultMinFontSize; |
43 | public bool IsRightToLeft |
47 | if (_languageDefn != null ) |
48 | return _languageDefn.IsRightToLeft; |
MainWindowViewModel处在ViewModel架构最前端, 负责在MainWindowModel值发生变化时,更新CommonViewModel中的当前语言: 04 | public void RefreshUILanguage() |
06 | _model.UpdateLanguageData(); |
07 | CommonViewModel.Current.LanguageDefn = _model.CurrentLanguage; |
10 | if (LanguageChanged != null ) |
11 | LanguageChanged( this , new EventArgs()); |
视图正如我所提到的,本地化文本通过数据绑定显示到视图中。然而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 : 01 | public object Convert(object value, Type targetType, |
02 | object parameter, CultureInfo culture) |
04 | string key = parameter as string; |
05 | UILanguageDefn defn = value as UILanguageDefn; |
07 | if (defn == null || key == null ) return "" ; |
09 | return defn.GetTextValue(key); |
绑定现在我们获得了了一个 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 的命名空间): 1 | zlt;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 绑定域,并从构造器中实例化它 : 1 | public LocalisedTextExtension() |
3 | _lookupBinding = UITextLookupConverter.CreateBinding( "" ); |
而静态的CreateBinding方法定义在值转换器(value converter)中: 01 | public static Binding CreateBinding(string key) |
03 | Binding languageBinding = new Binding( "LanguageDefn" ) |
05 | Source = CommonViewModel.Current, |
06 | Converter = _sharedConverter, |
07 | ConverterParameter = key, |
09 | return languageBinding; |
所以定义好了Binding后,可以通过ConverterParameter参数来获取和设置Key属性的值。这也使得ProvideValue方法可以大展身手: 1 | public override object ProvideValue(IServiceProvider serviceProvider) |
3 | return _lookupBinding.ProvideValue(serviceProvider); |
而一个Binding是一个MarkupExtension,所以它有自己的可以调用的ProvideValue方法。
|