好些年前,我曾用 WinForm 开发过一个 HTML 编辑器。无论是文档编辑器、博客系统,还是内容管理平台,一个功能完善且界面美观的富文本编辑器都能显著提升用户体验。
你是否曾经为以下问题而困扰:如何在WPF应用中实现专业级的文本编辑功能?如何设计出既美观又实用的编辑器界面?如何处理复杂的文本格式化逻辑?
今天,我将通过一个完整的实战项目,带你从零开始构建一个具备VS Code暗黑主题风格的富文本编辑器,让你的C#技能更上一层楼!
我们将构建一个功能完整的富文本编辑器,具备以下特性:
C#AppRichTextBox/
├── MainWindow.xaml # UI界面设计
├── MainWindow.xaml.cs # 业务逻辑实现
首先,让我们创建按钮样式:
XML<Style x:Key="ToolBarButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="#DCDCDC"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="8,4"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="2">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#3E3E42"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#007ACC"/>
</Trigger>
<Trigger Property="Tag" Value="Active">
<Setter Property="Background" Value="#007ACC"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
🔥 关键技巧:通过Tag属性控制按钮的激活状态,这是实现状态同步的关键!
C#/// <summary>
/// 加粗按钮点击事件 - 展示状态切换的核心逻辑
/// </summary>
private void BoldButton_Click(object sender, RoutedEventArgs e)
{
var selection = MainRichTextBox.Selection;
if (!selection.IsEmpty)
{
// 获取当前字体粗细状态
var currentWeight = selection.GetPropertyValue(TextElement.FontWeightProperty);
// 智能切换:如果已经是粗体则变为正常,否则变为粗体
var newWeight = (FontWeight)currentWeight == FontWeights.Bold ?
FontWeights.Normal : FontWeights.Bold;
selection.ApplyPropertyValue(TextElement.FontWeightProperty, newWeight);
}
MainRichTextBox.Focus(); // 保持焦点
UpdateToolbarButtons(); // 更新按钮状态
}
💡 核心思路:
C#/// <summary>
/// 初始化颜色选择面板 - 动态创建颜色按钮
/// </summary>
private void InitializeColorPanel()
{
// 精心挑选的24种常用颜色
var colors = new List<Color>
{
Colors.Black, Colors.White, Colors.Red, Colors.Green,
Colors.Blue, Colors.Yellow, Colors.Orange, Colors.Purple,
Colors.Pink, Colors.Brown, Colors.Gray, Colors.LightGray,
// 更多颜色...
};
foreach (var color in colors)
{
var colorButton = new Button
{
Background = new SolidColorBrush(color),
Style = (Style)FindResource("ColorButtonStyle"),
Tag = color, // 将颜色信息存储在Tag中
ToolTip = color.ToString()
};
colorButton.Click += ColorButton_Click;
ColorButtons.Children.Add(colorButton);
}
}
这是整个项目的核心亮点!
C#/// <summary>
/// 选择改变时更新工具栏状态 - 实现完美的状态同步
/// </summary>
private void UpdateToolbarButtons()
{
var selection = MainRichTextBox.Selection;
// 检查并更新加粗按钮状态
var fontWeight = selection.GetPropertyValue(TextElement.FontWeightProperty);
BoldButton.Tag = fontWeight != DependencyProperty.UnsetValue &&
(FontWeight)fontWeight == FontWeights.Bold ? "Active" : null;
// 检查并更新斜体按钮状态
var fontStyle = selection.GetPropertyValue(TextElement.FontStyleProperty);
ItalicButton.Tag = fontStyle != DependencyProperty.UnsetValue &&
(FontStyle)fontStyle == FontStyles.Italic ? "Active" : null;
// 下划线状态检查(稍微复杂一些)
var textDecoration = selection.GetPropertyValue(Inline.TextDecorationsProperty);
var hasUnderline = textDecoration != DependencyProperty.UnsetValue &&
textDecoration != null &&
((TextDecorationCollection)textDecoration).Count > 0;
UnderlineButton.Tag = hasUnderline ? "Active" : null;
}
🚀 性能优化技巧:通过SelectionChanged事件实现实时状态更新,确保用户界面始终与文本状态保持一致。
text<Window x:Class="AppRichTextBox.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:AppRichTextBox" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <Style x:Key="ToolBarButtonStyle" TargetType="Button"> <Setter Property="Background" Value="Transparent"/> <Setter Property="Foreground" Value="#DCDCDC"/> <Setter Property="BorderBrush" Value="Transparent"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Padding" Value="8,4"/> <Setter Property="Margin" Value="2"/> <Setter Property="FontSize" Value="12"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="{TemplateBinding Padding}"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="#3E3E42"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background" Value="#007ACC"/> </Trigger> <Trigger Property="Tag" Value="Active"> <Setter Property="Background" Value="#007ACC"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="UnderlineButtonStyle" TargetType="Button" BasedOn="{StaticResource ToolBarButtonStyle}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"> <TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="{TemplateBinding Padding}" Foreground="{TemplateBinding Foreground}" TextDecorations="Underline"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="#3E3E42"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background" Value="#007ACC"/> </Trigger> <Trigger Property="Tag" Value="Active"> <Setter Property="Background" Value="#007ACC"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="ColorButtonStyle" TargetType="Button"> <Setter Property="Width" Value="20"/> <Setter Property="Height" Value="20"/> <Setter Property="Margin" Value="2"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="BorderBrush" Value="#686868"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="BorderBrush" Value="#DCDCDC"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!-- 工具栏 --> <ToolBar Grid.Row="0" Background="#2D2D30" BorderBrush="#3C3C3C" BorderThickness="0,0,0,1"> <Button x:Name="BoldButton" Content="B" FontWeight="Bold" Click="BoldButton_Click" Style="{StaticResource ToolBarButtonStyle}"/> <Button x:Name="ItalicButton" Content="I" FontStyle="Italic" Click="ItalicButton_Click" Style="{StaticResource ToolBarButtonStyle}"/> <Button x:Name="UnderlineButton" Content="U" Click="UnderlineButton_Click" Style="{StaticResource UnderlineButtonStyle}"/> <Separator Margin="5,0"/> <TextBlock Text="字体大小:" Foreground="#DCDCDC" VerticalAlignment="Center" Margin="5,0"/> <ComboBox x:Name="FontSizeCombo" Width="60" SelectionChanged="FontSizeCombo_SelectionChanged" Background="#3C3C3C" Foreground="#DCDCDC" BorderBrush="#686868"/> <Separator Margin="5,0"/> <Button x:Name="TextColorButton" Content="A" Click="TextColorButton_Click" Style="{StaticResource ToolBarButtonStyle}" ToolTip="文字颜色"/> <Separator Margin="5,0"/> <Button x:Name="AlignLeftButton" Content="⬅" Click="AlignLeftButton_Click" Style="{StaticResource ToolBarButtonStyle}" ToolTip="左对齐"/> <Button x:Name="AlignCenterButton" Content="⬛" Click="AlignCenterButton_Click" Style="{StaticResource ToolBarButtonStyle}" ToolTip="居中对齐"/> <Button x:Name="AlignRightButton" Content="➡" Click="AlignRightButton_Click" Style="{StaticResource ToolBarButtonStyle}" ToolTip="右对齐"/> </ToolBar> <!-- 颜色选择面板 --> <Border x:Name="ColorPanel" Grid.Row="1" Background="#2D2D30" BorderBrush="#3C3C3C" BorderThickness="0,0,0,1" Visibility="Collapsed"> <StackPanel Orientation="Horizontal" Margin="10,5"> <TextBlock Text="选择颜色:" Foreground="#DCDCDC" VerticalAlignment="Center" Margin="0,0,10,0"/> <WrapPanel x:Name="ColorButtons" Orientation="Horizontal"> </WrapPanel> </StackPanel> </Border> <RichTextBox x:Name="MainRichTextBox" Grid.Row="2" Background="#1E1E1E" Foreground="#DCDCDC" BorderBrush="#3C3C3C" BorderThickness="1" FontFamily="Consolas" FontSize="14" SelectionChanged="MainRichTextBox_SelectionChanged" AcceptsTab="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Padding="10"> <RichTextBox.Resources> <Style TargetType="ScrollBar"> <Setter Property="Background" Value="#2D2D30"/> <Setter Property="Foreground" Value="#686868"/> </Style> </RichTextBox.Resources> </RichTextBox> </Grid> </Window>
C#using System.IO;
using System.Text;
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;
namespace AppRichTextBox
{
public partial class MainWindow : Window
{
private bool isColorPanelVisible = false;
public MainWindow()
{
InitializeComponent();
InitializeFontSizes();
InitializeRichTextBox();
InitializeColorPanel();
}
#region 初始化方法
/// <summary>
/// 初始化字体大小选项
/// </summary>
private void InitializeFontSizes()
{
var fontSizes = new[] { 8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 72 };
FontSizeCombo.ItemsSource = fontSizes;
FontSizeCombo.SelectedItem = 14; // 默认字体大小
}
/// <summary>
/// 初始化RichTextBox默认样式
/// </summary>
private void InitializeRichTextBox()
{
// 设置默认段落样式
var paragraph = new Paragraph();
paragraph.LineHeight = 1.2; // 行间距
paragraph.Margin = new Thickness(0); // 去除默认边距
MainRichTextBox.Document.Blocks.Clear();
MainRichTextBox.Document.Blocks.Add(paragraph);
// 聚焦到编辑器
MainRichTextBox.Focus();
}
/// <summary>
/// 初始化颜色选择面板
/// </summary>
private void InitializeColorPanel()
{
// 预定义颜色列表
var colors = new List<Color>
{
Colors.Black, Colors.White, Colors.Red, Colors.Green,
Colors.Blue, Colors.Yellow, Colors.Orange, Colors.Purple,
Colors.Pink, Colors.Brown, Colors.Gray, Colors.LightGray,
Colors.DarkRed, Colors.DarkGreen, Colors.DarkBlue, Colors.Gold,
Colors.Silver, Colors.Maroon, Colors.Navy, Colors.Teal,
Colors.Lime, Colors.Aqua, Colors.Fuchsia, Colors.Olive
};
foreach (var color in colors)
{
var colorButton = new Button
{
Background = new SolidColorBrush(color),
Style = (Style)FindResource("ColorButtonStyle"),
Tag = color,
ToolTip = color.ToString()
};
colorButton.Click += ColorButton_Click;
ColorButtons.Children.Add(colorButton);
}
}
#endregion
#region 工具栏事件处理
/// <summary>
/// 加粗按钮点击事件
/// </summary>
private void BoldButton_Click(object sender, RoutedEventArgs e)
{
var selection = MainRichTextBox.Selection;
if (!selection.IsEmpty)
{
var currentWeight = selection.GetPropertyValue(TextElement.FontWeightProperty);
var newWeight = (FontWeight)currentWeight == FontWeights.Bold ? FontWeights.Normal : FontWeights.Bold;
selection.ApplyPropertyValue(TextElement.FontWeightProperty, newWeight);
}
MainRichTextBox.Focus();
UpdateToolbarButtons();
}
/// <summary>
/// 斜体按钮点击事件
/// </summary>
private void ItalicButton_Click(object sender, RoutedEventArgs e)
{
var selection = MainRichTextBox.Selection;
if (!selection.IsEmpty)
{
var currentStyle = selection.GetPropertyValue(TextElement.FontStyleProperty);
var newStyle = (FontStyle)currentStyle == FontStyles.Italic ? FontStyles.Normal : FontStyles.Italic;
selection.ApplyPropertyValue(TextElement.FontStyleProperty, newStyle);
}
MainRichTextBox.Focus();
UpdateToolbarButtons();
}
/// <summary>
/// 下划线按钮点击事件
/// </summary>
private void UnderlineButton_Click(object sender, RoutedEventArgs e)
{
var selection = MainRichTextBox.Selection;
if (!selection.IsEmpty)
{
var currentDecoration = selection.GetPropertyValue(Inline.TextDecorationsProperty);
TextDecorationCollection newDecoration = null;
if (currentDecoration != DependencyProperty.UnsetValue)
{
var decorations = currentDecoration as TextDecorationCollection;
if (decorations != null && decorations.Count > 0)
{
newDecoration = null; // 移除下划线
}
else
{
newDecoration = TextDecorations.Underline; // 添加下划线
}
}
else
{
newDecoration = TextDecorations.Underline;
}
selection.ApplyPropertyValue(Inline.TextDecorationsProperty, newDecoration);
}
MainRichTextBox.Focus();
UpdateToolbarButtons();
}
/// <summary>
/// 字体大小改变事件
/// </summary>
private void FontSizeCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (FontSizeCombo.SelectedItem != null)
{
var fontSize = (int)FontSizeCombo.SelectedItem;
var selection = MainRichTextBox.Selection;
if (!selection.IsEmpty)
{
selection.ApplyPropertyValue(TextElement.FontSizeProperty, (double)fontSize);
}
MainRichTextBox.Focus();
}
}
/// <summary>
/// 文字颜色按钮点击事件
/// </summary>
private void TextColorButton_Click(object sender, RoutedEventArgs e)
{
// 切换颜色选择面板的可见性
isColorPanelVisible = !isColorPanelVisible;
ColorPanel.Visibility = isColorPanelVisible ? Visibility.Visible : Visibility.Collapsed;
// 更新按钮状态
TextColorButton.Tag = isColorPanelVisible ? "Active" : null;
}
/// <summary>
/// 颜色按钮点击事件
/// </summary>
private void ColorButton_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is Color color)
{
var brush = new SolidColorBrush(color);
var selection = MainRichTextBox.Selection;
if (!selection.IsEmpty)
{
selection.ApplyPropertyValue(TextElement.ForegroundProperty, brush);
}
// 隐藏颜色面板
isColorPanelVisible = false;
ColorPanel.Visibility = Visibility.Collapsed;
TextColorButton.Tag = null;
MainRichTextBox.Focus();
}
}
/// <summary>
/// 左对齐按钮点击事件
/// </summary>
private void AlignLeftButton_Click(object sender, RoutedEventArgs e)
{
SetTextAlignment(TextAlignment.Left);
}
/// <summary>
/// 居中对齐按钮点击事件
/// </summary>
private void AlignCenterButton_Click(object sender, RoutedEventArgs e)
{
SetTextAlignment(TextAlignment.Center);
}
/// <summary>
/// 右对齐按钮点击事件
/// </summary>
private void AlignRightButton_Click(object sender, RoutedEventArgs e)
{
SetTextAlignment(TextAlignment.Right);
}
/// <summary>
/// 设置文本对齐方式
/// </summary>
private void SetTextAlignment(TextAlignment alignment)
{
var selection = MainRichTextBox.Selection;
if (!selection.IsEmpty)
{
selection.ApplyPropertyValue(Paragraph.TextAlignmentProperty, alignment);
}
MainRichTextBox.Focus();
UpdateAlignmentButtons();
}
#endregion
#region 选择改变事件处理
/// <summary>
/// RichTextBox选择改变事件
/// </summary>
private void MainRichTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
UpdateToolbarButtons();
UpdateFontSizeCombo();
UpdateAlignmentButtons();
}
/// <summary>
/// 更新工具栏按钮状态
/// </summary>
private void UpdateToolbarButtons()
{
var selection = MainRichTextBox.Selection;
// 更新加粗按钮状态
var fontWeight = selection.GetPropertyValue(TextElement.FontWeightProperty);
BoldButton.Tag = fontWeight != DependencyProperty.UnsetValue &&
(FontWeight)fontWeight == FontWeights.Bold ? "Active" : null;
// 更新斜体按钮状态
var fontStyle = selection.GetPropertyValue(TextElement.FontStyleProperty);
ItalicButton.Tag = fontStyle != DependencyProperty.UnsetValue &&
(FontStyle)fontStyle == FontStyles.Italic ? "Active" : null;
// 更新下划线按钮状态
var textDecoration = selection.GetPropertyValue(Inline.TextDecorationsProperty);
var hasUnderline = textDecoration != DependencyProperty.UnsetValue &&
textDecoration != null &&
((TextDecorationCollection)textDecoration).Count > 0;
UnderlineButton.Tag = hasUnderline ? "Active" : null;
}
/// <summary>
/// 更新字体大小组合框
/// </summary>
private void UpdateFontSizeCombo()
{
var selection = MainRichTextBox.Selection;
var fontSize = selection.GetPropertyValue(TextElement.FontSizeProperty);
if (fontSize != DependencyProperty.UnsetValue)
{
var size = (double)fontSize;
var intSize = (int)Math.Round(size);
if (FontSizeCombo.Items.Contains(intSize))
{
FontSizeCombo.SelectedItem = intSize;
}
}
}
/// <summary>
/// 更新对齐按钮状态
/// </summary>
private void UpdateAlignmentButtons()
{
var selection = MainRichTextBox.Selection;
var alignment = selection.GetPropertyValue(Paragraph.TextAlignmentProperty);
// 重置所有对齐按钮状态
AlignLeftButton.Tag = null;
AlignCenterButton.Tag = null;
AlignRightButton.Tag = null;
if (alignment != DependencyProperty.UnsetValue)
{
switch ((TextAlignment)alignment)
{
case TextAlignment.Left:
AlignLeftButton.Tag = "Active";
break;
case TextAlignment.Center:
AlignCenterButton.Tag = "Active";
break;
case TextAlignment.Right:
AlignRightButton.Tag = "Active";
break;
}
}
}
#endregion
#region 文档操作方法
/// <summary>
/// 获取纯文本内容
/// </summary>
public string GetPlainText()
{
return new TextRange(MainRichTextBox.Document.ContentStart,
MainRichTextBox.Document.ContentEnd).Text;
}
/// <summary>
/// 设置纯文本内容
/// </summary>
public void SetPlainText(string text)
{
MainRichTextBox.Document.Blocks.Clear();
var paragraph = new Paragraph(new Run(text));
MainRichTextBox.Document.Blocks.Add(paragraph);
}
/// <summary>
/// 获取RTF格式内容
/// </summary>
public string GetRtfContent()
{
var range = new TextRange(MainRichTextBox.Document.ContentStart,
MainRichTextBox.Document.ContentEnd);
using (var stream = new MemoryStream())
{
range.Save(stream, DataFormats.Rtf);
stream.Position = 0;
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
/// <summary>
/// 设置RTF格式内容
/// </summary>
public void SetRtfContent(string rtf)
{
var range = new TextRange(MainRichTextBox.Document.ContentStart,
MainRichTextBox.Document.ContentEnd);
using (var stream = new MemoryStream())
{
using (var writer = new StreamWriter(stream))
{
writer.Write(rtf);
writer.Flush();
stream.Position = 0;
range.Load(stream, DataFormats.Rtf);
}
}
}
#endregion
#region 窗口事件处理
/// <summary>
/// 窗口失去焦点时隐藏颜色面板
/// </summary>
protected override void OnDeactivated(EventArgs e)
{
base.OnDeactivated(e);
if (isColorPanelVisible)
{
isColorPanelVisible = false;
ColorPanel.Visibility = Visibility.Collapsed;
TextColorButton.Tag = null;
}
}
#endregion
}
}

C#/// <summary>
/// 获取RTF格式内容 - 支持完整的格式保存
/// </summary>
public string GetRtfContent()
{
var range = new TextRange(MainRichTextBox.Document.ContentStart,
MainRichTextBox.Document.ContentEnd);
using (var stream = new MemoryStream())
{
range.Save(stream, DataFormats.Rtf);
stream.Position = 0;
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
💾 实用价值:这个方法让你的编辑器具备了专业级的文档保存能力!
通过这个完整的项目实战,你掌握了:
这不仅仅是一个富文本编辑器,更是你掌握WPF高级开发技巧的完美起点。每一行代码都体现了实战经验的积累,每一个功能都经过了实际项目的验证。
💬 互动时刻:你在开发富文本编辑器时遇到过什么技术难题?或者你想为这个编辑器添加哪些新功能?欢迎在评论区分享你的想法和经验!
🔄 觉得这篇教程对你有帮助?别忘了转发给更多的C#开发同行,让我们一起在技术的道路上成长!
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!