使用 .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 方法中,它通知所有已经注册的窗体控件改变界面显示元素。 使用这个方法来更改程序的界面显示语言,逻辑很清晰,非常容易让人理解。但有一个缺点,就是需要对现有程序做较大程度的改动。
稍后再介绍另外一个方法。如果大家有什么更好的方法,也请多多交流哦! :)