---
tags: 110-2視窗程式設計
---
# 實作:精簡版 Word
如果同學瞭解實作基本的記事本程式,應該就已經瞭解「資料流(FileStream)」的用途,並且學習如何用到基本的開啟檔案與存檔,現在我們進一步帶同學製作精簡版 Word,不僅可以編輯文字,還能對文字增加字形樣式、粗體、斜體與字體大小等設計。
## 程式功能
這次我們會設計一個簡單的文件編輯器,不僅可以編輯文字,還能對文字增加字形樣式、粗體、斜體與字體大小等設計,就像是一個超精簡版的 Word 軟體。
## 試用軟體
同學你可以在以下連結,先把完整的程式下載回去使用看看。
[下載連結](https://fgu365-my.sharepoint.com/:f:/g/personal/chlu_vdi_fgu_edu_tw/ErAp2L7jZPFLtKxozH19ldgBUePawwFRqANx_wo7HsQj9Q?e=fWApLK)
## 開啟新專案
開啟新專案之前有教學過,如果你忘記了或不太熟悉,請點選[這裡](https://hackmd.io/sFFV6T2oT0-ysHotbIybhw?view#WPF-%E7%9A%84%E5%B0%88%E6%A1%88%E8%A8%AD%E5%AE%9A)。
:::success
我們的專案名稱可以叫做「TextEditor」,簡單就好。
:::
## 開始吧!
還記得第二部分的基本視窗程式設計嗎?同樣的,我們依照之前說過的兩個主要程式設計流程來設計:
1. 介面基本設計
2. 程式撰寫
因此,我們也是先初步設計介面,再來進行程式撰寫。
## 第一步:進行介面基本設計
和過去的範例程式都類似,請你先從工具箱拉控制項元件進來,需要五個按鍵,和一個「RichTextBox(豐富文件輸入文字框)」。
程式背景可以設定一個淺一點的顏色,可以和按鍵做出區別即可。
也請記得,將每一個控制項設定一個名稱,例如以下的範例名稱:
:::info
1. 開啟檔案按鍵:btnOpen
2. 存檔按鍵:btnSave
3. 文字編輯區(RichTextBox):rtbText
4. 粗體按鍵:btnBold
5. 斜體按鍵:btnItalic
6. 底線按鍵:btnUnderline
:::
![](https://i.imgur.com/i3CoyPk.png)
你可能會發現,好像按鍵群中間有兩個不一樣的控制項,那是我們接下來要來介紹的「下拉選單(ComboBox)」控制項。
### 控制項:下拉選單控制項
這邊介紹一個新的控制項,就是「ComboBox」,這個你一定熟悉,當需要做選擇的時候只要按下去,就會出現選單,你可以在裡面選出你想要的選項。
這是一種常見的控制項,在工具箱裡面就能找到。其實它也是一種按鍵,不過它是一個會跳出選單的按鍵。可以讓使用者選擇項目,又能節省一些畫面空間的情境,不過下拉選單不宜設計太長,會讓使用者不易使用。
![](https://i.imgur.com/qfugTjR.png)
現在我們要設定一個下拉選單要能選擇「字型」,另一個則能選擇「字體大小」,它也需要設定名稱,以下是你可以參考的名稱。
:::info
1. 字型下拉選單:cmbFontFamily
2. 字體大小下拉選單:cmbFontSize
:::
## 第二部分:程式碼撰寫
完成基本介面設計後,我們就要把程式寫進來,讓程式可以運作。請找出一個「**MainWindow()**」的函式片段,將以下的程式內容放進你的程式碼之中。
```csharp=
public MainWindow()
{
InitializeComponent();
// 清除rtbText的內容
rtbText.Document.Blocks.Clear();
// 設定字型下拉選單的選單內容,存取你的電腦裡面的字型庫,將你安裝的字型清單都放進去
cmbFontFamily.ItemsSource = Fonts.SystemFontFamilies.OrderBy(f => f.Source);
// 設定字體大小下拉選單的選單內容,設定8`72的數字,這要用來設定字體大小
cmbFontSize.ItemsSource = new List<double>() { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 };
}
```
這裡要說明以上程式的內容,有的時候,我們希望我們設計的視窗程式,在「一開始執行」的時候要設定一些初始化資料,要做這樣的事情,你就可以把程式放在「**MainWindow()**」這個函式區段。
這次我們使用新的下拉選單控制項,我們希望程式一開始就能夠將字型的清單與字體大小,一次都直接匯進這兩個下拉選單裡面,所以我們將這兩個下拉選單的設定,在這個函式區段做好設定。
要設定一個下拉選單的選項內容,你需要將清單內容,指定到「**ItemsSource**」裡面,這個就能設定下拉選單的清單內容。我們設定了字型清單與字體大小,字型直接從電腦的字型庫中匯入,字體大小則是指定一個「**清單(List)變數**」,並且直接輸入 8 - 72 的數字。
以下就是程式測試的效果,你可以看到選單都會出現在下拉選單之中。
![](https://i.imgur.com/ORdBBbJ.png)
![](https://i.imgur.com/Xdh7CMl.png)
### 開啟與儲存RTF檔案
我們先讓程式可以開啟與儲存檔案,首先將兩個開啟與儲存按鍵做事件綁定。程式都和上一個記事本範例是很像的,不過你需要調整「過濾器」,還有實際檔案開啟與儲存時,要指定為RTF檔案格式,以下是參考的程式碼。
```csharp=
private void btnOpen_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
// 跟記事本範例程式類似,不過要改成過濾為RTF檔案格式
dlg.Filter = "RTF文件 (*.rtf)|*.rtf|All files (*.*)|*.*";
if (dlg.ShowDialog() == true)
{
FileStream fileStream = new FileStream(dlg.FileName, FileMode.Open);
TextRange range = new TextRange(rtbText.Document.ContentStart, rtbText.Document.ContentEnd);
// DataFormats 檔案格式也要設定為RTF檔案格式
range.Load(fileStream, DataFormats.Rtf);
}
}
private void btnSave_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.Filter = "RTF文件 (*.rtf)|*.rtf|All files (*.*)|*.*";
if (dlg.ShowDialog() == true)
{
FileStream fileStream = new FileStream(dlg.FileName, FileMode.Create);
TextRange range = new TextRange(rtbText.Document.ContentStart, rtbText.Document.ContentEnd);
range.Save(fileStream, DataFormats.Rtf);
}
}
```
### RTF文件
這邊稍微跟同學說明什麼是 RTF 文件,依據維基百科的簡單說明:
:::info
富文字格式(Rich Text Format)即RTF格式,又稱多文字格式,是由微軟公司開發的跨平台文件格式。大多數的文書處理軟體都能讀取和儲存RTF文件。
:::
這種文件格式你可以看成是一種「簡單版的 Word 檔案」,除了文字資料外,可以帶有其他的文字格式設定,例如字型、字體大小、粗體等等格式。
除了 Office Word 可以開啟外,如果你的電腦還是 Windows 10(11已經移除),其實都有一套軟體叫做「WordPad」,你就可以讀取這樣的檔案。或者使用 Apache OpenOffice 也可以。
![](https://i.imgur.com/bAliDJg.png)
由於 RTF 大多數的電腦都能開啟,因此我們使用這個 RTF 格式來製作我們的精簡版 Word 程式。
### 使用下拉選單來改變字型與字體大小
接下來我們要實現,讓程式可以依據所選擇的字型與字體大小,同步也改變豐富文字框中的文字格式。
同樣的,也是要做事件綁定,我們在下拉選項的事件中,選擇的是「**SelectionChanged事件**」。
![](https://i.imgur.com/NPsROFG.png)
這個事件故名思義,就是「**當選擇項目改變的時候**」,亦即當你按下下拉選單,並且選擇其中一個項目的時間點。我們希望在使用者選擇項目後,能夠直接改變文字格式。
所以請做好綁定後,將以下程式碼納入:
```csharp=
private void cmbFontFamily_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// 判斷式:必須要有選擇項目,才會做文字格式改變
if (cmbFontFamily.SelectedItem != null)
// 將rtbText豐富文字框所選的項目,套用所設定的字型
rtbText.Selection.ApplyPropertyValue(Inline.FontFamilyProperty, cmbFontFamily.SelectedItem);
}
private void cmbFontSize_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (cmbFontSize.SelectedItem != null)
// 將rtbText豐富文字框所選的項目,套用所設定的字體大小
rtbText.Selection.ApplyPropertyValue(Inline.FontSizeProperty, cmbFontSize.SelectedItem);
}
```
以上的程式,你可以先看到有一個簡單的判斷式,我們希望使用者「**必須要選到項目**」,才會做出文字格式的改變。
再來,我們如何改變rtbText豐富文字框的文字格式,要使用一個方法叫做「**Selection.ApplyPropertyValue**」,這個方法會把你選擇的文字,改變成你想要修改的格式。他的語法如下:
```csharp
Selection.ApplyPropertyValue(要改變的格式是哪一種?, 你要設定格式內容)
```
因為我們要改字型與字體大小,字型就是設定「**Inline.FontFamilyProperty**」,字體大小則是「**Inline.FontSizeProperty**」,我們只要分別指定所選擇的下拉選單項目就可以了。
那麼如何取得使用者所選擇的項目,你就必須要用「**SelectedItem**」來取得,你可以看到上面的程式碼,都是使用 **SelectedItem** 來取得使用者所選擇的字型與字體大小。
設定好程式之後,你應該會發現,當你選擇任何一段文字,在下拉選單中選擇所要的字型與字體大小,你所選擇的文字也會跟著改變格式。
### 粗體、斜體、底線的按鍵Click事件綁定
接下來我們要設計三個粗體、斜體、底線的按鍵Click事件綁定,以下是程式碼,請同學做好事件綁定後,將相關程式碼加入。
```csharp=
private void btnBold_Click(object sender, RoutedEventArgs e)
{
// 取得你目前選取的文字,取得文字的字體粗細
object temp = rtbText.Selection.GetPropertyValue(Inline.FontWeightProperty);
if ((temp != DependencyProperty.UnsetValue) && (temp.Equals(FontWeights.Bold)))
// 判斷:文字要有設定格式、設定為粗體,改變文字成為原來的粗細程度
rtbText.Selection.ApplyPropertyValue(FontWeightProperty, FontWeights.Normal);
else
// 如果文字不是粗體,則改為粗體
rtbText.Selection.ApplyPropertyValue(FontWeightProperty, FontWeights.Bold);
}
private void btnItalic_Click(object sender, RoutedEventArgs e)
{
// 取得你目前選取的文字,取得文字的字體樣式(斜體或非斜體)
object temp = rtbText.Selection.GetPropertyValue(Inline.FontStyleProperty);
if ((temp != DependencyProperty.UnsetValue) && (temp.Equals(FontStyles.Italic)))
// 判斷:文字要有設定格式、設定為斜體,改變文字成為原來的正體
rtbText.Selection.ApplyPropertyValue(FontStyleProperty, FontStyles.Normal);
else
// 如果文字為正體,則改為斜體
rtbText.Selection.ApplyPropertyValue(FontStyleProperty, FontStyles.Italic);
}
private void btnUnderline_Click(object sender, RoutedEventArgs e)
{
// 取得你目前選取的文字,取得文字的字體樣式(字體裝飾)
object temp = rtbText.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
if ((temp != DependencyProperty.UnsetValue) && (temp.Equals(TextDecorations.Underline)))
// 判斷:文字要有設定格式、設定為底線,將文字移除底線
rtbText.Selection.ApplyPropertyValue(Inline.TextDecorationsProperty, null);
else
// 如果文字沒有底線,則增加底線
rtbText.Selection.ApplyPropertyValue(Inline.TextDecorationsProperty, TextDecorations.Underline);
}
```
以上的程式碼原理很單純,先取得選取文字目前的格式設定內容,「**FontWeightProperty**」是字體粗細、「**FontStyleProperty**」是字體樣式、「**TextDecorationsProperty**」是字體裝飾。
然後依照字體目前的格式,來判斷是否要套用格式?例如:文字設定為粗體,就改變文字成為原來的粗細程度;如果不是粗體,則修改為粗體。
設定好之後,你可以測試程式,應該就可以看到當你選擇文字,按下這三個樣式修改按鍵,也會隨之修改文字格式,如果再按一次,則會取消修改的樣式。
![](https://i.imgur.com/J2DmasY.png)
### 選取文字,同步更新控制項內容
程式寫到這邊,你應該會發現好像有一點很奇怪,試想:你去使用 Word,選擇文字,上方的工具列也會跟著一起更新字型項目、字體大小等等項目,讓你知道現在選擇的文字是什麼格式。
我們程式寫到這裡,好像我們的程式就沒辦法跟著更新,這樣有點奇怪,所以接下來我們要來撰寫這一部分的程式內容。
這次我們要綁定的事件則是豐富輸入文字框的「**SelectionChanged**」事件,**這個事件是指:當選擇項目改變時**。請綁定好之後,輸入以下程式碼:
```csharp=
// 設定一個筆刷色彩
SolidColorBrush DefaultColor = new SolidColorBrush(Color.FromArgb(100, 221, 221, 221));
private void rtbText_SelectionChanged(object sender, RoutedEventArgs e)
{
// 取得你目前選取的文字,取得文字的字體粗細
object temp = rtbText.Selection.GetPropertyValue(Inline.FontWeightProperty);
if ((temp != DependencyProperty.UnsetValue) && (temp.Equals(FontWeights.Bold)))
btnBold.Background = Brushes.Gray; // 如果是粗體,按鍵底色變灰色
else
btnBold.Background = DefaultColor; // 如果非粗體,按鍵底色變成預設顏色
// 取得你目前選取的文字,取得文字的字體樣式(斜體或非斜體)
temp = rtbText.Selection.GetPropertyValue(Inline.FontStyleProperty);
if ((temp != DependencyProperty.UnsetValue) && (temp.Equals(FontStyles.Italic)))
btnItalic.Background = Brushes.Gray; // 如果是斜體,按鍵底色變灰色
else
btnItalic.Background = DefaultColor; // 如果非斜體,按鍵底色變成預設顏色
// 取得你目前選取的文字,取得文字的字體樣式(底線或無底線)
temp = rtbText.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
if ((temp != DependencyProperty.UnsetValue) && (temp.Equals(TextDecorations.Underline)))
btnUnderline.Background = Brushes.Gray; // 如果有底線,按鍵底色變灰色
else
btnUnderline.Background = DefaultColor; // 如果無底線,按鍵底色變成預設顏色
// 取得你目前選取的文字,取得文字的字型
temp = rtbText.Selection.GetPropertyValue(Inline.FontFamilyProperty);
cmbFontFamily.SelectedItem = temp; // 依據選取文字的字型,字型下拉選單設定成該項字型
// 取得你目前選取的文字,取得文字的字體大小
temp = rtbText.Selection.GetPropertyValue(Inline.FontSizeProperty);
cmbFontSize.SelectedItem = temp; // 依據選取文字的字體大小,設定字體大小下拉選單的數字
}
```
請測試程式,你應該會發現,輸入文字之後,如果是粗體,粗體按鍵底色會變化;如果是斜體,斜體按鍵底色會變化;如果字體是有底線的,底線的按鍵也會變色。此外字型與字體大小,也會一起更新相關的下拉選單內容。
![](https://i.imgur.com/DyxSxxl.png)
其實這樣的程式跟上述改變字體的粗細、斜體、底線的概念也是很像的,取得所選文字的格式,然後做判斷,再改變相關的控制項。你可以仔細觀察,字體的格式,應該都可以從以下的語法所取得:
```csharp
rtbText.Selection.GetPropertyValue(Inline.字型格式);
```
字型格式則是有以下類型:
:::info
FontWeightProperty:字體粗細
FontStyleProperty:字體樣式(斜體)
TextDecorationsProperty:字體裝飾(底線)
FontFamilyProperty:字型設定
FontSizeProperty:字體大小
:::
如果要針對所選擇的項目進行格式的指定,則要使用以下語法:
```csharp
rtbText.Selection.ApplyPropertyValue(字型格式, 格式的項目);
```
例如你打算把所選的文字設定為「粗體」,就可以寫這樣的語法:
```csharp
rtbText.Selection.ApplyPropertyValue(FontWeightProperty, FontWeights.Bold);
```
### 小小修改
還有一個小地方要修改,你可能會發現會有以下的錯誤:當你選擇一段文字之後,再按字型或字體大小下拉選單,結果選擇的文字底色就會消失。
![](https://i.imgur.com/bYxRPiw.png)
這是因為當你選好文字後,又去選擇別的控制項,就會「**失去焦點(Lost Focus)**」,每一個控制項當你點選的時候,就會得到焦點,如果你又去點選別的,原來你選擇的控制項,那就會失去焦點。
所以我們要在「**當豐富輸入文字框失去焦點時候**」,也就是「**LostFocus事件**」發生時,在把焦點放回豐富輸入文字框中,這樣就能避免選擇文字的底色不見了。
請綁定這個事件,然後輸入以下程式碼,就能解決這樣的問題。
```csharp=
private void rtbText_LostFocus(object sender, RoutedEventArgs e)
{
// 避免選擇的文字因為按下修改格式的選項與按鍵,造成取消選擇
e.Handled = true;
}
```
![](https://i.imgur.com/7Pde3hR.png)
## 小結
透過簡單的文字編輯程式實作,同學可以學習到基本的檔案存取,以及豐富輸入文字框、下拉選單控制項的使用。並且也學習到如何取得豐富輸入文字框所選文字的格式,藉此來製作簡易的文字編輯程式。