iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 4
0
Software Development

當我遊走在程式的初學路上-從入門到放棄系列 第 5

Project 1 - 自我介紹程式(5):衍伸變化並複習前幾天的內容 - 實作多語系與跨視窗應用

  • 分享至 

  • xImage
  •  

Project 1 - 自我介紹程式(4):活用「語法、邏輯、資料結構、型態函式庫」-確保正確的資料進到程式當中
https://ithelp.ithome.com.tw/articles/10213632

複習第四天:確保正確的資料進到程式當中

情況一:NULL、空字串

每次做任何處理資料前,檢查的有沒有資料

情況二+三:資料型態不對或範圍錯誤造成溢位

  1. 從源頭解決
  2. 在資料輸入的當下過濾
  3. 在存到變數前檢查

情況四:讀寫檔的過程中,因檔案不存在或檔案類型造成程式錯誤

  1. 檢查檔案存不存在
  2. 檢查檔案格式正不確

從教學者與學習者看放棄程式的人

多語系看似是一個簡單的需求,單是實作的過程你會發現,網路上有十幾種解法,對於程式的初學者,在網路上找到其中真正符合你需要的過程無法精確的找到關鍵字的方向,往往需要耗費不少時間

以下是我找到兩個相對比較簡潔的解法資源:
http://limitedcode.blogspot.com/2016/09/c-globalization.html
https://stackoverflow.com/questions/32989100/how-to-make-multi-language-app-in-winforms

第五天簡介:多語系 需求與實作結果

我想讓今天這支程式可以用來對英文語系國家的人自我介紹,可是又不想重新另外寫一個英文版的專案

  1. 剛進入程式,選擇語系
    https://ithelp.ithome.com.tw/upload/images/20190906/20120331ZAjsOTm73l.png

  2. 進入主畫面前
    https://ithelp.ithome.com.tw/upload/images/20190906/20120331RzD7v31sOI.png

  3. 進入主畫面後
    https://ithelp.ithome.com.tw/upload/images/20190906/20120331qsa0BI1cb9.png

  4. 按下自我介紹後的結果
    https://ithelp.ithome.com.tw/upload/images/20190906/20120331kKFRNtbzqc.png

實作流程

一、專案架構面

  1. 在專案當中加入三個語系資源檔

程式會依照當 前的語系設定,根據資源檔的語系取得對應的文字:
(1) 資源檔名稱.resx :預設語系對應的文字
(2) 資源檔名稱.語系.resx:其他語系對應的文字

https://ithelp.ithome.com.tw/upload/images/20190907/20120331FLlQnvoJER.png

  1. 新增語言選擇的視窗
    https://ithelp.ithome.com.tw/upload/images/20190907/201203318MIZu1PnnQ.png

二、選擇語言視窗的畫面與程式

  1. 設計選擇語言視窗的畫面與欄位 (LanguageSelectForm.cs)
    https://ithelp.ithome.com.tw/upload/images/20190907/20120331VyvM8snkSN.png

  2. 修改專案的Program.cs,讓程式執行後,出現語言選擇的視窗

using System;
using System.Windows.Forms;

namespace IT_Day01
{
    static class Program
    {
        /// <summary>
        /// 應用程式的主要進入點。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            
            // 讓程式一開始出現LanguageSelectForm 語言選擇畫面
            Application.Run(new LanguageSelectForm());
        }
    }
}

  1. 撰寫 語言選擇畫面 (LanguageSelectForm) 的 「Load事件」 與 「選擇語言的Click事件」
    當使用者按下選擇語言時,帶入選擇的語言到主畫面,呈現對應的語系。
    同時設定主畫面的偵聽事件,當使用者關閉自我介紹主畫面後,重新顯示選擇語系的畫面

A視窗 透過產生物件的建構子 傳遞 資料到 B視窗:
B視窗的類別 視窗物件變數名稱 = new B視窗的類別(將傳遞的資料傳到B視窗的建構子參數);

/// <summary>
/// 帶入選擇的語言到主視窗
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void selectBtn_Click(object sender, EventArgs e)
{
    IntroductionForm introductionForm = new IntroductionForm(languageComboBox.SelectedIndex);

    introductionForm.FormClosed += introductionForm_FormClosed;

    introductionForm.Show();
    this.Visible = false;
}

/// <summary>
/// 關閉自我介紹主畫面後,重新開啟語系選擇的視窗
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void introductionForm_FormClosed(object sender, FormClosedEventArgs e)
{
    this.Visible = true;
}

三、設定預設、中文和英文的語言資源檔

分別在三個語系的資源檔當中,加入所有多語系的文字變數。

預設、中文語系:
https://ithelp.ithome.com.tw/upload/images/20190907/20120331Xba8MkF80G.png

https://ithelp.ithome.com.tw/upload/images/20190907/20120331jrs7XbR1se.png

英文語系:
https://ithelp.ithome.com.tw/upload/images/20190907/20120331kRECQSP7AF.png

四、自我介紹視窗的畫面與程式

  1. 修改自我介紹的主畫面 (IntroductionForm) 載入時,根據選擇語系,修改主畫面的語系

B視窗 透過視窗類別的建構子參數 接收 A視窗的資料:
public B視窗類別建構子(透過參數接收A視窗傳遞過來的資料) {}

public IntroductionForm(int languageIndex)
{
    string[] language = { "zh-TW", "en-US" };

    // 在產生畫面前改變語系
    Thread.CurrentThread.CurrentUICulture = new CultureInfo(language[languageIndex]);

    InitializeComponent();
}
  1. 設計自我介紹 (IntroductionForm) 的多語系畫面

預設語系、中文語系
https://ithelp.ithome.com.tw/upload/images/20190907/20120331vhFjLGxgtO.png

https://ithelp.ithome.com.tw/upload/images/20190907/20120331fFg1UmgmEr.png

英文語系:
https://ithelp.ithome.com.tw/upload/images/20190907/20120331DBcTXGRqEX.png

  1. 替換自我介紹click當中的所有文字,以資源檔的方式帶入
// 使用多語系變數的方式
// 資源檔名稱.變數名稱
LanguageResources.Name

// 校驗文字
string[] allTextBoxName = new string[]{LanguageResources.Name, 
                                       LanguageResources.HomeTown,
                                       LanguageResources.Birthday_Year, 
                                       LanguageResources.Birthday_Month, 
                                       LanguageResources.Birthday_Day }; 

for (int i = 0; i < allTextBox.GetLength(0); i++)
{
    if (string.IsNullOrEmpty(allTextBox[i].Text))
    {
        errorMsg.AppendLine(string.Format(LanguageResources.Message_PleaseInput, allTextBoxName[i]));
    }
}

if (errorMsg.ToString() != "")
{
    MessageBox.Show(errorMsg.ToString());
    return;
}

if (!Regex.IsMatch(birthdate_YearBox.Text, @"\d") || !Regex.IsMatch(birthdate_MonthBox.Text, @"\d") || !Regex.IsMatch(birthdate_DayBox.Text, @"\d"))
{
    MessageBox.Show(LanguageResources.Message_BirthdayNeedNum);
    return;
}
                                           
// 自我介紹的文字
string introductionText = string.Format(LanguageResources.Message_IntrouductionText, name, homeTown, yearOld);

第一天~第五天的完整程式碼

Program.cs 【最先被執行的程式檔】

using System;
using System.Windows.Forms;

namespace IT_Day01
{
    static class Program
    {
        /// <summary>
        /// 應用程式的主要進入點。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            Application.Run(new LanguageSelectForm());
        }
    }
}

LanguageSelectForm.cs 【選擇語系視窗】

using System;
using System.Windows.Forms;

namespace IT_Day01
{
    public partial class LanguageSelectForm : Form
    {
        public LanguageSelectForm()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 視窗載入時,設定語言預設為中文
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LanguageSelectForm_Load(object sender, EventArgs e)
        {
            languageComboBox.SelectedIndex = 0;
        }

        /// <summary>
        /// 帶入選擇的語言到主視窗
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void selectBtn_Click(object sender, EventArgs e)
        {
            IntroductionForm introductionForm = new IntroductionForm(languageComboBox.SelectedIndex);

            introductionForm.FormClosed += introductionForm_FormClosed;

            introductionForm.Show();
            this.Visible = false;
        }

        /// <summary>
        /// 關閉視窗後,重新開啟語系選擇的視窗
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void introductionForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            this.Visible = true;
        }
    }
}

IntroductionForm.cs 【自我介紹視窗】


using System;
using System.Drawing;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;

namespace IT_Day01
{
    public partial class IntroductionForm : Form
    {
        public IntroductionForm(int languageIndex)
        {
            string[] language = { "zh-TW", "en-US" };

            Thread.CurrentThread.CurrentUICulture = new CultureInfo(language[languageIndex]);

            InitializeComponent();
        }

        /// <summary>
        /// 顯示邀請自我介紹的文字
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void IntroductionForm_Load(object sender, EventArgs e)
        {
            MessageBox.Show(LanguageResources.FormStart);
        }

        /// <summary>
        /// 選擇相片
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void photoBox_DoubleClick(object sender, EventArgs e)
        {
            OpenFileDialog fileDialog = new OpenFileDialog();
            fileDialog.Filter = "Image files (*.jpg, *.jpeg, *.jpe, *.jfif, *.png) | *.jpg; *.jpeg; *.jpe; *.jfif; *.png";

            if (fileDialog.ShowDialog() == DialogResult.OK && fileDialog.FileName != "")
            {
                photoBox.Image = Image.FromFile(fileDialog.FileName);
            }
        }

        /// <summary>
        /// 自我介紹按鈕滑鼠偵聽事件:處理按下自我介紹按鈕後要做的事情
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void showIntroductionBtn_Click(object sender, EventArgs e)
        {
            // 校驗每個欄位是否輸入
            TextBox[] allTextBox = new TextBox[] { nameTextBox, homeTownTextBox, birthdate_YearBox, birthdate_MonthBox, birthdate_DayBox };
            string[] allTextBoxName = new string[]{LanguageResources.Name, LanguageResources.HomeTown,
                                                   LanguageResources.Birthday_Year, LanguageResources.Birthday_Month, LanguageResources.Birthday_Day };

            StringBuilder errorMsg = new StringBuilder();

            for (int i = 0; i < allTextBox.GetLength(0); i++)
            {
                if (string.IsNullOrEmpty(allTextBox[i].Text))
                {
                    errorMsg.AppendLine(string.Format(LanguageResources.Message_PleaseInput, allTextBoxName[i]));
                }
            }

            if (errorMsg.ToString() != "")
            {
                MessageBox.Show(errorMsg.ToString());
                return;
            }

            // 校驗日期格式是否正確
            if (!Regex.IsMatch(birthdate_YearBox.Text, @"\d") || !Regex.IsMatch(birthdate_MonthBox.Text, @"\d") || !Regex.IsMatch(birthdate_DayBox.Text, @"\d"))
            {
                MessageBox.Show(LanguageResources.Message_BirthdayNeedNum);
                return;
            }
            // 取得使用者輸入的姓名和家鄉
            string name = nameTextBox.Text;
            string homeTown = homeTownTextBox.Text;

            // 取得當下的日期
            int today_Year = DateTime.Today.Year;
            int today_Month = DateTime.Today.Month;
            int today_Day = DateTime.Today.Day;

            int yearOld;

            // 計算年齡
            int birthDate_Year = int.Parse(birthdate_YearBox.Text);
            int birthDate_Month = int.Parse(birthdate_MonthBox.Text);
            int birthDate_Day = int.Parse(birthdate_DayBox.Text);

            yearOld = today_Year - int.Parse(birthdate_YearBox.Text);
            if (today_Month < birthDate_Month || (today_Month == birthDate_Month && today_Day < birthDate_Day))
            {
                yearOld = yearOld - 1;
            }

            // 顯示自我介紹
            string introductionText = string.Format(LanguageResources.Message_IntrouductionText, name, homeTown, yearOld);

            MessageBox.Show(introductionText);
        }

        /// <summary>
        /// 自訂函式:判斷是否為整數
        /// </summary>
        /// <param name="num"></param>
        /// <returns></returns>
        private bool stringIsInt(string num)
        {
            int tryParse;

            return int.TryParse(num, out tryParse);
        }

        /// <summary>
        /// 生日欄位的鍵盤偵聽事件,讓使用者只能輸入數字
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void birthdate_YearBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            char inputChar = e.KeyChar;

            e.Handled = ! (char.IsDigit(inputChar) || char.IsControl(e.KeyChar));
        }
    }
}



往明天邁進 OR 放棄? 資源混亂的網路世界

當你嘗試Google 「C# multiple language」、「WinForm 多語系」 你會發現有上百種的解法,大約需要翻5~10個網頁才會找到幾個相對比較簡潔的做法,所以對於程式初學者來說,缺的不是資源,而是找到有用資料的方向

多語系在一個商務系統,也是常見的基本要求,另外也透過多語系的功能需求,展示C#在跨視窗的實際應用。
明天接著實作下一個變化:可以將每一次的自我介紹資料寫到一個檔案、儲存使用者選擇的照片檔,以方便每一次執行程式不需要重新輸入個人資訊和選擇照片。


上一篇
Project 1 - 自我介紹程式(4):活用「語法、邏輯、資料結構、型態函式庫」-確保正確的資料進到程式當中
下一篇
Project 1 - 自我介紹程式(6): 能否留下你的自介與相片 - 實作讀寫檔流程
系列文
當我遊走在程式的初學路上-從入門到放棄9
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言