C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > WPF实现C#代码编辑功能

基于WPF实现简单C#代码编辑功能的完整流程

作者:code_shenbing

在开发轻量级开发工具、代码演示程序或嵌入式调试工具时,常常需要集成简单的 C# 代码编辑功能,WPF能够轻松实现一款支持 C# 代码高亮、基础编辑、语法提示的简单代码编辑器,本文将详细讲解 WPF 实现简单 C# 代码编辑功能的完整流程,需要的朋友可以参考下

引言

在开发轻量级开发工具、代码演示程序或嵌入式调试工具时,常常需要集成简单的 C# 代码编辑功能。WPF(Windows Presentation Foundation)凭借其灵活的界面定制能力、数据绑定特性和控件扩展能力,能够轻松实现一款支持 C# 代码高亮、基础编辑、语法提示的简单代码编辑器。这款编辑器无需依赖复杂的第三方控件库,基于 WPF 内置控件即可完成核心功能,满足轻量级 C# 代码编辑的场景需求。本文将详细讲解 WPF 实现简单 C# 代码编辑功能的完整流程,包括界面设计、语法高亮、基础编辑逻辑与功能优化。

一、核心功能规划

一款简单实用的 WPF C# 代码编辑器,需覆盖基础的代码编辑与视觉优化需求,本次实现的核心功能如下:

  1. 基础代码编辑:支持 C# 代码的输入、删除、复制、粘贴、撤销 / 重做,兼容常规文本编辑操作;
  2. C# 语法高亮:对关键字(如usingclassif)、注释(单行//、多行/* */)、字符串(""包裹)进行差异化着色,提升代码可读性;
  3. 代码格式辅助:支持行号显示、制表符缩进(Tab 键)、自动换行,模拟专业编辑器的基础体验;
  4. 界面适配:采用弹性布局,支持窗口缩放,代码编辑区域与行号区域同步滚动;
  5. 轻量级无依赖:基于 WPF 内置TextBox(或RichTextBox)实现,无需引入第三方代码编辑库(如 AvalonEdit),降低项目耦合度。

二、开发环境与前置准备

1. 开发环境

2. 前置知识

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>

界面设计说明

  1. 采用深色主题(背景#1E1E1E),符合程序员的代码编辑习惯,降低视觉疲劳;
  2. 行号区域固定宽度,使用TextBlock显示行号,颜色为灰色(#808080),与代码区域形成视觉区分;
  3. 代码编辑区域使用RichTextBox,支持富文本着色(为后续语法高亮提供基础),启用AcceptsReturnAcceptsTab,支持回车换行与 Tab 缩进;
  4. 两个区域均嵌套在ScrollViewer中,通过ScrollChanged事件实现滚动同步,保证行号与代码行一一对应;
  5. 字体选用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();
    }
}

五、程序测试与运行

  1. 编译项目:点击 Visual Studio 中的「生成」按钮,确保项目无编译错误,生成可执行文件;
  2. 运行程序:启动生成的WpfCSharpCodeEditor.exe,程序窗口将居中显示,自动加载示例 C# 代码并完成语法高亮;
  3. 功能测试:
    • 基础编辑:输入、删除、复制、粘贴代码,验证行号是否实时更新,光标位置是否保持正常;
    • 语法高亮:输入 C# 关键字(如iffor)、注释(///* */)、字符串(""),验证是否能正确着色;
    • 编辑优化:按下 Tab 键,验证是否插入 4 个空格;按下回车键,验证是否自动继承上一行缩进;
    • 滚动同步:滚动代码编辑区域,验证行号区域是否同步滚动,行号与代码行是否一一对应;
  4. 扩展测试:手动删除示例代码,输入自定义 C# 代码,验证语法高亮的稳定性与准确性。

运行效果:

六、功能扩展与优化建议

本次实现的简单 C# 代码编辑器已满足基础编辑需求,可根据实际场景进行以下扩展与优化,使其更加强大:

  1. 扩展语法支持:添加更多 C# 语法元素高亮(如运算符、常量、泛型),支持VB.NET、Python 等其他编程语言;
  2. 高级编辑功能:添加代码折叠(折叠命名空间、类、方法)、查找 / 替换、撤销 / 重做(完善RichTextBox的撤销逻辑);
  3. 格式优化:添加代码自动格式化(对齐大括号、调整缩进)、注释格式化、快捷键支持(如Ctrl+S保存、Ctrl+F查找);
  4. 错误提示:集成简单的语法错误检测(如缺少分号、大括号不匹配),显示错误标记与提示信息;
  5. 保存与打开:添加代码文件(.cs)的打开与保存功能,支持编码格式选择(UTF-8、GBK);
  6. 界面优化:添加深色 / 浅色主题切换、字体大小调整、行号高亮(当前行),提升视觉体验;
  7. 第三方控件集成:若需要更专业的功能,可集成 AvalonEdit、ICSharpCode.TextEditor 等成熟的代码编辑控件,降低开发成本。

七、总结

本文基于 WPF 框架,无需第三方控件,完整实现了一款支持简单 C# 代码编辑的工具,核心亮点在于:

  1. 轻量级无依赖:基于 WPF 内置RichTextBox与正则表达式实现,无需引入额外库,部署简单;
  2. 核心功能完备:支持语法高亮、行号显示、滚动同步、Tab 缩进、回车自动缩进,满足基础代码编辑需求;
  3. 界面友好:采用深色主题与等宽字体,符合程序员编辑习惯,支持窗口缩放适配;
  4. 逻辑可扩展:语法高亮与编辑逻辑解耦,便于后续扩展更多语法元素与高级功能。

通过本文的讲解,不仅可以掌握 WPF 实现简单代码编辑器的方法,还能深入理解RichTextBox富文本操作、正则表达式语法匹配、WPF 控件联动等核心知识点。在此基础上,可根据实际需求进行功能扩展,开发出更贴合开发场景的轻量级代码编辑工具。

以上就是基于WPF实现简单C#代码编辑功能的完整流程的详细内容,更多关于WPF实现C#代码编辑功能的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文