--- 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) ## 小結 透過簡單的文字編輯程式實作,同學可以學習到基本的檔案存取,以及豐富輸入文字框、下拉選單控制項的使用。並且也學習到如何取得豐富輸入文字框所選文字的格式,藉此來製作簡易的文字編輯程式。