使用 .net 开发一个支持多语言的 WinForm 应用程序是很方便的。特别是在 .net framework 2.0 中,Visual Studio 2005 做了进一步的改进。它默认地为每个资源文件(.resx)添加一个叫 ResXFileCodeGenerator 的自定义工具来简化读取资源的操作。我们常用的一些软件,比如 BitComet、Skype 等都可以在菜单中设置界面显示的语言。那么如果要在我们的 .net 程序中实现这个功能该怎么办呢? 

在 .net 程序中,多语言的支持是以 satellite assemblies 的形式实现的,只要在相应的位置存在某种语言的 satellite assembly 即可。而在 Visual Studio 中,只需要为每个资源文件添加相应语言的资源文件就可以了。比如:Resources.resx 对应的简体中文的资源文件叫 Resources.zh-CHS.resx,英语的叫 Resources.en.resx。至于如何生成合适的 satellite assembly,就交给 Visual Studio 来完成了。 

在程序运行的时候,到底使用哪个资源文件,是由 Thread.CurrentThread.CurrentUICultrue 来决定的。我们暂且将需要用到资源文件的地方分为两类:一是需要用到的时候才从资源文件读取的资源,比如使用 MessageBox 弹出的对话框中的消息内容等;另一类是窗体各个控件上显示的文字,比如 Label 控件的 Text 属性等。要改变程序使用的界面语言,首先必须改变 CurrentUICultrue 属性的值。我们可以发现,在改变了 CurrentUICultrue 的值后,第一类是没有问题。但是第二类就没有任何变化,窗体上各个控件显示的文字仍然没有变化。下面我们主要讨论第二类,也就是如何改变窗体控件的显示。我们均以 Label 控件为例进行说明。 

方法一:
Label 控件在被创建好以后再改变 CurrentUICultrue,由于并没有任何用于处理当前线程的界面语言被改变的事件,也就是说此 Label 控件没有任何机会知道当前程序的界面语言已经被更改,因此 Label 控件的显示也不会有任何影响。既然是因为 Label 控件不知道 CurrentUICulture 已经改变了,那我们就想办法在改变 CurrentUICulture 的时候通知 Label 控件。这也使我们很自然地想到了这种情况和 Observer 模式极为相似。 

// Interface IObserver 
 public interface IObserver 
 { 
 void UpdateUI(); 
 }  // Class CultureManager 
 public sealed class CultureManager 
 { 
 private List<IObserver> _observers; 
 private static readonly CultureManager _instance = new CultureManager(); 
 private static readonly object _locker = new object(); private CultureManager() 
 { 
 _observers = new List<IObserver>(); 
 } public static CultureManager Instance 
 { 
 get { return _instance; } 
 } public CultureInfo CurrentUICulture 
 { 
 get { return Thread.CurrentThread.CurrentUICulture; } 
 set 
 { 
 if( value==null ){ 
 throw new ArgumentNullException( "value" ); 
 } CultureInfo current = Thread.CurrentThread.CurrentUICulture; 
 if (current.LCID != value.LCID) { 
 Thread.CurrentThread.CurrentUICulture = value; 
 Notify(); 
 } 
 } 
 } public void Attach(IObserver observer) 
 { 
 if (observer == null) { 
 throw new ArgumentNullException("observer"); 
 } lock (_locker) { 
 _observers.Add(observer); 
 } 
 } public void Detach(IObserver observer) 
 { 
 if (observer == null) { 
 throw new ArgumentNullException("observer"); 
 } lock (_locker) { 
 _observers.Remove(observer); 
 } 
 } public void Notify() 
 { 
 lock (_locker) { 
 foreach (IObserver observer in _observers) { 
 observer.UpdateUI(); 
 } 
 } 
 } 
 }  // Class LanguagedLabel 
 public sealed class LanguagedLabel : Label, IObserver 
 { 
 private string _resourceKey; public LanguagedLabel() 
 { 
 CultureManager.Instance.Attach(this); 
 } public string ResourceKey 
 { 
 get{ return _resourceKey; } 
 set 
 { 
 if( value==null || value.Length==0 ){ 
 throw new ArgumentNullException("value"); 
 } if( string.Compare(value, _resourceKey )!=0 ){ 
 _resourceKey = value; 
 UpdateUI(); 
 } 
 } 
 } public void UpdateUI() 
 { 
 ResourceManager rm; 
 // Creates an instance of ResourceManager class. Text = rm.GetString( _resourceKey ); 
 } 
 }

上面这个方法是通过改变 CultureManager 类的 CurrentUICulture 属性值来达到更改程序的界面语言的目的的。在这个属性的 setter 方法中,它通知所有已经注册的窗体控件改变界面显示元素。使用这个方法来更改程序的界面显示语言,逻辑很清晰,非常容易让人理解。但有一个缺点,就是需要对现有程序做较大程度的改动。 

稍后再介绍另外一个方法。如果大家有什么更好的方法,也请多多交流哦! :)