You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:实现WinForms中RichTextBox的逐字撤销(Undo)与重做(Redo)功能

解决RichTextBox逐单词撤销/重做的问题

我太懂你卡了3天的感受了——自带的RichTextBox.Undo()确实拉胯,要么清掉所有内容,要么触发奇怪的错误,完全满足不了逐单词撤销的需求。咱们先拆解下你现有代码的问题,再一步步实现靠谱的方案:

现有代码的核心问题

你当前的代码是把整个文本快照存在数组里,撤销的时候只是回到上一次的完整文本,这根本不是「逐单词」的逻辑,而且固定长度的数组还容易遇到容量不够的问题。要实现逐单词操作,得记录每一次输入的单词细节,而不是整个文本。

实现思路

我们需要:

  • 用两个栈分别存储「撤销操作记录」和「重做操作记录」(栈比数组更适合这种后进先出的场景)
  • 监听文本框的输入,识别出完整输入的单词,把单词的内容、插入位置等信息存到撤销栈里
  • 撤销时从栈里取出最后一次的单词操作,删除对应的内容并存到重做栈;重做则反过来

具体代码实现

1. 定义操作记录类

先创建一个类来存储每次单词输入的关键信息:

public class WordEditAction
{
    // 输入的单词内容
    public string Word { get; set; }
    // 单词在文本中的起始位置
    public int StartPosition { get; set; }
    // 单词+后续分隔符的总长度(比如输入"hello ",长度就是6)
    public int TotalLength { get; set; }
}

2. 初始化栈和状态标记

在你的窗体类里定义两个栈,以及一个标记防止撤销/重做时触发重复的文本变化事件:

private Stack<WordEditAction> _undoStack = new Stack<WordEditAction>();
private Stack<WordEditAction> _redoStack = new Stack<WordEditAction>();
// 标记当前是否正在执行撤销/重做,避免触发TextChanged循环
private bool _isProcessingUndoRedo = false;

3. 监听文本变化,记录单词

给richTextBoxPrintCtrl1绑定TextChanged事件,识别完整输入的单词:

private void richTextBoxPrintCtrl1_TextChanged(object sender, EventArgs e)
{
    // 如果正在撤销/重做,直接返回,避免循环触发
    if (_isProcessingUndoRedo) return;

    // 新输入内容后,重做栈就清空了(不能再重做之前的操作)
    _redoStack.Clear();

    string currentText = richTextBoxPrintCtrl1.Text;
    if (string.IsNullOrEmpty(currentText)) return;

    // 判断是否完成了一个单词输入(这里以空格、常见标点、回车作为分隔符)
    char lastChar = currentText.Last();
    var wordSeparators = new[] { ' ', ',', '.', '!', '?', '\n', '\t' };
    if (wordSeparators.Contains(lastChar))
    {
        // 从文本末尾往前找单词的起始位置
        int wordStartIndex = currentText.Length - 1;
        while (wordStartIndex > 0 && !wordSeparators.Contains(currentText[wordStartIndex - 1]))
        {
            wordStartIndex--;
        }

        // 提取单词(去掉末尾的分隔符)
        string inputWord = currentText.Substring(wordStartIndex, currentText.Length - wordStartIndex).Trim();
        if (!string.IsNullOrEmpty(inputWord))
        {
            // 计算总长度:单词长度 + 后面的分隔符长度
            int totalLength = currentText.Length - wordStartIndex;
            _undoStack.Push(new WordEditAction
            {
                Word = inputWord,
                StartPosition = wordStartIndex,
                TotalLength = totalLength
            });
        }
    }
}

4. 实现撤销按钮逻辑

private void btnUndo_Click(object sender, EventArgs e)
{
    if (_undoStack.Count == 0) return;

    _isProcessingUndoRedo = true;
    var lastAction = _undoStack.Pop();
    // 把当前操作存入重做栈,方便后续重做
    _redoStack.Push(lastAction);

    // 删除对应的单词和分隔符
    richTextBoxPrintCtrl1.Text = richTextBoxPrintCtrl1.Text.Remove(lastAction.StartPosition, lastAction.TotalLength);
    // 把光标定位到删除后的位置
    richTextBoxPrintCtrl1.SelectionStart = lastAction.StartPosition;
    _isProcessingUndoRedo = false;
}

5. 实现重做按钮逻辑

private void btnRedo_Click(object sender, EventArgs e)
{
    if (_redoStack.Count == 0) return;

    _isProcessingUndoRedo = true;
    var lastUndoneAction = _redoStack.Pop();
    // 把操作放回撤销栈
    _undoStack.Push(lastUndoneAction);

    // 插入单词和对应的分隔符
    string insertContent = lastUndoneAction.Word + richTextBoxPrintCtrl1.Text.Substring(lastUndoneAction.StartPosition, lastUndoneAction.TotalLength - lastUndoneAction.Word.Length);
    richTextBoxPrintCtrl1.Text = richTextBoxPrintCtrl1.Text.Insert(lastUndoneAction.StartPosition, insertContent);
    // 光标定位到插入后的位置
    richTextBoxPrintCtrl1.SelectionStart = lastUndoneAction.StartPosition + lastUndoneAction.TotalLength;
    _isProcessingUndoRedo = false;
}

一些优化建议

  • 可以根据你的需求调整wordSeparators数组,添加更多符合场景的分隔符
  • 如果需要支持删除单词的撤销(比如用户删除了一个单词,也能撤销这个删除),可以扩展WordEditAction类,增加操作类型(添加/删除)
  • 对于粘贴多个单词的场景,可以额外处理Paste事件,把粘贴的内容拆分单词后逐个记录

内容的提问来源于stack exchange,提问作者Amirmohammad Ramezani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 19:47:34