---
tags: 視窗程式設計
---
# 實作:記事本(儲存檔案)
同學無論你使用哪一種生成式 AI 工具,相信你應該或多或少都完成了儲存檔案的功能,現在這裡就要稍微說明可能的寫法。

# 第一部分:介面設定
同樣的,我們需要放入「**儲存檔案對話方塊**」,以下就是一個儲存檔案對話方塊的範例。

要在你的視窗畫面中增加儲存檔案對話方塊很簡單,請到「工具箱」中,找尋「對話方塊」中的「SaveFileDialog」,再直接拖拉到你的視窗就可以了。

## 事件綁定
我們這範例的事件綁定很簡單,**只需要將事件綁定到兩個按鍵的「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);
}
}
```
:::
## 程式說明

儲存檔案對話方塊必須要寫程式才能使用,以下是程式的主要內容:
:::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);
}
```