--- tags: 視窗程式設計 --- # 實作:記事本(儲存檔案) 同學無論你使用哪一種生成式 AI 工具,相信你應該或多或少都完成了儲存檔案的功能,現在這裡就要稍微說明可能的寫法。 ![](https://i.imgur.com/lexN2Oo.jpg) # 第一部分:介面設定 同樣的,我們需要放入「**儲存檔案對話方塊**」,以下就是一個儲存檔案對話方塊的範例。 ![](https://imgur.com/GUex7BF.png) 要在你的視窗畫面中增加儲存檔案對話方塊很簡單,請到「工具箱」中,找尋「對話方塊」中的「SaveFileDialog」,再直接拖拉到你的視窗就可以了。 ![](https://imgur.com/dfJZYIh.png) ## 事件綁定 我們這範例的事件綁定很簡單,**只需要將事件綁定到兩個按鍵的「Click」事件即可**,請分別對兩個按鍵設定事件綁定。 # 第二部分:程式碼撰寫 完成基本介面設計後,我們就要把程式寫進來,讓程式可以運作。這次則是讓使用 RichTextBox 中任意修改內容,然後能夠按下存檔,跳出一個儲存對話方塊,進行儲存檔案的工作。 ## 程式碼 以下的程式內容你可以打開來參考,這裡僅侷限於存檔按鍵的事件,其餘的程式都省略掉。 :::spoiler ```csharp= private void btnSave_Click(object sender, EventArgs e) { // 設置對話方塊標題 saveFileDialog1.Title = "儲存檔案"; // 設置對話方塊篩選器,限制使用者只能選擇特定類型的檔案 saveFileDialog1.Filter = "文字檔案 (*.txt)|*.txt|所有檔案 (*.*)|*.*"; // 如果希望預設儲存的檔案類型是文字檔案,可以這樣設置 saveFileDialog1.FilterIndex = 1; // 如果希望對話方塊在開啟時顯示的初始目錄,可以設置 InitialDirectory saveFileDialog1.InitialDirectory = "C:\\"; // 顯示對話方塊,並等待使用者指定儲存的檔案 DialogResult result = saveFileDialog1.ShowDialog(); //建立 FileStream 物件 FileStream fileStream = null; // 檢查使用者是否選擇了檔案 if (result == DialogResult.OK) { try { // 使用者指定的儲存檔案的路徑 string saveFileName = saveFileDialog1.FileName; // 使用 FileStream 建立檔案,如果檔案已存在則覆寫 fileStream = new FileStream(saveFileName, FileMode.Create, FileAccess.Write); // 將 RichTextBox 中的文字寫入檔案中 byte[] data = Encoding.UTF8.GetBytes(rtbText.Text); fileStream.Write(data, 0, data.Length); //// 使用 using 與 FileStream 建立檔案,如果檔案已存在則覆寫 //using (fileStream = new FileStream(saveFileName, FileMode.Create, FileAccess.Write)) //{ // // 將 RichTextBox 中的文字寫入檔案中 // byte[] data = Encoding.UTF8.GetBytes(rtbText.Text); // fileStream.Write(data, 0, data.Length); //} //// 將 RichTextBox 中的文字儲存到檔案中 //File.WriteAllText(saveFileName, rtbText.Text); MessageBox.Show("檔案儲存成功。", "訊息", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { // 如果發生錯誤,用 MessageBox 顯示錯誤訊息 MessageBox.Show("儲存檔案時發生錯誤: " + ex.Message, "錯誤訊息", MessageBoxButtons.OK, MessageBoxIcon.Information); } finally { // 關閉資源,如果使用 using 或者直接以 File.WriteAllText 儲存文字檔,可以不需要。 fileStream.Close(); } } else { MessageBox.Show("使用者取消了儲存檔案操作。", "訊息", MessageBoxButtons.OKCancel, MessageBoxIcon.Information); } } ``` ::: ## 程式說明 ![](https://imgur.com/GUex7BF.png) 儲存檔案對話方塊必須要寫程式才能使用,以下是程式的主要內容: :::info 1. 儲存檔案對話方塊的基本設定 2. 顯示對話方塊,等待使用者選擇檔案 3. 建立 FileStream 物件 4. 如果使用者選擇了檔案,使用 FileStream 建立檔案,如果檔案已存在則覆寫 6. 如果使用者不選擇檔案,則關閉開啟檔案對話框 ::: ### 開啟檔案對話方塊的基本設定 要使用開啟檔案對話方塊,通常我們都會先設定一些基礎設定,這部分其實不一定要設置,但可以對於使用者來說會更加便利。 ```csharp= // 設置對話方塊標題 saveFileDialog1.Title = "儲存檔案"; // 設置對話方塊篩選器,限制使用者只能選擇特定類型的檔案 saveFileDialog1.Filter = "文字檔案 (*.txt)|*.txt|所有檔案 (*.*)|*.*"; // 如果希望預設儲存的檔案類型是文字檔案,可以這樣設置 saveFileDialog1.FilterIndex = 1; // 如果希望對話方塊在開啟時顯示的初始目錄,可以設置 InitialDirectory saveFileDialog1.InitialDirectory = "C:\\"; ``` ### 顯示對話方塊,等待使用者選擇檔案 設定好對話方塊之後,以下的語法就能跳出對話方塊,我們一般來說都會設置一個對話方塊的結果變數,用來記錄使用者是否選擇了檔案,或者取消選擇檔案。這部分跟開啟檔案的程式都是很類似的。 ```csharp= // 顯示對話方塊,並等待使用者指定儲存的檔案 DialogResult result = saveFileDialog1.ShowDialog(); // 檢查使用者是否選擇了檔案 if (result == DialogResult.OK) { ... 如果使用者選擇了檔案 } else { MessageBox.Show("使用者取消了儲存檔案操作。", "訊息"); } ``` ### 資料流的概念:檔案儲存 同樣的,檔案儲存一樣也是要用到資料流。概念就像下圖: ``` mermaid graph TD; 開啟檔案-->讀取到暫存區; 讀取到暫存區-->程式對暫存區讀取; 程式對暫存區讀取-->完成讀取; 完成讀取-->關閉暫存區; 關閉暫存區-->關閉檔案; 讀取到暫存區-->進行暫存區寫入; 進行暫存區寫入-->暫存區資料回存到檔案; 暫存區資料回存到檔案-->關閉暫存區; ``` ### 檔案資料流 接下來再跟同學說明如何讀取檔案,並且放到檔案資料流中(暫存區),再將資料從 RichTextBox 讀出,進行存檔,請先參考以下程式碼。 ```csharp= //建立 FileStream 物件 FileStream fileStream = null; // 檢查使用者是否選擇了檔案 if (result == DialogResult.OK) { try { // 使用者指定的儲存檔案的路徑 string saveFileName = saveFileDialog1.FileName; // 使用 FileStream 建立檔案,如果檔案已存在則覆寫 fileStream = new FileStream(saveFileName, FileMode.Create, FileAccess.Write); // 將 RichTextBox 中的文字寫入檔案中 byte[] data = Encoding.UTF8.GetBytes(rtbText.Text); fileStream.Write(data, 0, data.Length); MessageBox.Show("檔案儲存成功。", "訊息", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { // 如果發生錯誤,用 MessageBox 顯示錯誤訊息 MessageBox.Show("儲存檔案時發生錯誤: " + ex.Message, "錯誤訊息", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { // 關閉資源 fileStream.Close(); } } else { MessageBox.Show("使用者取消了儲存檔案操作。", "訊息", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation); } ``` 開啟檔案一定要建立一個「**檔案資料流**」,檔案資料流的物件是「**FileStream**」,我們在第 2 行首先建立了 FileStream 物件。 並且你要新增這樣的物件,需要指定SaveFileDialog的檔案(saveFileName),以及開啟檔案的模式(FileMode.Create 或 FileMode.Write)。 然後我們將「RichTextBox」中修改後的文字,進行編碼,存到一個「**byte[] data**」。 byte[] 是 C# 中的一種資料型別,稱為位元組陣列(byte array)。位元組(byte)是記憶體中儲存資料的基本單位,它由8個位元組組成,每個位元組可以表示0到255之間的整數值。 byte[] 陣列是一系列的位元組集合,每個元素都是一個位元組。通常用來處理二進位資料或原始資料。例如,圖片、音訊、檔案內容等通常以位元組的形式存儲在計算機中。在這些情況下,你可能會使用 byte[] 來處理這些資料。 在上面的程式碼中,byte[] data 被用來存儲將 RichTextBox 中的文字轉換為 UTF-8 編碼的位元組序列。這樣可以將文字資料以二進位形式寫入檔案中。 最後,我們使用「fileStream.Write」將「**byte[] data**」中的位元組資料存進檔案中。 ### 關閉資源 同樣的,請記得將所建立的「**FileStream**」關閉起來,不過我們是用「try...catch」的「**finally**」來做這件事情。 之前我們已經介紹了「try...catch」,但是沒有講到「finally」區塊,其實你只要回想之前講的「try...catch」流程,這裡我們加上一段,你就可以理解「finally」區塊在做甚麼?請參考下圖。 ``` mermaid graph TD; try-->執行程式; 執行程式-->執行完成; 執行完成-->移向finally; 執行程式-->執行失敗; 執行失敗-->移向catch; 移向catch-->執行catch內的程式; 執行catch內的程式-->移向finally; 移向finally-->執行finally內的程式; 執行finally內的程式-->結束; ``` :::success 簡單來說,當 try 區塊中的程式碼執行完畢後,無論程式是否有問題(發生例外),「finally」區塊中的程式碼都將會執行。 ::: 由於我們無論是否成功執行存檔的動作,都需要關閉「FileStream」,所以我們將「fileStream.Close()」放在「finally」,這樣一定可以保證關閉「FileStream」。 :::info 之前開啟檔案的程式碼也是可以這樣用的,你可以試著改改看。 ::: #### 使用 using 與 FileStream 儲存檔案 同樣的我們也可以使用「**using**」,修改以上的程式碼,就像下面的樣子。特別需要注意的是,因為 「using」可以自動釋放資源,所以「finally」區塊可以不需要寫。 ```csharp= //建立 FileStream 物件 FileStream fileStream = null; // 檢查使用者是否選擇了檔案 if (result == DialogResult.OK) { try { // 使用者指定的儲存檔案的路徑 string saveFileName = saveFileDialog1.FileName; // 使用 using 與 FileStream 建立檔案,如果檔案已存在則覆寫 using (fileStream = new FileStream(saveFileName, FileMode.Create, FileAccess.Write)) { // 將 RichTextBox 中的文字寫入檔案中 byte[] data = Encoding.UTF8.GetBytes(rtbText.Text); fileStream.Write(data, 0, data.Length); } // 將 RichTextBox 中的文字儲存到檔案中 File.WriteAllText(saveFileName, rtbText.Text); MessageBox.Show("檔案儲存成功。", "訊息", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { // 如果發生錯誤,用 MessageBox 顯示錯誤訊息 MessageBox.Show("儲存檔案時發生錯誤: " + ex.Message, "錯誤訊息", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { // 關閉資源,如果使用 using 或者直接以 File.WriteAllText 儲存文字檔,可以不需要。 // fileStream.Close(); } } else { MessageBox.Show("使用者取消了儲存檔案操作。", "訊息", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation); } ``` #### 更為簡單的用法 如果你只是要簡單地將「RichTextBox」中的文字儲存,使用「**File.WriteAllText**」是更簡潔和方便的方法。其中可以特別注意第2行,使用「File.WriteAllText」就不需要 FileStream 物件了。 ```csharp= // 使用「File.WriteAllText」就不需要 FileStream 物件了 // FileStream fileStream = null; // 檢查使用者是否選擇了檔案 if (result == DialogResult.OK) { try { // 使用者指定的儲存檔案的路徑 string saveFileName = saveFileDialog1.FileName; // 將 RichTextBox 中的文字儲存到檔案中 File.WriteAllText(saveFileName, rtbText.Text); MessageBox.Show("檔案儲存成功。", "訊息", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { // 如果發生錯誤,用 MessageBox 顯示錯誤訊息 MessageBox.Show("儲存檔案時發生錯誤: " + ex.Message, "錯誤訊息", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { // 關閉資源,如果使用 using 或者直接以 File.WriteAllText 儲存文字檔,可以不需要。 // fileStream.Close(); } } else { MessageBox.Show("使用者取消了儲存檔案操作。", "訊息", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation); } ```