漫谈Silverlight(2)更加友好的国际化
Silverlight的国际化在4.0之后才逐渐让我们感到满意,例如提供了FlowDirection属性,更多的语言支持等等,本篇想与大家分享的是在多语言的Silverlight程序中动态切换语言的一些问题和经验。
在Silverlight中建立一个多语言的程序并不困难,与传统的多语言程序区别不大,通过建立不同语言的Resource文件切换语言,区别在于如何在Xaml中实现多语言,按照文档中的说明,我们要建立一个类像这样:
1: public class LocalizedStrings2: {3: public LocalizedStrings()
4: {5: }6: private static SilverlightApp.Resource1 resource1 = new SilverlightApp.Resource1();7: public SilverlightApp.Resource1 Resource1 { get { return resource1; } }8: }
为了能在Xaml中访问资源文件,我们要在Xaml中添加资源,例如在App.Resources中:
1: <Application.Resources>2: <local:LocalizedStrings xmlns:local ="clr-namespace:appNamespace"3: x:Key="LocalizedStrings" />4: </Application.Resources>
然后在Xaml中这样使用:
1: <Button Content="{Binding Path=Resource1.PressText, Source={StaticResource LocalizedStrings }}"/>
在这个过程中需要注意两点:一是对于Resource1.Designer.cs中生成的Resource1类的修饰符要改成public,默认为internal,而且当你添加一个新的项时修饰符会自动变回internal,这里我不得不说…(省略若干字);二是若要使上述代码生效还要对工程文件csproj进行修改,打开csproj文件找到SupportedCultures节(若没有就在PropertyGroup下添加),指定程序支持的语言,否则VS不会为我们生成相应的resource.dll
1: <?xml version="1.0" encoding="utf-8"?>2: <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">3: <PropertyGroup>4: <!-- ... -->
5: <SupportedCultures>zh-CN;en-US</SupportedCultures>6: </PropertyGroup>7: <PropertyGroup Condition="'$(MSBuildToolsVersion)' == '3.5'">8: <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>9: </PropertyGroup>10: <!-- ... -->
11: </Project>
只有这样修改后VS才会在编译生成的xap包中添加相应的语言目录,里面存放了各自语言的resource.dll。
这里已经简单说明了Silverlight文档中的建议做法,您觉得这种方式方便吗?
首先是在Xaml中的引用如此的累赘,暴长的引用方式,使我们的Xaml文件又增加了很多字节,其次是死板,假如我现在想在每个标题后面加上一个冒号“:”,我该怎么做呢,最差的做法是直接改动Resource文件,那么如果程序的其它地方也需要这个字符串而不需要加冒号怎么办?有人抬杠说在Resource中加两项,一个有冒号一个没冒号,呵呵,这显然不是一种好做法;还有人说可以在后面再加一个TextBlock只有一个冒号,这是一个可行的做法,可是还不够友好,如果我要加括号是不是在前后各加一个TextBlock呢?
其次是当我动态切换语言选项的时候,已经显示的UI并不会自动切换到新的语言,很显然,上面的例子只是将Button的Content单向绑定到了Resource1.PressText,有人会说那就改成这样:
1: <Button Content="{Binding Path=Resource1.PressText, Source={StaticResource LocalizedStrings, Mode=TwoWay}}"/>
但是这样并没有用,UI依然不会自动切换,因为Resource1并没有实现INotifyPropertyChanged接口,UI是无法得到通知的。
我们能不能统一处理这些问题呢?现在的问题在于在从Resource中读取相应的语言字符串并绑定到UI的过程中我们并没有机会干预,那我们能不能在中间加一层干预一下呢?分享一下我的做法:
1: public abstract class StringResourceBase : INotifyPropertyChanged, IDisposable2: {3: private static List<StringResourceBase> srList;4: private static List<StringResourceBase> SrList5: {6: get
7: {8: if (srList == null)9: srList = new List<StringResourceBase>();
10: return srList;
11: }12: }13: public static void Refresh()14: {15: foreach (var sr in SrList)16: sr.OnPropertyChanged(null);
17: }18: private ResourceManager manager;
19: protected StringResourceBase(ResourceManager manager)
20: {21: if (!SrList.Contains(this))22: SrList.Add(this);
23: this.manager = manager;
24: }25: public ResourceManager Manager
26: {27: get
28: {29: return this.manager;30: }31: }32: public virtual string this[string name]33: {34: get
35: {36: if (name.EndsWith(":"))37: return (GetString(name.TrimEnd(':'),
38: Thread.CurrentThread.CurrentUICulture) ?? String.Empty) + ":";
39: return GetString(name, Thread.CurrentThread.CurrentUICulture);
40: }41: }42: public virtual string GetString(string name, CultureInfo culture)43: {44: return manager.GetString(name,
45: culture ?? Thread.CurrentThread.CurrentUICulture);46: }47: public event PropertyChangedEventHandler PropertyChanged;48: protected virtual void OnPropertyChanged(string name)49: {50: if (this.PropertyChanged != null)51: this.PropertyChanged(this, new PropertyChangedEventArgs(name));52: }53: public void Dispose()54: {55: if (SrList.Contains(this))56: SrList.Remove(this);
57: }58: }
这是一个抽象基类,在构造函数中需要将生成的ResourceManager类传入,这个类做的事情很简单,把传入的ResourceManager缓存起来,并提供Refresh的静态方法来触发所有缓存的ResourceManager的PropertyChanged事件,提供GetString方法以及[string name]的字符串索引,在索引程序中我们特殊处理了冒号,那我们如何使用它呢,很简单,只需要在自己的程序集中指定一个类继承它,把该程序集的ResourceManager传入即可,这样做是考虑到多个类库的情况,每个类库会有自己的resource文件:
1: public class SR : StringResourceBase2: {3: public SR()
4: : base(StringResource.ResourceManager)
5: {6: }7: }
然后我们在Xaml中这样来使用,在App中指定SR:
1: <Application.Resources>2: <local:SR x:Key="SR" xmlns:local="clr-namespace:LocalizationDemo"></local:SR>3: </Application.Resources>
然后可以使用了:
1: <StackPanel x:Name="LayoutRoot" Background="White">2: <Button Content="英文" Name="en" Click="en_Click"></Button>3: <Button Content="中文" Name="cn" Click="cn_Click"></Button>4: <TextBlock Text="{Binding Path=[UserName],Source={StaticResource SR}}"></TextBlock>5: <TextBlock Text="{Binding Path=[Password:],Source={StaticResource SR}}"></TextBlock>6: </StackPanel>
注意这里的“Password:”带了冒号在UI上就会显示带冒号,并且这里我们利用的是Silverlight4的新特性,在Binding中可以使用字符串索引,用方括号包含的就是索引的Name,这个真的是很实用的功能,早就该提供啦,呵呵:)
后台的代码如下:
1: private void en_Click(object sender, RoutedEventArgs e)2: {3: System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");4: System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");5: StringResourceBase.Refresh();6: }7: private void cn_Click(object sender, RoutedEventArgs e)8: {9: System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("zh-CN");10: System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("zh-CN");11: StringResourceBase.Refresh();12: }
当我们切换语言的时候,UI会自动的切换,并且对于格式化等特殊处理,比如冒号、括号还是其他需求,你可以在Base类中的索引代码的位置自由发挥。希望本文对您有帮助。
转载请遵循此协议:署名 - 非商业用途 - 保持一致