终于有空整理下多语言实现思路。查阅已有方案,有用不同resx文件的,有每个控件动态设置的,有用反射去整的,颇为繁琐。

结合项目中实现方法,并做简化,实现通用的多语言切换方案,以做备忘。

它支持语言自定义添加与扩充,灵活易用,更易于维护。它以xml格式存储语言信息,支持自定义语言、ToolTip等字串,支持即时切换。

一、语言格式:

每种语言对应一个xml格式文件,比如中文为Chinese.lng,英文为English.lng,置于程序运行目录之Languages文件夹下,其存放位置可自定义。

本欲以反射获取已有程序集之字串,却未有成功,因为字串值初始化在InitializeComponent()中动态设置,反射不出来这些属性值,所以手动提取,以NotePad++做批量处理,亦是方便。

文件分为自定义字串区(strings节点)、窗体语言文件区(forms节点),结构如下图示:

  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <language Id="CHS" Name="Chinese" LocaleName="中文(简体)" LangID="0x0409" CodePage="1252">
  3. <strings>
  4. <INF_AppName>记事本</INF_AppName>
  5. <INF_NoTitle>无标题</INF_NoTitle>
  6. <INF_Information>提示</INF_Information>
  7. <INF_CannotFound>找不到 "{0}"</INF_CannotFound>
  8. <INF_ContentChanged>文件内容已被改变,要保存吗?</INF_ContentChanged>
  9. </strings>
  10. <forms>
  11. <MainForm Text="记事本">
  12. <miFile Text="文件(&amp;F)" />
  13. <miNew Text="新建(&amp;N)" />
  14. <miOpen Text="打开(&amp;0)..." />
  15. <miSave Text="保存(&amp;S)" />
  16. <miSaveAs Text="另存为(&amp;A)..." />
  17. <miPageSet Text="页面设置(&amp;U)..." />
  18. <miPrint Text="打印(&amp;P)..." />
  19. <miExit Text="退出(&amp;X)" />
  20. ...
  21. <miHelp Text="帮助(&amp;H)" />
  22. <miHelpTopic Text="帮助主题(&amp;H)" />
  23. <miLanguage Text="语言" />
  24. <miAbout Text="关于记事本(&amp;A)" />
  25. </MainForm>
  26. <GotoLineForm Text="跳转到指定行">
  27. <lblLine Text="行数(&amp;L):" />
    <txtLine ToolTip="请输入您想要跳转到的行号:" />
  28. <btnOk Text="确定" />
  29. <btnCancel Text="取消" />
  30. </GotoLineForm>
  31. <SearchForm Text="查找">
  32. <btnCancel Text="取消" />
  33. <btnSearch Text="查找下一个(&amp;F)" />
  34. <lblContent Text="查找内容(&amp;N):" />
  35. <cbCaseSensitive Text="区分大小写" />
  36. <gbDirection Text="方向" />
  37. <rbDown Text="向下" />
  38. <rbUp Text="向上" />
  39. </SearchForm>
  40. </forms>
  41. </language>

二、解析与加载类

仍然对每个窗体做遍历,加载对应字串,提取语言文件语种信息。

此文件是个完整的语言管理器类,且可根据需要自行扩充,列码如下:

  1. //多语言管理类
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Windows.Forms;
  6. using System.Xml;
  7.  
  8. namespace Notepad
  9. {
  10. //单个语言项
  11. public class LanguageItem
  12. {
  13. private const string RootNodeName = "language";
  14. private const string StringsNodeName = "strings";
  15. private const string FormsNodeName = "forms";
  16.  
  17. private XmlNode stringsNode;
  18. private XmlNode formsNode;
  19.  
  20. private string id;
  21. private string name;
  22. private string localeName;
  23.  
  24. public LanguageItem(string fileName)
  25. {
  26. var xmlDoc = new XmlDocument();
  27. xmlDoc.Load(fileName);
  28. var root = xmlDoc.DocumentElement;
  29. if (root != null)
  30. {
  31. this.id = GetAttributeValue(root, "Id");
  32. this.name = GetAttributeValue(root, "Name");
  33. this.localeName = GetAttributeValue(root, "LocaleName");
  34.  
  35. this.stringsNode = root.SelectSingleNode(StringsNodeName);
  36. this.formsNode = root.SelectSingleNode(FormsNodeName);
  37. }
  38. }
  39.  
  40. public string Id
  41. {
  42. get { return this.id; }
  43. }
  44.  
  45. public string Name
  46. {
  47. get { return this.name; }
  48. }
  49.  
  50. public string LocaleName
  51. {
  52. get { return this.localeName; }
  53. }
  54.  
  55. private void ApplyLanguage(Control control, XmlNode formNode, ToolTip toolTip = null)
  56. {
  57. if (string.IsNullOrEmpty(control.Name))
  58. return;
  59.  
  60. var ctrlNode = formNode.SelectSingleNode(control.Name);
  61. if (ctrlNode != null)
  62. {
  63. control.Text = GetAttributeValue(ctrlNode, "Text");
  64. string tips = GetAttributeValue(ctrlNode, "ToolTip");
  65. if (!string.IsNullOrEmpty(tips) && toolTip != null)
  66. toolTip.SetToolTip(control, tips);
  67. }
  68. //弹出菜单
  69. if (control.ContextMenuStrip != null)
  70. ApplyMenuLanguage(control.ContextMenuStrip, formNode);
  71. foreach (Control ctrl in control.Controls)
  72. ApplyLanguage(ctrl, formNode, toolTip);
  73. //菜单项,特别遍历
  74. if (control is ToolStrip)
  75. {
  76. foreach (ToolStripItem toolItem in (control as ToolStrip).Items)
  77. ApplyMenuLanguage(toolItem, formNode);
  78. }
  79. }
  80.  
  81. private void ApplyMenuLanguage(ToolStrip menu, XmlNode formNode)
  82. {
  83. foreach (ToolStripItem toolItem in menu.Items)
  84. ApplyMenuLanguage(toolItem, formNode);
  85. }
  86.  
  87. private void ApplyMenuLanguage(ToolStripItem menuItem, XmlNode formNode)
  88. {
  89. if (string.IsNullOrEmpty(menuItem.Name))
  90. return;
  91.  
  92. var itemNode = formNode.SelectSingleNode(menuItem.Name);
  93. if (itemNode != null)
  94. {
  95. menuItem.Text = GetAttributeValue(itemNode, "Text");
  96. menuItem.ToolTipText = GetAttributeValue(itemNode, "ToolTip");
  97. }
  98. if (menuItem is ToolStripDropDownItem)
  99. {
  100. foreach (ToolStripItem item in (menuItem as ToolStripDropDownItem).DropDownItems)
  101. ApplyMenuLanguage(item, formNode);
  102. }
  103. }
  104.  
  105. public bool LoadLanguageRes(ContainerControl cc)
  106. {
  107. if (cc == null || formsNode == null || !formsNode.HasChildNodes || formsNode.SelectSingleNode(cc.Name) == null)
  108. return false;
  109.  
  110. //创建ToolTip控件, 以支持ToolTip显示
  111. var toolTip = new ToolTip();
  112. var formNode = formsNode.SelectSingleNode(cc.Name);
  113. cc.Text = GetAttributeValue(formNode, "Text");
  114. foreach (Control ctrl in cc.Controls)
  115. ApplyLanguage(ctrl, formNode, toolTip);
  116. return true;
  117. }
  118.  
  119. public bool LoadLanguageRes(ContainerControl cc, ToolStrip menu)
  120. {
  121. if (cc == null || formsNode == null || !formsNode.HasChildNodes || formsNode.SelectSingleNode(cc.Name) == null)
  122. return false;
  123.  
  124. var formNode = formsNode.SelectSingleNode(cc.Name);
  125. ApplyLanguage(menu, formNode);
  126. return true;
  127. }
  128.  
  129. private string GetAttributeValue(XmlNode xmlNode, string attrName)
  130. {
  131. if (xmlNode.Attributes != null && xmlNode.Attributes[attrName] != null)
  132. return xmlNode.Attributes[attrName].Value;
  133. return string.Empty;
  134. }
  135.  
  136. public string GetText(string textID, string defaultText = "")
  137. {
  138. if (stringsNode == null || !stringsNode.HasChildNodes)
  139. return defaultText;
  140.  
  141. foreach (XmlNode node in stringsNode.ChildNodes)
  142. if (node.Name.Equals(textID))
  143. return node.InnerText;
  144.  
  145. return defaultText;
  146. }
  147. }
  148.  
  149. public class LanguageList : List<LanguageItem>
  150. {
  151. }
  152.  
  153. /// <summary>
  154. /// 多语言管理器
  155. /// </summary>
  156. public static class ML
  157. {
  158. private static LanguageItem activeLanguage;
  159. private static LanguageList languages;
  160.  
  161. //最初调用
  162. public static int LoadLanguages(string searchPattern, string defaultLanguageId = "")
  163. {
  164. string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Languages");
  165. return LoadLanguages(path, searchPattern, defaultLanguageId);
  166. }
  167.  
  168. public static int LoadLanguages(string path, string searchPattern, string defaultLanguageId = "")
  169. {
  170. languages = new LanguageList();
  171. if (!Directory.Exists(path))
  172. return ;
  173.  
  174. var files = Directory.GetFiles(path, searchPattern);
  175. foreach (string file in files)
  176. languages.Add(new LanguageItem(file));
  177. if (!string.IsNullOrEmpty(defaultLanguageId))
  178. LoadLanguageById(defaultLanguageId);
  179.  
  180. return languages.Count;
  181. }
  182.  
  183. public static string ActiveLanguageId
  184. {
  185. get { return (activeLanguage != null) ? activeLanguage.Id : string.Empty; }
  186. }
  187.  
  188. public static string[] LanguageLocalNames
  189. {
  190. get
  191. {
  192. if (languages == null || languages.Count == )
  193. return new string[];
  194. var names = new string[languages.Count];
  195. for (int i = ; i <= languages.Count - ; i++)
  196. names[i] = languages[i].LocaleName;
  197. return names;
  198. }
  199. }
  200.  
  201. public static LanguageItem ActiveLanguage
  202. {
  203. get { return activeLanguage; }
  204. }
  205.  
  206. public static LanguageList Languages
  207. {
  208. get { return languages; }
  209. }
  210.  
  211. public static bool LoadLanguageRes(ContainerControl cc)
  212. {
  213. return (ActiveLanguage != null) ? ActiveLanguage.LoadLanguageRes(cc) : false;
  214. }
  215.  
  216. //为动态弹出菜单设计
  217. public static bool LoadLanguageRes(ContainerControl cc, ToolStrip menu)
  218. {
  219. return (ActiveLanguage != null) ? ActiveLanguage.LoadLanguageRes(cc, menu) : false;
  220. }
  221.  
  222. public static string Text(string textId, string defaultText = "")
  223. {
  224. return GetText(textId, defaultText);
  225. }
  226.  
  227. public static string GetText(string textId, string defaultText = "")
  228. {
  229. return (ActiveLanguage != null) ? ActiveLanguage.GetText(textId, defaultText) : defaultText;
  230. }
  231.  
  232. public static bool LoadLanguageById(string id)
  233. {
  234. if (languages == null)
  235. return false;
  236.  
  237. foreach (var language in languages)
  238. {
  239. if (language.Id.Equals(id))
  240. {
  241. activeLanguage = language;
  242. return true;
  243. }
  244. }
  245.  
  246. return false;
  247. }
  248.  
  249. public static bool LoadLanguageByIndex(int index)
  250. {
  251. if (index < || index > languages.Count - )
  252. return false;
  253.  
  254. activeLanguage = languages[index];
  255. return true;
  256. }
  257. }
  258. }

OK,一行加载完成。

窗体创建事件中,加入加载语言代码:

  1. //改写构造函数
  2. public MainForm(string[] args)
  3. {
  4. InitializeComponent();
  5.  
  6. appName = ML.GetText("INF_AppName", APP_NAME);
  7. ML.LoadFormLanguage(this);
    //若有未与控件关联的弹出菜单,可加如下代码实现:
    //ML.LoadLanguageRes(this, pmMain);
  8. BuildLanguageMenuItems();
  9. this.args = args;
  10. }

这样就完成了语言加载。

这里有个多语言项生成菜单,其动态生成语言菜单项并加入事件触发,函数片段如下:

  1. private void BuildLanguageMenuItems()
  2. {
  3. if (ML.LanguageLocalNames.Length == )
  4. {
  5. miLanguage.Visible = false;
  6. return;
  7. }
  8.  
  9. for (int i = ; i <= ML.LanguageLocalNames.Length - ; i++)
  10. {
  11. string ln = ML.LanguageLocalNames[i];
  12. var menuItem = new ToolStripMenuItem(ln);
  13. //menuItem.CheckOnClick = true;
  14. menuItem.Tag = i;
  15. if (i == )
  16. menuItem.Checked = true;
  17. menuItem.Click += new EventHandler((sender, e) =>
  18. {
  19. foreach (ToolStripMenuItem item in miLanguage.DropDownItems)
  20. item.Checked = (item == sender);
  21. ML.LoadLanguageByIndex((int)(sender as ToolStripItem).Tag);
  22. ML.LoadFormLanguage(this);
  23. });
  24. miLanguage.DropDownItems.Add(menuItem);
  25. }
  26. }

功能完美实现。

四、自定义语言用法

语言串中,自定义语言定义在了strings节点,程序中如此调用:

  1. MessageBox.Show(this.searchForm, string.Format(ML.GetText("INF_CannotFound", "找不到 \"{0}\""), this.searchText), ML.GetText("INF_Information", "提示"), MessageBoxButtons.OK, MessageBoxIcon.Warning);
  1. switch (MessageBox.Show(ML.GetText("INF_ContentChanged", "文件内容已被改变,要保存吗?"), ML.GetText("INF_Information", "提示"), MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question))
  2. {
  3. case DialogResult.Yes:
  4. return Save();
  5. case DialogResult.No:
  6. return true;
  7. case DialogResult.Cancel:
  8. return false;
  9. default:
  10. return true;
  11. }

颇为灵活。

五、效果图:

Demo源码下载:

Notepad_ML.rar

c#: 界面多语言动态切换简单实现的更多相关文章

  1. C语言 动态库简单开发

    动态库项目 //简单的动态库开发----报文发送 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib. ...

  2. Qt 国际化之二:多国语界面动态切换的实现

    第一步在你的pro里面加入 TRANSLATIONS = myexec_zh.ts (根据对应的ts文件修改)第二步用lupdate 操作pro 将要翻译的提取到ts文件 命令是 lupdate my ...

  3. Spring简单实现数据源的动态切换

    Spring简单实现数据源的动态切换: 1. 创建一个数据源切换类: 2. 继承AbstractRoutingDataSource,创建多数据源路由类,并注入到spring的配置文件中: 3. AOP ...

  4. 动态切换采用 CSplitterWnd 静态划分的视图布局(MFC)

    标题读起来有些拗口,具体是什么情况,我们来看: 一.问题的提出 一个采用MFC开发的软件,其窗体视图采用CSplitterWnd三分,效果如下图所示: 图1 软件的默认视图布局 该MFC开发的软件功能 ...

  5. QT皮肤系统的动态切换

    应用需求: 提供皮肤切换选项,在不重启应用程序的情况下实现皮肤的动态切换. 理论基础: 1) 图片资源是如何被利用的 这里先简要说明一下实现原理,皮肤的动态切换其关键在于图片资源的加载方式.QT中每个 ...

  6. 动态切换数据库(EF框架)

             文章简略:本文测试项目为Silverlight+EF+RIA Service动态切换数据库的问题 通常,Ado.net EntityFramework的数据库连接字符串Connect ...

  7. Matlab中界面和注释---中英文切换问题

    有参考网页后实践的心得: Matlab中界面和注释---中英文切换问题 网上有大把的方法,并不是一一有效,这里介绍一种比较简单的方法我自己的电脑挺好用的,大家的电脑matlab需要你们自己实验了. 1 ...

  8. Spring AOP动态切换数据源

    现在稍微复杂一点的项目,一个数据库也可能搞不定,可能还涉及分布式事务什么的,不过由于现在我只是做一个接口集成的项目,所以分布式就先不用了,用Spring AOP来达到切换数据源,查询不同的数据库就可以 ...

  9. 踢爆IT劣书出版黑幕——由清华大学出版社之《C语言入门很简单》想到的(1)

    1.前言与作者 首先声明,我是由于非常偶然的机会获得<C语言入门很简单>这本书的,绝对不是买的.买这种书实在丢不起那人. 去年这书刚出版时,在CU论坛举行试读推广,我当时随口说了几句(没说 ...

随机推荐

  1. java_oop_类

    类的初始化顺序    再论类的组成    类的初始化顺序详解        变量        实例变量(成员变量)        类变量(静态变量)    方法        实例方法       ...

  2. jQuery基础(二)DOM

    DOM节点的创建 jQuery节点创建与属性的处理 创建元素节点: $("<div></div>") 创建为文本节点: $("<div> ...

  3. Linux内核分析第九次作业

    理解进程调度时机跟踪分析进程调度与进程切换的过程 一.基础知识 Linux系统的一般执行过程 一般情况:正在运行的用户态进程X切换到运行用户态进程Y的过程 1. 正在运行的用户态进程X 2. 发生中断 ...

  4. 《Linux内核原理与分析》第四次作业

    跟踪分析Linux内核的启动过程 使用实验楼的虚拟机打开shell 使用 gdb 跟踪调试内核 使用 qemu qemu -kernel linux-3.18.6 /arch/x86/boot/baI ...

  5. [转]python中pandas库中DataFrame对行和列的操作使用方法

    转自:http://blog.csdn.net/u011089523/article/details/60341016 用pandas中的DataFrame时选取行或列: import numpy a ...

  6. centos-rpm安装的mariadb,php52源码编译安装时注意点

    1.不要静态指定with-mysql 以扩展的mysql.so的形式安装 2.找不到header file之类的 要yum install mysql-devel find / -name mysql ...

  7. Numpy、SciPy、MatPlotLib在Python2.7.9下的安装与配置

    前言: Python安装完Numpy,SciPy和MatplotLib后,可以成为非常犀利的科研利器.网上关于这三个库的安装都写得非常不错,但是大部分人遇到的问题并不是如何安装,而是安装好后因为配置不 ...

  8. java中的内部类详解

    https://www.cnblogs.com/dolphin0520/p/3811445.html https://www.cnblogs.com/chenssy/p/3388487.html

  9. [转][C#]程序的动态编译

    转自:https://blog.csdn.net/clb929/article/details/51385399 附 文件下载

  10. Unity Shader Graph(一)初次尝试

    软件环境 Unity Version: 2018.1.2f1 边缘发光材质效果 创建工程 打开Unity并创建一个新工程 安装依赖项 Window -> Package Manager打开包管理 ...