1. 程式人生 > >c#: WinForm介面多語言簡單實現

c#: WinForm介面多語言簡單實現

終於有空整理下多語言實現思路。搜尋已有方案,有用不同resx檔案的,有每個控制元件動態設定的,有用反射去整的,頗為繁瑣。

結合專案中實現方法,並做簡化,實現通用的多語言切換方案,以做備忘。

它支援語言自定義新增與擴充,靈活易用,更易於維護。它以xml格式儲存語言資訊,支援自定義語言、ToolTip等字串。

 

一、語言格式:

每種語言對應一個xml格式檔案,比如中文為Chinese.lng,英文為English.lng,置於程式執行目錄之Languages資料夾下。

本欲以反射獲取已有程式集之字串,卻未有成功,因為字串值初始化在InitializeComponent()中動態設定,反射不出來這些屬性值。

結構如下圖示:

<?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):" 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)
        {
            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);
            }
            foreach (Control ctrl in control.Controls)
                ApplyLanguage(ctrl, formNode);
            //選單項,特別遍歷
            if (control is ToolStrip)
            {
                foreach (ToolStripItem toolItem in (control as ToolStrip).Items)
                    ApplyLanguage(toolItem, formNode);
            }

            //工具欄或者選單動態構建窗體或者控制元件的時候,重新對子控制元件進行處理
            control.ControlAdded += (sender, e) =>
            {
                ApplyLanguage(e.Control, formNode);
            };
        }

        private void ApplyLanguage(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)
                    ApplyLanguage(item, formNode);
            }
        }

        public bool LoadFormLanguage(Form form)
        {
            if (form == null || formsNode == null || !formsNode.HasChildNodes || formsNode.SelectSingleNode(form.Name) == null)
                return false;

            //建立ToolTip控制元件
            var toolTip = new ToolTip();
            var formNode = formsNode.SelectSingleNode(form.Name);
            form.Text = GetAttributeValue(formNode, "Text");
            foreach (Control ctrl in form.Controls)
                ApplyLanguage(ctrl, formNode, toolTip);
            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>
    {
    }

    //管理器
    public static class ML
    {
        private static LanguageItem activeLanguage;
        private static LanguageList languages;

        //最初呼叫 
        public static int LoadLanguages(string searchPattern, string defaultLanguageId = "")
        {
            languages = new LanguageList();
            string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Languages");
            if (!Directory.Exists(path))
                return 0;

            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 == 0)
                    return new string[0];
                var names = new string[languages.Count];
                for (int i = 0; i <= languages.Count - 1; 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 LoadFormLanguage(Form form)
        {
            return (ActiveLanguage != null) ? ActiveLanguage.LoadFormLanguage(form) : false;
        }

        public static string GetText(string textId, string defaultText = "")
        {
            return (ActiveLanguage != null) ? ActiveLanguage.GetText(textId, defaultText) : defaultText;
        }

        public static bool LoadLanguageById(string id)
        {
            foreach (var language in Languages)
            {
                if (language.Id.Equals(id))
                {
                    activeLanguage = language;
                    return true;
                }
            }

            return false;
        }

        public static bool LoadLanguageByIndex(int index)
        {
            if (index < 0 || index > languages.Count - 1)
                return false;

            activeLanguage = languages[index];
            return true;
        }
    }
}

 

三、初始化與載入

程式檔案中,以下程式碼做初始化:

        static void Main(string[] args)
        {
            ML.LoadLanguages("*.lng", "CHS");

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm(args));
        }

OK,一行載入完成。

窗體建立事件中,加入載入語言程式碼:

        //改寫建構函式
        public MainForm(string[] args)
        {
            InitializeComponent();

            appName = ML.GetText("INF_AppName", APP_NAME);
            ML.LoadFormLanguage(this);
            BuildLanguageMenuItems();
            this.args = args;
        }

這樣就完成了語言載入。

這裡有個多語言項生成選單,其動態生成語言選單項並加入事件觸發,函式片段如下:

        private void BuildLanguageMenuItems()
        {
            if (ML.LanguageLocalNames.Length == 0)
            {
                miLanguage.Visible = false;
                return;
            }

            for (int i = 0; i <= ML.LanguageLocalNames.Length - 1; i++)
            {
                string ln = ML.LanguageLocalNames[i];
                var menuItem = new ToolStripMenuItem(ln);
                //menuItem.CheckOnClick = true;
                menuItem.Tag = i;
                if (i == 0)
                    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