WPF中RichTextBox的研究
最近在写一个类似文本编辑器的东西,有选择字体样式和大小的功能,但在设计的时候遇到了一些问题。
如果需要设计像QQ聊天时那样的改变字体大小及样式的话(即选择一个字体样式或大小而下面的字体将全部改变),只要把RichTextBox和Combobox绑定就可以了。这样比较简单。不过WPF中的默认字体( System.Windows.Media.FontFamily)是没有中文字体的,所以我用了安装的字体( System.Drawing.FontFamily)。因此就需要自己来写这个类,同时需要添加System.Drawing的这个引用。同样那字号的话为了看起来明显可以挑一些来写一个新类。代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<Window x:Class="RichTextBoxDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:RichTextBoxDemo" Title="MainWindow" Height="550" Width="900" WindowStartupLocation="CenterScreen"> <Window.Resources> <local:FontFamilySource x:Key="fontFamilies" /> <local:FontSizeSource x:Key="fontSizes" /> </Window.Resources> <DockPanel> <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="5, 10"> <ComboBox x:Name="ComboBoxFontFamily" ItemsSource="{StaticResource fontFamilies}" MinWidth="180" Margin="10,0" IsSynchronizedWithCurrentItem="True"> <ComboBox.ItemTemplate> <DataTemplate> <Label Content="{Binding Name}" FontFamily="{Binding Name}" /> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> <ComboBox x:Name="ComboBoxFontSize" ItemsSource="{StaticResource fontSizes}" MinWidth="100" IsSynchronizedWithCurrentItem="True"/> </StackPanel> <UniformGrid Rows="1"> <RichTextBox Padding="5,10" x:Name="RichTextBox1" FontFamily="{Binding Source={StaticResource fontFamilies},Path=/}" FontSize="{Binding Source={StaticResource fontSizes},Path=/}" /> </UniformGrid> </DockPanel> </Window>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Drawing; namespace RichTextBoxDemo { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } public class FontFamilySource : List<System.Drawing.FontFamily> { public FontFamilySource() { this.AddRange(new System.Drawing.Text.InstalledFontCollection().Families.Select(p => p).ToList()); } } public class FontSizeSource : List<double> { public FontSizeSource() { this.AddRange(Enumerable.Range(10, 62).Where(p => p % 4 == 0).Select(p => (double)p)); } } }
如果需要设计像WORD那样当改变字体的大小及样式时(即原来已写的字体大小不变,二从光标处继续写的字体变为改变的字体。同时还可以通过划出一段字来改变其字体),这时WPF中的设置就很不人性化,不能直接通过Selection或是对Paragraph很简单的设置字体,这样会出现当鼠标点到中间时,改变字体会迫使其他字体全部改变。只能通过TextChanged时的offset来判断位置。同时还需考虑是不是文档的开头或中间。最后才通过ApplyPropertyValue()来设置字体。代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<Window x:Class="RtbDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:RtbDemo" Title="MainWindow" Height="550" Width="900" WindowStartupLocation="CenterScreen"> <Window.Resources> <local:FontFamilySource x:Key="fontFamilies" /> <local:FontSizeSource x:Key="fontSizes" /> </Window.Resources> <DockPanel> <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="5, 10"> <ComboBox x:Name="ComboBoxFontFamily" ItemsSource="{StaticResource fontFamilies}" MinWidth="180" Margin="10,0" SelectionChanged="ComboBoxFontFamily_SelectionChanged"> <ComboBox.ItemTemplate> <DataTemplate> <Label Content="{Binding Name}" FontFamily="{Binding Name}" /> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> <ComboBox x:Name="ComboBoxFontSize" ItemsSource="{StaticResource fontSizes}" MinWidth="100" /> </StackPanel> <UniformGrid Rows="1"> <RichTextBox FontSize="24" Padding="5,10" x:Name="RichTextBox1" /> </UniformGrid> </DockPanel> </Window>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Drawing; namespace RtbDemo { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); RichTextBox1.Focus(); RichTextBox1.TextChanged += OnTextChanged; ComboBoxFontSize.SelectionChanged += ComboBoxFontSize_SelectionChanged; this.Loaded += OnLoaded; } void ComboBoxFontSize_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (!RichTextBox1.Selection.IsEmpty) { RichTextBox1.Selection.ApplyPropertyValue(RichTextBox.FontSizeProperty, ComboBoxFontSize.SelectedValue); } RichTextBox1.Focus(); } private void ComboBoxFontFamily_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (!RichTextBox1.Selection.IsEmpty) { TextRange range = new TextRange(RichTextBox1.Selection.Start, RichTextBox1.Selection.End); var v = ((System.Drawing.FontFamily)ComboBoxFontFamily.SelectedValue).Name.ToString(); range.ApplyPropertyValue(RichTextBox.FontFamilyProperty, v); } } private void OnLoaded(object sender, RoutedEventArgs e) { this.ComboBoxFontFamily.SelectedValue = (this.ComboBoxFontFamily.ItemsSource as FontFamilySource).FirstOrDefault(p => p.Name == "宋体"); this.ComboBoxFontSize.SelectedValue = (this.ComboBoxFontSize.ItemsSource as FontSizeSource).FirstOrDefault(p => p.ToString() == "24"); } private void OnTextChanged(object sender, TextChangedEventArgs e) { if (RichTextBox1.Selection.IsEmpty) { foreach (var each in e.Changes) { var start = RichTextBox1.Document.ContentStart; for (int i = 0; i < each.AddedLength; ++i) { var pos = start.GetPositionAtOffset(each.Offset + i + 1); var ctx = pos.GetPointerContext(LogicalDirection.Backward); if (ctx == TextPointerContext.ElementStart) { var adjacent = pos.GetAdjacentElement(LogicalDirection.Backward); if (adjacent.GetType() == typeof(Run)) { var r = adjacent as Run; r.FontSize = (double)ComboBoxFontSize.SelectedValue; System.Windows.Media.FontFamily fontFamily = new System.Windows.Media.FontFamily((ComboBoxFontFamily.SelectedValue as System.Drawing.FontFamily).Name); r.FontFamily = fontFamily; } } else if (ctx == TextPointerContext.Text) { if (pos.Parent is Run) { var r = pos.Parent as Run; if (r.FontSize != (double)ComboBoxFontSize.SelectedValue) { RichTextBox1.Selection.Select(pos, pos.GetNextInsertionPosition(LogicalDirection.Backward)); RichTextBox1.Selection.ApplyPropertyValue(RichTextBox.FontSizeProperty, ComboBoxFontSize.SelectedValue); RichTextBox1.Selection.Select(pos, pos); } System.Windows.Media.FontFamily fontFamily = new System.Windows.Media.FontFamily((ComboBoxFontFamily.SelectedValue as System.Drawing.FontFamily).Name); if (r.FontFamily != fontFamily) { RichTextBox1.Selection.Select(pos, pos.GetNextInsertionPosition(LogicalDirection.Backward)); RichTextBox1.Selection.ApplyPropertyValue(RichTextBox.FontFamilyProperty, fontFamily); RichTextBox1.Selection.Select(pos, pos); } } } } } } } } public class FontFamilySource : List<System.Drawing.FontFamily> { public FontFamilySource() { this.AddRange(new System.Drawing.Text.InstalledFontCollection().Families.Select(p => p).ToList()); } } public class FontSizeSource : List<double> { public FontSizeSource() { this.AddRange(Enumerable.Range(10, 62).Where(p => p % 4 == 0).Select(p => (double)p)); } } }