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

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

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

一、语言格式:

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

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

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

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<language Id="CHS" Name="Chinese" LocaleName="中文(简体)" LangID="0x0409" CodePage="1252">
<strings>
<INF_AppName>记事本</INF_AppName>
<INF_NoTitle>无标题</INF_NoTitle>
<INF_Information>提示</INF_Information>
<INF_CannotFound>找不到 "{0}"</INF_CannotFound>
<INF_ContentChanged>文件内容已被改变,要保存吗?</INF_ContentChanged>
</strings>
<forms>
<MainForm Text="记事本">
<miFile Text="文件(&amp;F)" />
<miNew Text="新建(&amp;N)" />
<miOpen Text="打开(&amp;0)..." />
<miSave Text="保存(&amp;S)" />
<miSaveAs Text="另存为(&amp;A)..." />
<miPageSet Text="页面设置(&amp;U)..." />
<miPrint Text="打印(&amp;P)..." />
<miExit Text="退出(&amp;X)" />
...
<miHelp Text="帮助(&amp;H)" />
<miHelpTopic Text="帮助主题(&amp;H)" />
<miLanguage Text="语言" />
<miAbout Text="关于记事本(&amp;A)" />
</MainForm>
<GotoLineForm Text="跳转到指定行">
<lblLine Text="行数(&amp;L):" />
<txtLine ToolTip="请输入您想要跳转到的行号:" />
<btnOk Text="确定" />
<btnCancel Text="取消" />
</GotoLineForm>
<SearchForm Text="查找">
<btnCancel Text="取消" />
<btnSearch Text="查找下一个(&amp;F)" />
<lblContent Text="查找内容(&amp;N):" />
<cbCaseSensitive Text="区分大小写" />
<gbDirection Text="方向" />
<rbDown Text="向下" />
<rbUp Text="向上" />
</SearchForm>
</forms>
</language>

二、解析与加载类

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

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

//多语言管理类
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using System.Xml; namespace Notepad
{
//单个语言项
public class LanguageItem
{
private const string RootNodeName = "language";
private const string StringsNodeName = "strings";
private const string FormsNodeName = "forms"; private XmlNode stringsNode;
private XmlNode formsNode; private string id;
private string name;
private string localeName; public LanguageItem(string fileName)
{
var xmlDoc = new XmlDocument();
xmlDoc.Load(fileName);
var root = xmlDoc.DocumentElement;
if (root != null)
{
this.id = GetAttributeValue(root, "Id");
this.name = GetAttributeValue(root, "Name");
this.localeName = GetAttributeValue(root, "LocaleName"); this.stringsNode = root.SelectSingleNode(StringsNodeName);
this.formsNode = root.SelectSingleNode(FormsNodeName);
}
} public string Id
{
get { return this.id; }
} public string Name
{
get { return this.name; }
} public string LocaleName
{
get { return this.localeName; }
} private void ApplyLanguage(Control control, XmlNode formNode, ToolTip toolTip = null)
{
if (string.IsNullOrEmpty(control.Name))
return; var ctrlNode = formNode.SelectSingleNode(control.Name);
if (ctrlNode != null)
{
control.Text = GetAttributeValue(ctrlNode, "Text");
string tips = GetAttributeValue(ctrlNode, "ToolTip");
if (!string.IsNullOrEmpty(tips) && toolTip != null)
toolTip.SetToolTip(control, tips);
}
//弹出菜单
if (control.ContextMenuStrip != null)
ApplyMenuLanguage(control.ContextMenuStrip, formNode);
foreach (Control ctrl in control.Controls)
ApplyLanguage(ctrl, formNode, toolTip);
//菜单项,特别遍历
if (control is ToolStrip)
{
foreach (ToolStripItem toolItem in (control as ToolStrip).Items)
ApplyMenuLanguage(toolItem, formNode);
}
} private void ApplyMenuLanguage(ToolStrip menu, XmlNode formNode)
{
foreach (ToolStripItem toolItem in menu.Items)
ApplyMenuLanguage(toolItem, formNode);
} private void ApplyMenuLanguage(ToolStripItem menuItem, XmlNode formNode)
{
if (string.IsNullOrEmpty(menuItem.Name))
return; var itemNode = formNode.SelectSingleNode(menuItem.Name);
if (itemNode != null)
{
menuItem.Text = GetAttributeValue(itemNode, "Text");
menuItem.ToolTipText = GetAttributeValue(itemNode, "ToolTip");
}
if (menuItem is ToolStripDropDownItem)
{
foreach (ToolStripItem item in (menuItem as ToolStripDropDownItem).DropDownItems)
ApplyMenuLanguage(item, formNode);
}
} public bool LoadLanguageRes(ContainerControl cc)
{
if (cc == null || formsNode == null || !formsNode.HasChildNodes || formsNode.SelectSingleNode(cc.Name) == null)
return false; //创建ToolTip控件, 以支持ToolTip显示
var toolTip = new ToolTip();
var formNode = formsNode.SelectSingleNode(cc.Name);
cc.Text = GetAttributeValue(formNode, "Text");
foreach (Control ctrl in cc.Controls)
ApplyLanguage(ctrl, formNode, toolTip);
return true;
} public bool LoadLanguageRes(ContainerControl cc, ToolStrip menu)
{
if (cc == null || formsNode == null || !formsNode.HasChildNodes || formsNode.SelectSingleNode(cc.Name) == null)
return false; var formNode = formsNode.SelectSingleNode(cc.Name);
ApplyLanguage(menu, formNode);
return true;
} private string GetAttributeValue(XmlNode xmlNode, string attrName)
{
if (xmlNode.Attributes != null && xmlNode.Attributes[attrName] != null)
return xmlNode.Attributes[attrName].Value;
return string.Empty;
} public string GetText(string textID, string defaultText = "")
{
if (stringsNode == null || !stringsNode.HasChildNodes)
return defaultText; foreach (XmlNode node in stringsNode.ChildNodes)
if (node.Name.Equals(textID))
return node.InnerText; return defaultText;
}
} public class LanguageList : List<LanguageItem>
{
} /// <summary>
/// 多语言管理器
/// </summary>
public static class ML
{
private static LanguageItem activeLanguage;
private static LanguageList languages; //最初调用
public static int LoadLanguages(string searchPattern, string defaultLanguageId = "")
{
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Languages");
return LoadLanguages(path, searchPattern, defaultLanguageId);
} public static int LoadLanguages(string path, string searchPattern, string defaultLanguageId = "")
{
languages = new LanguageList();
if (!Directory.Exists(path))
return ; var files = Directory.GetFiles(path, searchPattern);
foreach (string file in files)
languages.Add(new LanguageItem(file));
if (!string.IsNullOrEmpty(defaultLanguageId))
LoadLanguageById(defaultLanguageId); return languages.Count;
} public static string ActiveLanguageId
{
get { return (activeLanguage != null) ? activeLanguage.Id : string.Empty; }
} public static string[] LanguageLocalNames
{
get
{
if (languages == null || languages.Count == )
return new string[];
var names = new string[languages.Count];
for (int i = ; i <= languages.Count - ; i++)
names[i] = languages[i].LocaleName;
return names;
}
} public static LanguageItem ActiveLanguage
{
get { return activeLanguage; }
} public static LanguageList Languages
{
get { return languages; }
} public static bool LoadLanguageRes(ContainerControl cc)
{
return (ActiveLanguage != null) ? ActiveLanguage.LoadLanguageRes(cc) : false;
} //为动态弹出菜单设计
public static bool LoadLanguageRes(ContainerControl cc, ToolStrip menu)
{
return (ActiveLanguage != null) ? ActiveLanguage.LoadLanguageRes(cc, menu) : false;
} public static string Text(string textId, string defaultText = "")
{
return GetText(textId, defaultText);
} public static string GetText(string textId, string defaultText = "")
{
return (ActiveLanguage != null) ? ActiveLanguage.GetText(textId, defaultText) : defaultText;
} public static bool LoadLanguageById(string id)
{
if (languages == null)
return false; foreach (var language in languages)
{
if (language.Id.Equals(id))
{
activeLanguage = language;
return true;
}
} return false;
} public static bool LoadLanguageByIndex(int index)
{
if (index < || index > languages.Count - )
return false; activeLanguage = languages[index];
return true;
}
}
}

OK,一行加载完成。

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

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

这样就完成了语言加载。

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

        private void BuildLanguageMenuItems()
{
if (ML.LanguageLocalNames.Length == )
{
miLanguage.Visible = false;
return;
} for (int i = ; i <= ML.LanguageLocalNames.Length - ; i++)
{
string ln = ML.LanguageLocalNames[i];
var menuItem = new ToolStripMenuItem(ln);
//menuItem.CheckOnClick = true;
menuItem.Tag = i;
if (i == )
menuItem.Checked = true;
menuItem.Click += new EventHandler((sender, e) =>
{
foreach (ToolStripMenuItem item in miLanguage.DropDownItems)
item.Checked = (item == sender);
ML.LoadLanguageByIndex((int)(sender as ToolStripItem).Tag);
ML.LoadFormLanguage(this);
});
miLanguage.DropDownItems.Add(menuItem);
}
}

功能完美实现。

四、自定义语言用法

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

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

颇为灵活。

五、效果图:

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. 关于C++指针

    C++继承于C,对C的语法做了一些扩展.C语言中的指针占一个机器长度(32位处理器上一个指针占32位,64位处理器上安装64位操作系统一个指针占64位),指针的作用就是使用这块内存(4字节或者8字节) ...

  2. cordova最小化app插件的使用: cordova-plugin-appminimize

    1. 添加插件:cordova plugin add cordova-plugin-appminimize 2.调用方法: window.plugins.appMinimize.minimize(); ...

  3. 3.python中的基本概念

    注释: 单行注释 # 多行注释 """ ''' 变量: 把程序运行中产生的值,暂时存储在内存,方便后面的程序调用. 变量命名的规则: 1.用数字.字母.下划线组成. 2. ...

  4. Java虚拟机的内部体系结构

    1.Java程序执行流程 Java程序的执行依赖于编译环境和运行环境.源码代码转变成可执行的机器代码,由下面的流程完成: Java技术的核心就是Java虚拟机,因为所有的Java程序都在虚拟机上运行. ...

  5. NotePad++ 配置Python工作环境

    下载地址:https://notepad-plus-plus.org/ Current Version: 7.5.3 sss 显示空格和指标符 为什么建议这么作?因为判断Python语句是否在同一层次 ...

  6. 为嵌入式全志V3s荔枝派板卡添加USB MT7601U(小米随身WIFI)驱动

    折腾了了一天终于scan出环境热点了,感觉本来挺简单的事情,网上教程一大把还费了一天的劲,很丧.不过网上教程虽多,但是还还是有些不同之处的,现在特意总结一下 全志V3s荔枝派板卡 添加该驱动的过程. ...

  7. [转帖][分享] 关于系统DIY--by 原罪

    http://wuyou.net/forum.php?mod=viewthread&tid=399277&extra=page%3D1 前几天我发了一个帖子<Windows组件w ...

  8. Python的Django

    1   第一部分目录详解 修改django的项目当中的url中的配置: from django.contrib import admin from django.conf.urls import ur ...

  9. 01-Introspector内省机制

    在java领域编程中,内省机制相当的不错,可以省去我们程序员很多的不必要的代码 比如说:在jdbc工具类 我们可以将ResultSet结果集待到 javabean对象中 将http请求报文的数据 转换 ...

  10. Cookie的存活时间

    1. 默认情况下,cookie数据保存到内存里,当浏览器关闭后,Cookie数据被销毁 2. 持久化存储: setMaxAge(int seconds) 1. 正数:将Cookie数据写到硬盘的文件中 ...