This article has been localized into Vietnamese by the community.
How-to: Tạo một trình biên tập Rich Text
Đây là một bài viết hướng dẫn khác, lấy cảm hứng từ việc điều khiển RichTextBox thú vị như thế nào và cách thức tạo ra dễ dàng làm sao nhưng vẫn có khả năng như trình soạn thảo rich-text : Windows Wordpad! Mặc dù WPF giúp điều này thực sự rất dễ dàng đối với chúng ta, nhưng sẽ có một vài mã lệnh XAML và C# hơn bình thường, tuy nhiên điều đó không vấn đề gì cả. Chúng ta sẽ đi xuyên suốt qua các phần thú vị này một lần, và sau đó đến cuối chương, tôi sẽ gửi bạn toàn bộ mã lệnh trong danh sách
Ở bài viết này, chúng tôi sẽ sử dụng rất nhiều control và kỹ thuật đã được sử dụng trong các bài viết trước, vì vậy cách giải thích sẽ không quá chi tiết. Trong trường hợp bạn cần hiểu rõ hơn về các phần đó, bạn hoàn toàn có thể quay lại các bài viết trước để đọc lại kĩ hơn.
Để bắt đầu, hãy xem xét những gì chúng ta sẽ làm. Đây là kết quả cuối cùng:
Giao diện
Giao diện bao gồm một ToolBar với các button và combo box trong đó. Ở đó có các nút bấm để mở và lưu tài liệu, các nút điều chỉnh thuộc tính font-weight và kiểu chữ, và hai combo box điều chỉnh font family và cỡ chữ.
Dưới thanh công cụ là hộp điều khiển RichTextBox, nơi những chỉnh sửa có thể được thực hiện.
Các lệnh
Điều đầu tiên bạn có thể nhận thấy đó là việc sử dụng các lệnh WPF, điều mà chúng ta đã thảo luận trước đây trong bài viết này. Chúng ta sử dụng lệnh Open và Save từ lớp ApplicationCommand để tải và lưu tài liệu, và chúng ta sử dụng lệnh ToggleBold, ToggleItalic và Toggle Underline từ lớp EditingCommand cho các nút liên quan đến kiểu sử dụng của chúng ta.
Lợi thế của việc sử dụng Commands là một điều hiển nhiên khi trình điều khiển RichTextBox đã tích hợp các lệnh ToggleBold, ToggleItalic và ToggleUnderline. Điều này có nghĩa rằng chúng ta không cần phải viết bất kỳ một mã lệnh nào về chúng để làm việc. Chỉ cần kết nối chúng vào nút ấn chỉ định và nó sẽ hoạt động.
<ToggleButton Command="EditingCommands.ToggleBold" Name="btnBold">
<Image Source="/WpfTutorialSamples;component/Images/text_bold.png" Width="16" Height="16" />
</ToggleButton>
Chúng ta cũng có một số các phím tắt như sau - bấm Ctrl+B để bật tắt chữ bôi đậm, Ctrl+I để bật tắt chữ viết nghiêng và Ctrl+U để bật tắt chữ gạch dưới
Lưu ý rằng tôi đang sử dụng kiểu điều khiển ToggleButton thay vì sử dụng kiểu điều khiển Button như thông thường. Tôi muốn nút bấm này có thể kiểm tra được, nếu phần đang được chọn hiện tại là in đậm, và được hỗ trợ thông qua thuộc tính IsChecked của ToggleButton. Tuy nhiên, WPF lại không có cách nào xử lý phần này cho chúng ta, vì vậy chúng ta cần một chút code để cập nhật các trạng thái khác nhau của nút bấm và combo box. Các thông tin này sẽ được nói tiếp trong phần sau.
Những lệnh Open và Save không được xử lý tự động, do đó ta phải tự làm như thường lệ, với một CommandBinding cho cửa sổ và rồi xử lý sự kiện ở Code-behind:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open" Executed="Open_Executed" />
<CommandBinding Command="ApplicationCommands.Save" Executed="Save_Executed" />
</Window.CommandBindings>
Chi tiết về cài đặt của tôi sẽ ở dưới bài viết này sau đây.
Font và cỡ chữ
Để hiển thị và chỉnh font và cỡ chữ ta có một vài hộp combo. Chúng được khởi tạo sẵn với font hệ thống và một vài cỡ chữ trong hàm khởi tạo của cửa sổ, như sau:
public RichTextEditorSample()
{
InitializeComponent();
cmbFontFamily.ItemsSource = Fonts.SystemFontFamilies.OrderBy(f => f.Source);
cmbFontSize.ItemsSource = new List<double>() { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 };
}
Một lần nữa, WPF có thể dễ dàng cho chúng ta một danh sách font có thể dùng với thuộc tính SystemFontFamilies. Vì một danh sách cỡ chữ gợi ý là không đủ, chúng ta sẽ làm control ComboBox có thể chỉnh sửa được. Nhờ đó, người dùng có thể nhập cỡ chữ tùy ý:
<ComboBox Name="cmbFontFamily" Width="150" SelectionChanged="cmbFontFamily_SelectionChanged" />
<ComboBox Name="cmbFontSize" Width="50" IsEditable="True" TextBoxBase.TextChanged="cmbFontSize_TextChanged" />
Điều này cũng đồng nghĩa rằng chúng ta sẽ xử lí các thay đổi bằng cách khác. Với ComboBox dùng để hiển thị các font, chúng ta có thể xử lí event SelectionChanged, trong khi chúng ta nối nó vào TextBoxBase.TextChanged của ComboBox, để người dùng có thể chọn và nhập kích thước một cách thủ công.
WPF xử lý cài đặt của lệnh Bold, Italic và Underline cho chúng ta, nhưng với font và cỡ chữ, chúng ta phải tự tay chỉnh những giá trị này. May mắn thay, sử dụng phương thức ApplyPropertyValue() khiến điều này trở nên dễ dàng. Xử lý sự kiện sẽ trông như sau:
private void cmbFontFamily_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(cmbFontFamily.SelectedItem != null)
rtbEditor.Selection.ApplyPropertyValue(Inline.FontFamilyProperty, cmbFontFamily.SelectedItem);
}
private void cmbFontSize_TextChanged(object sender, TextChangedEventArgs e)
{
rtbEditor.Selection.ApplyPropertyValue(Inline.FontSizeProperty, cmbFontSize.Text);
}
Không có gì quá thú vị ở đây - chỉ đơn giản là chuyển giá trị được chọn/nhập vào ApplyPropertyValue(), cùng thuộc tính mà ta muốn thay đổi.
Cập nhật trạng thái
Như đã đề cập ở trước, WPF xử lý các lệnh Bold, Italic và Underline cho chúng ta, nhưng ta vẫn cần tự tay cập nhật trạng thái của những nút này, bởi đây không phải một phần của chức năng của các Lệnh. Tuy nhiên, không sao hết, bởi ta cũng cần phải cập nhật hai combo box để hiển thị font và cỡ chữ hiện tại.
Ta muốn cập nhật trạng thái ngay khi con trỏ di chuyển và/hoặc có thay đổi lựa chọn; và sự kiện SelectionChanged của RichTextBox rất phù hợp để làm điều đó. Đây là cách ta xử lý:
private void rtbEditor_SelectionChanged(object sender, RoutedEventArgs e)
{
object temp = rtbEditor.Selection.GetPropertyValue(Inline.FontWeightProperty);
btnBold.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontWeights.Bold));
temp = rtbEditor.Selection.GetPropertyValue(Inline.FontStyleProperty);
btnItalic.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontStyles.Italic));
temp = rtbEditor.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
btnUnderline.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(TextDecorations.Underline));
temp = rtbEditor.Selection.GetPropertyValue(Inline.FontFamilyProperty);
cmbFontFamily.SelectedItem = temp;
temp = rtbEditor.Selection.GetPropertyValue(Inline.FontSizeProperty);
cmbFontSize.Text = temp.ToString();
}
Khá nhiều dòng, nhưng thực tế ta chỉ cần lặp lại một vài dòng và thay đổi một chút để thay đổi từng nút trong ba nút bấm, giống như với hai ComboBox.
Bản chất khá đơn giản. Đối với các nút, ta dùng phương thức GetPropertyValue() để lấy giá trị hiện tại của một thuộc tính văn bản nhất định, ví dụ như FontWeight, và rồi ta cập nhật thuộc tính IsChecked phụ thuộc vào liệu giá trị trả về có bằng với giá trị ta đang tìm hay không.
Đối với combo box, ta làm điều tương tự, nhưng tay vì đặt một thuộc tính IsChecked, ta đặt các thuộc tính SelectedItem hay Text trực tiếp với các giá trị được trả về.
Nạp và lưu một tệp
Khi xử lý các lệnh Open và Save, chúng ta dùng những đoạn mã gần giống nhau:
private void Open_Executed(object sender, ExecutedRoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
if(dlg.ShowDialog() == true)
{
FileStream fileStream = new FileStream(dlg.FileName, FileMode.Open);
TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
range.Load(fileStream, DataFormats.Rtf);
}
}
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
SaveFileDialog dlg = new SaveFileDialog();
dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
if(dlg.ShowDialog() == true)
{
FileStream fileStream = new FileStream(dlg.FileName, FileMode.Create);
TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
range.Save(fileStream, DataFormats.Rtf);
}
}
OpenFileDialog hay SaveFileDialog được dùng để chỉ rõ địa chỉ và tên tệp, và rồi văn bản sẽ được nạp hoặc lưu sử dụng một đối tượng TextRange, thứ mà chúng ta có được trực tiếp từ RichTextBox, kết hợp với một FileStream, sẽ cho phép ta truy cập tới chính tệp đó. Tệp đó sẽ được nạp và lưu trong format RTF, nhưng bạn có thể chọn một kiểu format khác nếu bạn muốn trình biên tập của bạn hỗ trợ những format khác, chẳng hạn như văn bản thô.
Hoàn thiện ví dụ
Đây là đoạn code hoàn chỉnh cho ứng dụng - đầu tiên là XAML và sau đó là C# Code-behind:
<Window x:Class="WpfTutorialSamples.Rich_text_controls.RichTextEditorSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="RichTextEditorSample" Height="300" Width="400">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open" Executed="Open_Executed" />
<CommandBinding Command="ApplicationCommands.Save" Executed="Save_Executed" />
</Window.CommandBindings>
<DockPanel>
<ToolBar DockPanel.Dock="Top">
<Button Command="ApplicationCommands.Open">
<Image Source="/WpfTutorialSamples;component/Images/folder.png" Width="16" Height="16" />
</Button>
<Button Command="ApplicationCommands.Save">
<Image Source="/WpfTutorialSamples;component/Images/disk.png" Width="16" Height="16" />
</Button>
<Separator />
<ToggleButton Command="EditingCommands.ToggleBold" Name="btnBold">
<Image Source="/WpfTutorialSamples;component/Images/text_bold.png" Width="16" Height="16" />
</ToggleButton>
<ToggleButton Command="EditingCommands.ToggleItalic" Name="btnItalic">
<Image Source="/WpfTutorialSamples;component/Images/text_italic.png" Width="16" Height="16" />
</ToggleButton>
<ToggleButton Command="EditingCommands.ToggleUnderline" Name="btnUnderline">
<Image Source="/WpfTutorialSamples;component/Images/text_underline.png" Width="16" Height="16" />
</ToggleButton>
<Separator />
<ComboBox Name="cmbFontFamily" Width="150" SelectionChanged="cmbFontFamily_SelectionChanged" />
<ComboBox Name="cmbFontSize" Width="50" IsEditable="True" TextBoxBase.TextChanged="cmbFontSize_TextChanged" />
</ToolBar>
<RichTextBox Name="rtbEditor" SelectionChanged="rtbEditor_SelectionChanged" />
</DockPanel>
</Window>
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.Win32;
using System.Windows.Controls;
namespace WpfTutorialSamples.Rich_text_controls
{
public partial class RichTextEditorSample : Window
{
public RichTextEditorSample()
{
InitializeComponent();
cmbFontFamily.ItemsSource = Fonts.SystemFontFamilies.OrderBy(f => f.Source);
cmbFontSize.ItemsSource = new List<double>() { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 };
}
private void rtbEditor_SelectionChanged(object sender, RoutedEventArgs e)
{
object temp = rtbEditor.Selection.GetPropertyValue(Inline.FontWeightProperty);
btnBold.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontWeights.Bold));
temp = rtbEditor.Selection.GetPropertyValue(Inline.FontStyleProperty);
btnItalic.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontStyles.Italic));
temp = rtbEditor.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
btnUnderline.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(TextDecorations.Underline));
temp = rtbEditor.Selection.GetPropertyValue(Inline.FontFamilyProperty);
cmbFontFamily.SelectedItem = temp;
temp = rtbEditor.Selection.GetPropertyValue(Inline.FontSizeProperty);
cmbFontSize.Text = temp.ToString();
}
private void Open_Executed(object sender, ExecutedRoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
if(dlg.ShowDialog() == true)
{
FileStream fileStream = new FileStream(dlg.FileName, FileMode.Open);
TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
range.Load(fileStream, DataFormats.Rtf);
}
}
private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
{
SaveFileDialog dlg = new SaveFileDialog();
dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
if(dlg.ShowDialog() == true)
{
FileStream fileStream = new FileStream(dlg.FileName, FileMode.Create);
TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
range.Save(fileStream, DataFormats.Rtf);
}
}
private void cmbFontFamily_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(cmbFontFamily.SelectedItem != null)
rtbEditor.Selection.ApplyPropertyValue(Inline.FontFamilyProperty, cmbFontFamily.SelectedItem);
}
private void cmbFontSize_TextChanged(object sender, TextChangedEventArgs e)
{
rtbEditor.Selection.ApplyPropertyValue(Inline.FontSizeProperty, cmbFontSize.Text);
}
}
}
Và đây là một ảnh chụp khác về đoạn text chúng ta đã chọn. Chú ý cách toolbar hiển thị trạng thái của đoạn text đã được chọn hiện tại:
Kết luận
Như bạn có thể thấy, việc tạo ra một trình soạn thảo trong WPF rất đơn giản, đặc biệt nhờ control RichTextBox. Bạn thậm chí còn có thể mở rộng ví dụ này với các tính năng khác như căn chỉnh văn bản, màu, danh sách và bảng - nếu bạn muốn.
Chú ý rằng mặc dù ví dụ trên hoạt động bình thường, nhưng nó không hề có xử lý ngoại lệ để giữ code gọn tối thiểu. Có khá nhiều chỗ mà ta có thể throw một ngoại lệ (exception), ví dụ như ở phần cỡ chữ combo box, ai đó có thể nhập vào một giá trị không phải số. Tất nhiên là bạn nên kiểm tra hết những thứ này và xử lý các ngoại lệ nếu như bạn muốn làm dựa trên ví dụ này.