Be a programmer

Live with passion....
  首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

在运行时切换 WinForm 程序的界面语言 - part I

Posted on 2006-03-04 19:48  Programmer  阅读(3108)  评论(3编辑  收藏  举报

使用 .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 方法中,它通知所有已经注册的窗体控件改变界面显示元素。 使用这个方法来更改程序的界面显示语言,逻辑很清晰,非常容易让人理解。但有一个缺点,就是需要对现有程序做较大程度的改动。

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