C#实现动态多语言界面程序
一直想做一个多语言的程序,研究了一下.net的本地化方法,觉得做起来比较麻烦,而且不能快速切换,就自己琢磨着写一个。
以我做的一个C# winform 项目为例。
首先设计多语言文件,这里我用XML来保存,基本结构如下。
在Form里面,其每个子树分别对应一个窗体。XML每项有三个域,id 这个只是用来标号,程序中为用,key,value形成一个字典,key是控件
的名称,value是控件的text。在Dialog中key用数字编号。
做其他语言文件时,只用将value里面的值改成对应的语言即可。
当然,我们也不一定用XML来写语言文件,简单的ini文件也行。我现在觉得在一般的中小型程序中用XML,出于一个最基本的目的就是装B。
下面设计读取这个XML的类,
Forms, Menu, Toolbar, Dialog几个属性分别对应XML中的子树,使用.net中的Dictionary范型,Forms嵌套了一层Dictionary。
Load方法是加载语言文件,readLanguage 和paraseXML 函数对XML进行解析,并保存字符串到对应的属性中。
AddForm这个方法是将每个窗体的动态的添加到forms 里面。
在程序开始main 函数中,首先调用AddForm方法,添加所有窗体。
在每个Form的Load事件中初始化每个控件的Text。
递归更新每个控件Text
另外主窗体的Menu和Toolbar,我采用以下的方法更新。
需要转变为不同语言时只需要再调用一次Localization.Load方法。
小结:
这种实现多语言界面的方式我思考了很久,也在网上查了一些资料,最后设计了这样一种方式,XML中利用字典来记录控件的语言
在添加,读取时非常方便,Localization类做成静态类,在运行时就相当于一个常量,没有构造函数这样的开销,整个界面也可以再运行时
直接改变界面语言。
当然这种方法不一定是最好的, 如果有更好的方法欢迎指点。
以我做的一个C# winform 项目为例。
首先设计多语言文件,这里我用XML来保存,基本结构如下。
1
<?xml version = "1.0" encoding = "GB2312"?>
2
<AirControl language="简体中文">
3
<Menu>
4
<Project>
5
<Item id="0" key="MenuProject" value="项目(&P)" />
6
<Item id="1" key="MenuProjectItem1" value="新建(&N)" />
7
<Item id="2" key="MenuProjectItem2" value="打开(&O)" />
8
<Item id="3" key="MenuProjectItem3" value="保存(&S)" />
9
<Item id="5" key="MenuProjectItem5" value="退出(&X)" />
10
</Project>
11
<Manage>
12
<Item id="0" key="MenuManage" value="管理(&M)" />
13
<Item id="1" key="MenuManageItem1" value="登录(&I)" />
14
<Item id="2" key="MenuManageItem2" value="注销(&O)" />
15
<Item id="3" key="MenuManageItem3" value="修改密码(&C)" />
16
<Item id="4" key="MenuManageItem4" value="用户管理(&U)" />
17
</Manage>
18
<Help>
19
<Item id="0" key="MenuHelp" value="帮助(&H)" />
20
<Item id="1" key="MenuHelpItem1" value="帮助内容(&H)" />
21
<Item id="2" key="MenuHelpItem2" value="关于(&A)" />
22
</Help>
23
</Menu>
24
<Toolbar>
25
<Statusbar>
26
<Item id="1" key="StatusItem1" value="用户名: " />
27
<Item id="2" key="StatusItem2" value="用户组: " />
28
<Item id="3" key="StatusItem3" value="上次登录时间: " />
29
<Item id="4" key="StatusItem4" value="本次登录时间:" />
30
</Statusbar>
31
</Toolbar>
32
<Form>
33
<MainForm>
34
<Item id="0" key="MainForm" value="xx" />
35
<Item id="1" key="buttonGo" value="开始" />
36
<Item id="2" key="buttonStop" value="停止" />
37
<Item id="3" key="groupBox1" value="用户信息" />
38
<Item id="4" key="groupBox2" value="常规数据" />
39
</MainForm>
40
<UserLoginForm>
41
<Item id="0" key="UserLoginForm" value="用户登录" />
42
<Item id="1" key="labelTitle" value="xx" />
43
<Item id="2" key="labelUsername" value="用户名" />
44
<Item id="3" key="labelPassword" value="密码" />
45
<Item id="4" key="buttonLogin" value="登录" />
46
</UserLoginForm>
47
<ChangePasswordForm>
48
<Item id="0" key="ChangePasswordForm" value="修改密码" />
49
<Item id="1" key="label1" value="原密码" />
50
<Item id="2" key="label2" value="新密码" />
51
<Item id="3" key="label3" value="再输入" />
52
<Item id="4" key="buttonConfirm" value="确认" />
53
<Item id="5" key="buttonCancel" value="取消" />
54
</ChangePasswordForm>
55
</Form>
56
<Dialog>
57
<Title>
58
<Item id="0" key="0001" value="xx" />
59
<Item id="1" key="0002" value="添加测试" />
60
<Item id="2" key="0003" value="添加用户" />
61
<Item id="3" key="0004" value="修改密码" />
62
</Title>
63
<Message>
64
<Item id="0" key="0000" value="一切正常" />
65
<Item id="1" key="2001" value="用户名或密码错误" />
66
<Item id="5" key="2002" value="密码不一致" />
67
<Item id="6" key="2003" value="用户名已存在" />
68
<Item id="7" key="2004" value="添加用户成功" />
69
</Message>
70
</Dialog>
71
</AirControl>
这里是语言文件的局部,主体分为四个部分,Menu, Toolbar, Form 和 Dialog,分别对应菜单,工具栏,窗体和对话框的显示字符串。![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
2
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
3
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
4
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
5
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
6
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
7
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
8
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
9
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
10
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
11
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
12
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
13
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
14
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
15
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
16
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
17
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
18
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
19
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
20
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
21
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
22
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
23
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
24
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
25
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
26
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
27
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
28
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
29
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
30
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
31
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
32
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
33
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
34
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
35
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
36
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
37
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
38
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
39
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
40
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
41
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
42
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
43
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
44
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
45
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
46
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
47
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
48
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
49
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
50
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
51
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
52
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
53
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
54
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
55
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
56
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
57
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
58
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
59
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
60
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
61
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
62
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
63
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
64
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
65
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
66
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
67
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
68
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
69
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
70
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
71
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
在Form里面,其每个子树分别对应一个窗体。XML每项有三个域,id 这个只是用来标号,程序中为用,key,value形成一个字典,key是控件
的名称,value是控件的text。在Dialog中key用数字编号。
做其他语言文件时,只用将value里面的值改成对应的语言即可。
当然,我们也不一定用XML来写语言文件,简单的ini文件也行。我现在觉得在一般的中小型程序中用XML,出于一个最基本的目的就是装B。
下面设计读取这个XML的类,
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Text;
5
using System.Xml;
6![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
7
namespace AirLibrary
8
{
9
/// <summary>
10
/// 本地化类
11
/// </summary>
12
public static class Localization
13
{
14
Property //Property
18![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
19
Attribute //Attribute
25![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
26
Method //Method
66![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
67
Function //Function
116![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
117
Property //Property
158
}
159
}
160![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
这里我使用静态类来读取和保存,这样效率相对会高一些。读取XML时,我使用的是XmlReader,它使用流式读取,速度也比较快。![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
2
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
3
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
4
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
5
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
6
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
7
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
8
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
9
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
10
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
11
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
12
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
13
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
14
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
18
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
19
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
25
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
26
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
66
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
67
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
116
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
117
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif)
158
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
159
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
160
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
Forms, Menu, Toolbar, Dialog几个属性分别对应XML中的子树,使用.net中的Dictionary范型,Forms嵌套了一层Dictionary。
Load方法是加载语言文件,readLanguage 和paraseXML 函数对XML进行解析,并保存字符串到对应的属性中。
AddForm这个方法是将每个窗体的动态的添加到forms 里面。
在程序开始main 函数中,首先调用AddForm方法,添加所有窗体。
1 // 添加所有窗体用于本地化(按XML中顺序)
2 private static void AddForm()
3 {
4 Localization.AddForm("MainForm");
5 Localization.AddForm("UserLoginForm");
6 Localization.AddForm("UserManageForm");
7 Localization.AddForm("ChangePasswordForm");
8 }
然后加载语言文件。2 private static void AddForm()
3 {
4 Localization.AddForm("MainForm");
5 Localization.AddForm("UserLoginForm");
6 Localization.AddForm("UserManageForm");
7 Localization.AddForm("ChangePasswordForm");
8 }
1 if (!Localization.Load("zh"))
2 {
3 MessageBox.Show("无法加载语言配置文件, 将显示英文.", "错误", MessageBoxButtons.OK,
4 MessageBoxIcon.Exclamation);
5 Localization.HasLang = false;
6 }
7 else
8 Localization.HasLang = true;
2 {
3 MessageBox.Show("无法加载语言配置文件, 将显示英文.", "错误", MessageBoxButtons.OK,
4 MessageBoxIcon.Exclamation);
5 Localization.HasLang = false;
6 }
7 else
8 Localization.HasLang = true;
在每个Form的Load事件中初始化每个控件的Text。
1 if (Localization.HasLang)
2 RefreshLanguage();
2 RefreshLanguage();
// 更新窗体语言
public static void RefreshLanguage(Form form)
{
form.Text = Localization.Forms[form.Name][form.Name];
SetControlsLanguage(form, Localization.Forms[form.Name]);
}
public static void RefreshLanguage(Form form)
{
form.Text = Localization.Forms[form.Name][form.Name];
SetControlsLanguage(form, Localization.Forms[form.Name]);
}
递归更新每个控件Text
1 /// <summary>
2 /// 设置control子控件语言
3 /// </summary>
4 /// <param name="control">父控件</param>
5 /// <param name="obj">语言字典</param>
6 public static void SetControlsLanguage(Control control, Dictionary<string, string> obj)
7 {
8 foreach (Control ctrl in control.Controls)
9 {
10 // set the control which one's key in the dictionary
11 string text = "";
12 if (obj.TryGetValue(ctrl.Name, out text))
13 ctrl.Text = text;
14
15 if (ctrl.HasChildren)
16 SetControlsLanguage(ctrl, obj);
17 }
18 }
2 /// 设置control子控件语言
3 /// </summary>
4 /// <param name="control">父控件</param>
5 /// <param name="obj">语言字典</param>
6 public static void SetControlsLanguage(Control control, Dictionary<string, string> obj)
7 {
8 foreach (Control ctrl in control.Controls)
9 {
10 // set the control which one's key in the dictionary
11 string text = "";
12 if (obj.TryGetValue(ctrl.Name, out text))
13 ctrl.Text = text;
14
15 if (ctrl.HasChildren)
16 SetControlsLanguage(ctrl, obj);
17 }
18 }
另外主窗体的Menu和Toolbar,我采用以下的方法更新。
1 // Refresh the menu language
2 foreach (ToolStripMenuItem topItem in MainMenuStrip.Items)
3 {
4 topItem.Text = Localization.Menu[topItem.Name];
5 foreach (ToolStripItem item in topItem.DropDownItems)
6 {
7 if (item is ToolStripMenuItem)
8 {
9 string text = "";
10 if (Localization.Menu.TryGetValue(item.Name, out text))
11 item.Text = text;
12 }
13 }
14 }
15
16 // Refresh the statusbar language
17 foreach (ToolStripItem item in mainStatus.Items)
18 {
19 string text = "";
20 if (Localization.Toolbar.TryGetValue(item.Name, out text))
21 item.Text = text;
22 }
Dialog就直接调用Localization中的Dialog属性即可。2 foreach (ToolStripMenuItem topItem in MainMenuStrip.Items)
3 {
4 topItem.Text = Localization.Menu[topItem.Name];
5 foreach (ToolStripItem item in topItem.DropDownItems)
6 {
7 if (item is ToolStripMenuItem)
8 {
9 string text = "";
10 if (Localization.Menu.TryGetValue(item.Name, out text))
11 item.Text = text;
12 }
13 }
14 }
15
16 // Refresh the statusbar language
17 foreach (ToolStripItem item in mainStatus.Items)
18 {
19 string text = "";
20 if (Localization.Toolbar.TryGetValue(item.Name, out text))
21 item.Text = text;
22 }
需要转变为不同语言时只需要再调用一次Localization.Load方法。
小结:
这种实现多语言界面的方式我思考了很久,也在网上查了一些资料,最后设计了这样一种方式,XML中利用字典来记录控件的语言
在添加,读取时非常方便,Localization类做成静态类,在运行时就相当于一个常量,没有构造函数这样的开销,整个界面也可以再运行时
直接改变界面语言。
当然这种方法不一定是最好的, 如果有更好的方法欢迎指点。