基于WPF实现简单C#代码编辑功能的完整流程
作者:code_shenbing
引言
在开发轻量级开发工具、代码演示程序或嵌入式调试工具时,常常需要集成简单的 C# 代码编辑功能。WPF(Windows Presentation Foundation)凭借其灵活的界面定制能力、数据绑定特性和控件扩展能力,能够轻松实现一款支持 C# 代码高亮、基础编辑、语法提示的简单代码编辑器。这款编辑器无需依赖复杂的第三方控件库,基于 WPF 内置控件即可完成核心功能,满足轻量级 C# 代码编辑的场景需求。本文将详细讲解 WPF 实现简单 C# 代码编辑功能的完整流程,包括界面设计、语法高亮、基础编辑逻辑与功能优化。
一、核心功能规划
一款简单实用的 WPF C# 代码编辑器,需覆盖基础的代码编辑与视觉优化需求,本次实现的核心功能如下:
- 基础代码编辑:支持 C# 代码的输入、删除、复制、粘贴、撤销 / 重做,兼容常规文本编辑操作;
- C# 语法高亮:对关键字(如
using、class、if)、注释(单行//、多行/* */)、字符串(""包裹)进行差异化着色,提升代码可读性; - 代码格式辅助:支持行号显示、制表符缩进(Tab 键)、自动换行,模拟专业编辑器的基础体验;
- 界面适配:采用弹性布局,支持窗口缩放,代码编辑区域与行号区域同步滚动;
- 轻量级无依赖:基于 WPF 内置
TextBox(或RichTextBox)实现,无需引入第三方代码编辑库(如 AvalonEdit),降低项目耦合度。
二、开发环境与前置准备
1. 开发环境
- 操作系统:Windows 10/11
- 开发工具:Visual Studio 2022(或更高版本)
- 开发框架:.NET 6(或.NET 7/.NET 8,推荐长期支持版本)
- 核心技术:WPF 布局控件、
RichTextBox控件(支持富文本着色)、C# 字符串处理、正则表达式(语法匹配)
2. 前置知识
- WPF 基础布局(
Grid、ScrollViewer、StackPanel)的使用,实现行号与编辑区域的联动; RichTextBox控件的核心操作,包括文本插入、选区获取、段落格式设置、富文本着色;- 正则表达式基础,能够匹配 C# 关键字、注释、字符串等语法元素;
- C# 事件处理,掌握
KeyDown、TextChanged、ScrollChanged等事件的使用,实现编辑交互。
3. 项目创建
打开 Visual Studio 2022,创建一个新的「WPF 应用 (.NET)」项目,命名为WpfCSharpCodeEditor,选择对应的.NET 框架版本,完成项目初始化。项目结构保持简洁,核心代码集中在MainWindow.xaml(界面)和MainWindow.xaml.cs(逻辑)中,无需额外添加复杂依赖。
三、界面设计(XAML):打造轻量级代码编辑界面
WPF 代码编辑器的界面设计需兼顾实用性与简洁性,本次采用「行号区域 + 代码编辑区域」的左右布局,核心使用RichTextBox实现富文本编辑与语法高亮,通过ScrollViewer实现两个区域的同步滚动,核心 XAML 代码如下:
<Window
x:Class="WpfCSharpCodeEditor.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:local="clr-namespace:WpfCSharpCodeEditor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="mainWindow"
Title="WPF简单C#代码编辑器"
Width="1000"
Height="700"
ResizeMode="CanResize"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Grid Margin="5">
<!-- 定义全局样式,统一界面风格 -->
<Grid.Resources>
<!-- 代码编辑区域字体样式 -->
<Style x:Key="CodeTextStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Consolas" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Foreground" Value="#FFFFFF" />
</Style>
<!-- 行号区域样式 -->
<Style x:Key="LineNumberStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Consolas" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Foreground" Value="#808080" />
<Setter Property="TextAlignment" Value="Right" />
<Setter Property="Padding" Value="0,0,5,0" />
</Style>
</Grid.Resources>
<!-- 核心布局:左右分栏(行号+代码编辑) -->
<Grid.ColumnDefinitions>
<!-- 行号区域(固定宽度) -->
<ColumnDefinition Width="50" />
<!-- 代码编辑区域(自适应剩余宽度) -->
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- 1. 行号区域(带滚动,与编辑区域同步) -->
<ScrollViewer
x:Name="ScrollLineNumber"
Grid.Column="0"
Background="#1E1E1E"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
<TextBlock
x:Name="TxtLineNumber"
Margin="5,2,5,2"
Style="{StaticResource LineNumberStyle}" />
</ScrollViewer>
<!-- 2. 代码编辑区域(核心:移除外层ScrollViewer,优化RichTextBox属性) -->
<RichTextBox
x:Name="RtbCodeEditor"
Grid.Column="1"
Padding="5,2,5,2"
AcceptsReturn="True"
AcceptsTab="True"
Background="#1E1E1E"
BorderThickness="0"
FontFamily="Consolas"
FontSize="14"
Foreground="#FFFFFF"
HorizontalScrollBarVisibility="Auto"
KeyDown="RtbCodeEditor_KeyDown"
TextChanged="RtbCodeEditor_TextChanged"
VerticalScrollBarVisibility="Auto">
<FlowDocument
ColumnWidth="999999"
PagePadding="0"
PageWidth="{Binding ElementName=MainWindow, Path=ActualWidth}"
TextAlignment="Left">
<Paragraph Margin="0" LineHeight="20" />
</FlowDocument>
</RichTextBox>
</Grid>
</Window>界面设计说明
- 采用深色主题(背景
#1E1E1E),符合程序员的代码编辑习惯,降低视觉疲劳; - 行号区域固定宽度,使用
TextBlock显示行号,颜色为灰色(#808080),与代码区域形成视觉区分; - 代码编辑区域使用
RichTextBox,支持富文本着色(为后续语法高亮提供基础),启用AcceptsReturn和AcceptsTab,支持回车换行与 Tab 缩进; - 两个区域均嵌套在
ScrollViewer中,通过ScrollChanged事件实现滚动同步,保证行号与代码行一一对应; - 字体选用
Consolas(专业代码字体),等宽显示,提升代码可读性。
四、核心逻辑实现(C#):实现代码编辑与语法高亮
界面搭建完成后,核心工作是实现行号更新、语法高亮、基础编辑辅助等逻辑,所有代码均在MainWindow.xaml.cs中实现,确保逻辑简洁、高效。
1. 定义全局常量与辅助变量
首先定义 C# 关键字常量(用于语法匹配)、颜色常量(用于差异化着色),以及辅助变量,提升代码可维护性:
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 WpfCSharpCodeEditor;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Input;
public partial class MainWindow : Window
{
// 常量:C#核心关键字(可根据需求扩展)
private readonly HashSet<string> _csharpKeywords = new HashSet<string>
{
"using", "namespace", "class", "public", "private", "protected",
"static", "void", "int", "string", "bool", "double", "float",
"if", "else", "for", "foreach", "while", "do", "return",
"new", "this", "base", "try", "catch", "finally"
};
// 常量:语法着色颜色
private readonly SolidColorBrush _keywordBrush = new SolidColorBrush(Color.FromRgb(0, 122, 204)); // 蓝色(关键字)
private readonly SolidColorBrush _commentBrush = new SolidColorBrush(Color.FromRgb(0, 128, 0)); // 绿色(注释)
private readonly SolidColorBrush _stringBrush = new SolidColorBrush(Color.FromRgb(255, 165, 0)); // 橙色(字符串)
private readonly SolidColorBrush _defaultBrush = new SolidColorBrush(Color.FromRgb(255, 255, 255));// 白色(默认文本)
// 辅助变量:标记是否正在执行语法高亮(避免TextChanged事件递归触发)
private bool _isHighlighting = false;
public MainWindow()
{
InitializeComponent();
// 初始化行号
UpdateLineNumbers();
// 初始化示例C#代码
InitSampleCode();
}
/// <summary>
/// 初始化示例C#代码
/// </summary>
private void InitSampleCode()
{
string sampleCode = @"using System;
namespace WpfCSharpCodeEditor
{
// 简单C#代码示例
public class SampleClass
{
public static void Main(string[] args)
{
// 输出问候语
string message = ""Hello, WPF C# Code Editor!"";
Console.WriteLine(message);
// 简单循环
for (int i = 0; i < 5; i++)
{
Console.WriteLine(""Loop index: "" + i);
}
}
}
}";
// 将示例代码写入RichTextBox
RtbCodeEditor.Document.Blocks.Clear();
Paragraph paragraph = new Paragraph();
paragraph.Inlines.Add(new Run(sampleCode));
RtbCodeEditor.Document.Blocks.Add(paragraph);
// 执行首次语法高亮
HighlightCSharpSyntax();
// 更新行号
UpdateLineNumbers();
}
/// <summary>
/// 代码编辑区域滚动事件(同步行号区域滚动,适配RichTextBox内置滚动)
/// </summary>
private void RtbCodeEditor_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e.VerticalChange != 0)
{
ScrollLineNumber.ScrollToVerticalOffset(e.VerticalOffset);
}
}
/// <summary>
/// 更新行号显示
/// </summary>
private void UpdateLineNumbers()
{
// 获取代码总行数
int lineCount = GetCodeLineCount();
// 构建行号文本
StringBuilder lineNumberText = new StringBuilder();
for (int i = 1; i <= lineCount; i++)
{
lineNumberText.AppendLine(i.ToString());
}
// 赋值给行号TextBlock
TxtLineNumber.Text = lineNumberText.ToString();
}
/// <summary>
/// 获取代码总行数
/// </summary>
/// <returns>总行数</returns>
private int GetCodeLineCount()
{
// 从RichTextBox中提取文本并统计行数
TextRange textRange = new TextRange(RtbCodeEditor.Document.ContentStart, RtbCodeEditor.Document.ContentEnd);
string codeText = textRange.Text;
if (string.IsNullOrEmpty(codeText)) return 1;
// 统计换行符数量,总行数=换行符数+1
return codeText.Split(new[] { Environment.NewLine }, StringSplitOptions.None).Length;
}
/// <summary>
/// 代码编辑区域滚动事件(同步行号区域滚动)
/// </summary>
private void ScrollCodeEditor_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (e.VerticalChange != 0)
{
ScrollLineNumber.ScrollToVerticalOffset(e.VerticalOffset);
}
}
/// <summary>
/// C#语法高亮核心方法
/// </summary>
private void HighlightCSharpSyntax()
{
// 防止递归触发(TextChanged事件中调用此方法,避免重复执行)
if (_isHighlighting) return;
_isHighlighting = true;
try
{
// 1. 保存当前光标位置与选区(避免高亮后光标丢失)
TextPointer caretPosition = RtbCodeEditor.CaretPosition;
TextRange selectionRange = new TextRange(RtbCodeEditor.Selection.Start, RtbCodeEditor.Selection.End);
bool hasSelection = !selectionRange.IsEmpty;
// 2. 提取完整代码文本
TextRange fullTextRange = new TextRange(RtbCodeEditor.Document.ContentStart, RtbCodeEditor.Document.ContentEnd);
string codeText = fullTextRange.Text;
if (string.IsNullOrEmpty(codeText)) return;
// 3. 清空现有格式(保留文本内容)
fullTextRange.ClearAllProperties();
// 4. 分步实现语法着色
// 4.1 匹配多行注释 /* ... */
MatchCollection multiLineCommentMatches = Regex.Matches(codeText, @"/\*.*?\*/", RegexOptions.Singleline);
ApplyHighlightToMatches(multiLineCommentMatches, _commentBrush);
// 4.2 匹配单行注释 // ...
MatchCollection singleLineCommentMatches = Regex.Matches(codeText, @"//.*?$", RegexOptions.Multiline);
ApplyHighlightToMatches(singleLineCommentMatches, _commentBrush);
// 4.3 匹配字符串 ""...""
MatchCollection stringMatches = Regex.Matches(codeText, @"\""([^\""\\]|\\.)*\""", RegexOptions.Multiline);
ApplyHighlightToMatches(stringMatches, _stringBrush);
// 4.4 匹配C#关键字(边界匹配,避免匹配到单词中的子串)
string keywordPattern = @"\b(" + string.Join("|", _csharpKeywords) + @")\b";
MatchCollection keywordMatches = Regex.Matches(codeText, keywordPattern, RegexOptions.IgnoreCase);
ApplyHighlightToMatches(keywordMatches, _keywordBrush);
// 5. 恢复光标位置与选区
RtbCodeEditor.CaretPosition = caretPosition;
if (hasSelection)
{
RtbCodeEditor.Selection.Select(selectionRange.Start, selectionRange.End);
}
}
catch (Exception ex)
{
MessageBox.Show($"语法高亮失败:{ex.Message}", "错误提示", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
_isHighlighting = false;
}
}
/// <summary>
/// 为匹配到的语法元素应用着色
/// </summary>
/// <param name="matches">正则匹配结果集合</param>
/// <param name="brush">着色画笔</param>
private void ApplyHighlightToMatches(MatchCollection matches, SolidColorBrush brush)
{
foreach (Match match in matches)
{
if (match.Success && match.Length > 0)
{
// 获取匹配项的起始与结束位置对应的TextPointer
TextPointer startPointer = GetTextPointerByOffset(match.Index);
TextPointer endPointer = GetTextPointerByOffset(match.Index + match.Length);
if (startPointer != null && endPointer != null)
{
TextRange matchRange = new TextRange(startPointer, endPointer);
// 应用前景色
matchRange.ApplyPropertyValue(TextElement.ForegroundProperty, brush);
// 可选:设置粗体(关键字加粗)
if (brush == _keywordBrush)
{
matchRange.ApplyPropertyValue(TextElement.FontWeightProperty, FontWeights.Bold);
}
}
}
}
}
/// <summary>
/// 根据字符偏移量获取对应的TextPointer
/// </summary>
/// <param name="offset">字符偏移量</param>
/// <returns>对应的TextPointer</returns>
private TextPointer GetTextPointerByOffset(int offset)
{
TextPointer start = RtbCodeEditor.Document.ContentStart;
int currentOffset = 0;
while (start != null && currentOffset < offset)
{
if (start.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
int textLength = start.GetTextRunLength(LogicalDirection.Forward);
if (currentOffset + textLength > offset)
{
return start.GetPositionAtOffset(offset - currentOffset);
}
currentOffset += textLength;
}
start = start.GetNextContextPosition(LogicalDirection.Forward);
}
return start;
}
/// <summary>
/// 代码文本变化事件(更新行号+语法高亮)
/// </summary>
private void RtbCodeEditor_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
// 更新行号
UpdateLineNumbers();
// 执行语法高亮
HighlightCSharpSyntax();
}
/// <summary>
/// 键盘按下事件(优化编辑体验:Tab缩进、回车自动缩进)
/// </summary>
private void RtbCodeEditor_KeyDown(object sender, KeyEventArgs e)
{
// 1. Tab键:插入4个空格(代替默认Tab字符,保持格式统一)
if (e.Key == Key.Tab)
{
// 取消默认Tab行为
e.Handled = true;
// 插入4个空格作为缩进
RtbCodeEditor.CaretPosition.InsertTextInRun(" ");
}
// 2. 回车键:自动缩进(匹配上一行的缩进格式)
if (e.Key == Key.Enter)
{
// 取消默认回车行为(后续手动处理)
e.Handled = true;
// 获取当前行的缩进字符串
string currentIndent = GetCurrentLineIndent();
// 插入回车+缩进
RtbCodeEditor.CaretPosition.InsertTextInRun(Environment.NewLine + currentIndent);
// 调整光标位置(保持在缩进后)
RtbCodeEditor.CaretPosition = RtbCodeEditor.CaretPosition.GetPositionAtOffset(currentIndent.Length);
}
}
/// <summary>
/// 获取当前行的缩进字符串(空格)
/// </summary>
/// <returns>缩进字符串</returns>
private string GetCurrentLineIndent()
{
TextPointer caret = RtbCodeEditor.CaretPosition;
// 定位到当前行的起始位置
TextPointer lineStart = caret.GetLineStartPosition(0);
if (lineStart == null) return string.Empty;
// 提取当前行从起始到光标位置的文本
TextRange lineRange = new TextRange(lineStart, caret);
string lineText = lineRange.Text;
// 提取前面的空格(缩进部分)
StringBuilder indentBuilder = new StringBuilder();
foreach (char c in lineText)
{
if (c == ' ')
{
indentBuilder.Append(c);
}
else
{
break;
}
}
return indentBuilder.ToString();
}
}
五、程序测试与运行
- 编译项目:点击 Visual Studio 中的「生成」按钮,确保项目无编译错误,生成可执行文件;
- 运行程序:启动生成的
WpfCSharpCodeEditor.exe,程序窗口将居中显示,自动加载示例 C# 代码并完成语法高亮; - 功能测试:
- 基础编辑:输入、删除、复制、粘贴代码,验证行号是否实时更新,光标位置是否保持正常;
- 语法高亮:输入 C# 关键字(如
if、for)、注释(//、/* */)、字符串(""),验证是否能正确着色; - 编辑优化:按下 Tab 键,验证是否插入 4 个空格;按下回车键,验证是否自动继承上一行缩进;
- 滚动同步:滚动代码编辑区域,验证行号区域是否同步滚动,行号与代码行是否一一对应;
- 扩展测试:手动删除示例代码,输入自定义 C# 代码,验证语法高亮的稳定性与准确性。
运行效果:

六、功能扩展与优化建议
本次实现的简单 C# 代码编辑器已满足基础编辑需求,可根据实际场景进行以下扩展与优化,使其更加强大:
- 扩展语法支持:添加更多 C# 语法元素高亮(如运算符、常量、泛型),支持VB.NET、Python 等其他编程语言;
- 高级编辑功能:添加代码折叠(折叠命名空间、类、方法)、查找 / 替换、撤销 / 重做(完善
RichTextBox的撤销逻辑); - 格式优化:添加代码自动格式化(对齐大括号、调整缩进)、注释格式化、快捷键支持(如
Ctrl+S保存、Ctrl+F查找); - 错误提示:集成简单的语法错误检测(如缺少分号、大括号不匹配),显示错误标记与提示信息;
- 保存与打开:添加代码文件(
.cs)的打开与保存功能,支持编码格式选择(UTF-8、GBK); - 界面优化:添加深色 / 浅色主题切换、字体大小调整、行号高亮(当前行),提升视觉体验;
- 第三方控件集成:若需要更专业的功能,可集成 AvalonEdit、ICSharpCode.TextEditor 等成熟的代码编辑控件,降低开发成本。
七、总结
本文基于 WPF 框架,无需第三方控件,完整实现了一款支持简单 C# 代码编辑的工具,核心亮点在于:
- 轻量级无依赖:基于 WPF 内置
RichTextBox与正则表达式实现,无需引入额外库,部署简单; - 核心功能完备:支持语法高亮、行号显示、滚动同步、Tab 缩进、回车自动缩进,满足基础代码编辑需求;
- 界面友好:采用深色主题与等宽字体,符合程序员编辑习惯,支持窗口缩放适配;
- 逻辑可扩展:语法高亮与编辑逻辑解耦,便于后续扩展更多语法元素与高级功能。
通过本文的讲解,不仅可以掌握 WPF 实现简单代码编辑器的方法,还能深入理解RichTextBox富文本操作、正则表达式语法匹配、WPF 控件联动等核心知识点。在此基础上,可根据实际需求进行功能扩展,开发出更贴合开发场景的轻量级代码编辑工具。
以上就是基于WPF实现简单C#代码编辑功能的完整流程的详细内容,更多关于WPF实现C#代码编辑功能的资料请关注脚本之家其它相关文章!
