iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 4
2
Software Development

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

Project 1 - 自我介紹程式(6): 能否留下你的自介與相片 - 實作讀寫檔流程

  • 分享至 

  • twitterImage
  •  

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

複習第五天:從實作多語系的當中喚醒前兩天的記憶

1.多語系檔案 - 專案是每個程式作品的單位:

  • 如何透過開發工具,在WinForm專案新增多語系的檔案
  • 理解語系資源檔與WinForm專案的關係
  • 學會讓專案的視窗程式拿來使用的方式

2.選擇語系 - 讓程式在對的時間點完成一件事:

  • 修改Program.cs的Main方法,讓程式開啟時顯示「選擇語系」的視窗
  • 當選擇語系的視窗開啟(Load事件),下拉選單預設為中文
  • 當使用者按下「選擇語系」按鈕,送出選擇的語系到「自我介紹主畫面」,當「自我介紹主畫面」關閉(FormClosed)後,重新顯示選擇語系的畫面
  • 當「自我介紹主畫面」開啟後(public IntroductionForm 建構子),取得「選擇語系畫面」傳過來的選擇語系。

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

讀寫檔跟多語系一樣,是許多資訊系統當中常見的功能需求。然而實作讀寫檔的過程非常容易造成程式的錯誤。包括資料夾或檔案不存在、資料為Null或空值、型態的錯誤。面對這些錯誤對學習者和教學者造成無數的挫折感和時間的耗費

第六天簡介 - 不用每次重新輸入個人資訊和選擇圖片

當我輸入個人資訊、選擇大頭貼,按下保存之後,將這些資訊存到檔案當中:
https://ithelp.ithome.com.tw/upload/images/20190907/20120331s5Ad9Awdpl.png

存檔之後,在程式的檔案附近看到被保存的JSON資料和圖片檔:
https://ithelp.ithome.com.tw/upload/images/20190907/20120331rF1tFGvBM4.png

個人資料在JSON的資料格式:
https://ithelp.ithome.com.tw/upload/images/20190907/20120331zob5TfRHnJ.png

讀寫檔的處理重點

一、讀取保存輸入的個人資訊 - 文字類讀寫檔

可以實作的檔案類型包括「TXT、CSV、JSON、XML、XLS」,每一種檔案格式都有對應的IO函式庫。今天我們將使用Json.net函式庫,以JSON來實作檔案讀寫

二、讀取與保存相片檔 - 圖片讀寫檔

在C#,可以使用Image函式庫來處理圖片,或者透過Stream 讀取Byte[]後轉成陣列

三、防範並處理資料讀寫可能造成的錯誤

  1. 讀取檔案結果檔案不存在
    https://ithelp.ithome.com.tw/upload/images/20190907/20120331TdsIGeWEjh.png

  2. 讀取檔案資料,結果因為檔案裡面的資料結構有問題,無法存到變數當中
    https://ithelp.ithome.com.tw/upload/images/20190907/20120331DrB6SPZHao.png

  3. 將資料寫入檔案,結果資料夾或檔案路徑不存在
    https://ithelp.ithome.com.tw/upload/images/20190907/20120331gUjDx5N0vS.png

  4. 將資料寫入檔案,結果資料為NULL,寫入文字檔的過程中無法轉成文字造成錯誤。寫入圖片檔的過程中無法轉成圖片格式造成錯誤
    https://ithelp.ithome.com.tw/upload/images/20190907/20120331a0waPu9F9F.png

四、讀寫檔主要流程:

1.確認資料夾與檔案是否存在

// 如果資校夾不存在
if (!Directory.Exists(dirPath))
{
    
}

// 如果檔案不存在
if (!File.Exists(jsonPath))
{
    
}
  1. 進行讀寫檔

(1) 建立資料夾與檔案

// 建立資料夾
Directory.CreateDirectory(dirPath);

// 建立檔案
File.Create(jsonPath).Close();

(2) 讀檔

// 從JSON檔讀取先前保存的個人資訊
    string introductionJsonStr = File.ReadAllText(filePath);
    photoBox.Image = Image.FromFile(imagePath);

(3) 寫檔

    // 保存個人資訊到JSON
    File.WriteAllText(jsonPath, JsonConvert.SerializeObject(introductionJson));
    // 保存個人大頭貼到Jpeg 圖片
    photoBox.Image.Save(imagePath, ImageFormat.Jpeg);

3.進行讀檔後、寫檔前,確認資料結構是否正常

    JObject introductionJson = (JObject)JsonConvert.DeserializeObject(introductionJsonStr);

    //讀完資料發現資料空白、結構有缺漏、結構被破壞,不要讀取
    if (introductionJson == null || 
        !introductionJson.ContainsKey("Name") || !introductionJson.ContainsKey("HomeTown") || !introductionJson.ContainsKey("BirthDate"))
    {
        return;
    }
     else
    {
        nameTextBox.Text = introductionJson["Name"].ToString();
        homeTownTextBox.Text = introductionJson["HomeTown"].ToString();

        birthdate_YearBox.Text = introductionJson["BirthDate"].ToString().Split('-')[0];
        birthdate_MonthBox.Text = introductionJson["BirthDate"].ToString().Split('-')[1];
        birthdate_DayBox.Text = introductionJson["BirthDate"].ToString().Split('-')[2];
    }

實作流程

1.將Json.net函式庫,透過Nuget安裝到專案當中
https://ithelp.ithome.com.tw/upload/images/20190907/20120331V8mPzjuW5E.png

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

2.當自我介紹主畫面開啟時(Load事件),進行讀檔

// IntroductionForm.cs

private void IntroductionForm_Load(object sender, EventArgs e)
{
    loadSaveIntroduction();

    MessageBox.Show(LanguageResources.FormStart);
}

/// <summary>
/// 將自我介紹的資訊顯示到畫面上
/// </summary>
private void loadSaveIntroduction()
{
    string dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data");
    string filePath = Path.Combine(dirPath, "Introduction.json");
    string imagePath = Path.Combine(dirPath, "Photo.jpeg");

    // 每次讀寫檔之前,檢查路徑的資料夾與檔案是否存在,避免發生路徑不存在的錯誤
    if (!Directory.Exists(dirPath) || !File.Exists(filePath) || !File.Exists(imagePath))
    {
        return;
    }

    // 從JSON檔讀取先前保存的個人資訊
    string introductionJsonStr = File.ReadAllText(filePath);
    JObject introductionJson = (JObject)JsonConvert.DeserializeObject(introductionJsonStr);

    //讀完資料發現資料空白、結構有缺漏、結構被破壞,不要讀取
    if (introductionJson == null || 
        !introductionJson.ContainsKey("Name") || !introductionJson.ContainsKey("HomeTown") || !introductionJson.ContainsKey("BirthDate"))
    {
        return;
    }
    else
    {
        nameTextBox.Text = introductionJson["Name"].ToString();
        homeTownTextBox.Text = introductionJson["HomeTown"].ToString();

        birthdate_YearBox.Text = introductionJson["BirthDate"].ToString().Split('-')[0];
        birthdate_MonthBox.Text = introductionJson["BirthDate"].ToString().Split('-')[1];
        birthdate_DayBox.Text = introductionJson["BirthDate"].ToString().Split('-')[2];
    }

    photoBox.Image = Image.FromFile(imagePath);
}

3.當使用者按下,「保存個人資訊」時,將資料寫入JSON與圖片檔

// IntroductionForm.cs

/// <summary>
/// 按下「保存個人資訊」
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void saveBtn_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(), "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
        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, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    // 檢查有沒有選擇大頭照
    if (photoBox.Image == null)
    {
        MessageBox.Show(LanguageResources.Message_NoImage, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    string name = nameTextBox.Text;
    string homeTown = homeTownTextBox.Text;

    int birthDate_Year = int.Parse(birthdate_YearBox.Text);
    int birthDate_Month = int.Parse(birthdate_MonthBox.Text);
    int birthDate_Day = int.Parse(birthdate_DayBox.Text);

    JObject introductionJson = new JObject();

    introductionJson.Add("Name", name);
    introductionJson.Add("HomeTown", homeTown);
    introductionJson.Add("BirthDate", string.Format("{0}-{1}-{2}", birthDate_Year, birthDate_Month, birthDate_Day));

    string dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data");
    string jsonPath = Path.Combine(dirPath, "Introduction.json");
    string imagePath = Path.Combine(dirPath, "Photo.jpeg");

    // 每次讀寫檔之前,檢查路徑的資料夾與檔案是否存在,避免發生路徑不存在的錯誤
    if (!Directory.Exists(dirPath))
    {
        Directory.CreateDirectory(dirPath);
    }

    // 檔案不存在,產生個人資訊的檔案
    if (!File.Exists(jsonPath))
    {
        File.Create(jsonPath).Close();
    }

    // 保存個人資訊到JSON
    File.WriteAllText(jsonPath, JsonConvert.SerializeObject(introductionJson));
    // 保存個人大頭貼到Jpeg 圖片
    photoBox.Image.Save(imagePath, ImageFormat.Jpeg);

    MessageBox.Show("保存完成!", "訊息", MessageBoxButtons.OK, MessageBoxIcon.Information);

}

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)
        {
            loadSaveIntroduction();
            
            MessageBox.Show(LanguageResources.FormStart);
        }
        
        /// <summary>
        /// 將自我介紹的資訊顯示到畫面上
        /// </summary>
        private void loadSaveIntroduction()
        {
            string dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data");
            string filePath = Path.Combine(dirPath, "Introduction.json");
            string imagePath = Path.Combine(dirPath, "Photo.jpeg");

            // 每次讀寫檔之前,檢查路徑的資料夾與檔案是否存在,避免發生路徑不存在的錯誤
            if (!Directory.Exists(dirPath) || !File.Exists(filePath) || !File.Exists(imagePath))
            {
                return;
            }

            // 從JSON檔讀取先前保存的個人資訊
            string introductionJsonStr = File.ReadAllText(filePath);
            JObject introductionJson = (JObject)JsonConvert.DeserializeObject(introductionJsonStr);

            //讀完資料發現資料空白、結構有缺漏、結構被破壞,不要讀取
            if (introductionJson == null || 
                !introductionJson.ContainsKey("Name") || !introductionJson.ContainsKey("HomeTown") || !introductionJson.ContainsKey("BirthDate"))
            {
                return;
            }
            else
            {
                nameTextBox.Text = introductionJson["Name"].ToString();
                homeTownTextBox.Text = introductionJson["HomeTown"].ToString();

                birthdate_YearBox.Text = introductionJson["BirthDate"].ToString().Split('-')[0];
                birthdate_MonthBox.Text = introductionJson["BirthDate"].ToString().Split('-')[1];
                birthdate_DayBox.Text = introductionJson["BirthDate"].ToString().Split('-')[2];
            }

            photoBox.Image = Image.FromFile(imagePath);
        }
        /// <summary>
        /// 按下「保存個人資訊」
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void saveBtn_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(), "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
                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, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            // 檢查有沒有選擇大頭照
            if (photoBox.Image == null)
            {
                MessageBox.Show(LanguageResources.Message_NoImage, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            string name = nameTextBox.Text;
            string homeTown = homeTownTextBox.Text;

            int birthDate_Year = int.Parse(birthdate_YearBox.Text);
            int birthDate_Month = int.Parse(birthdate_MonthBox.Text);
            int birthDate_Day = int.Parse(birthdate_DayBox.Text);

            JObject introductionJson = new JObject();

            introductionJson.Add("Name", name);
            introductionJson.Add("HomeTown", homeTown);
            introductionJson.Add("BirthDate", string.Format("{0}-{1}-{2}", birthDate_Year, birthDate_Month, birthDate_Day));

            string dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data");
            string jsonPath = Path.Combine(dirPath, "Introduction.json");
            string imagePath = Path.Combine(dirPath, "Photo.jpeg");

            // 每次讀寫檔之前,檢查路徑的資料夾與檔案是否存在,避免發生路徑不存在的錯誤
            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
            }

            // 檔案不存在,產生個人資訊的檔案
            if (!File.Exists(jsonPath))
            {
                File.Create(jsonPath).Close();
            }

            // 保存個人資訊到JSON
            File.WriteAllText(jsonPath, JsonConvert.SerializeObject(introductionJson));
            // 保存個人大頭貼到Jpeg 圖片
            photoBox.Image.Save(imagePath, ImageFormat.Jpeg);

            MessageBox.Show("保存完成!", "訊息", MessageBoxButtons.OK, MessageBoxIcon.Information);

        }
        
        /// <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 放棄? 魔鬼藏在細節

讀寫檔看似是一個簡單的輸入輸出流程,往往需要考量到檔案和型態在使用者的操作過程,可能因為什麼狀況而造成程式錯誤。建議自己在不看程式碼的情況下練習讀寫檔的功能,對於型態和檔案讀寫的經驗和能力也會幫助許多。

自我介紹的程式功能大致完成了,但是你從程式結構面的角度會發現,程式碼全部混在同一個檔案,也有許多重複的地方。明天為我們的程式碼結構進行整理,準備為第一支自我介紹程式畫下句點。


上一篇
Project 1 - 自我介紹程式(5):衍伸變化並複習前幾天的內容 - 實作多語系與跨視窗應用
下一篇
Project 1 - 自我介紹程式(7):斷開程式碼的牽連 - 重構 (附上完整專案 Github)
系列文
當我遊走在程式的初學路上-從入門到放棄9
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言