3

I have a WinForm text editor.

I would like to be able to allow the user to undo and redo changes in the Rich Text Box, like they can in Microsoft Word.

I have spent the past week or so researching how to do this, and most results seem to be regarding graphics applications.

The standard richTextBox1.Undo(); gives disappointing results, as it undoes everything that the user has written.

Does anybody have any idea how I could implement effective undo/redo? Preferably one which undoes/redoes the action word-by-word as opposed to character-by-character.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Toby
  • 377
  • 1
  • 10
  • 23

2 Answers2

3

This is a very basic idea, and I'm sure that many improvements could be made.

I would create a String Array and incrementally store the value of the RichTextBox (In the TextChanged event, under your own conditions) in the array. As you store the value, increment the value of a counter, say stackcount. When the user undoes, decrement the stackcount and set the RichTextBox.Text = array(stackcount). If they redo, then increment the value of the counter and set the value again. If they undo and then change the text, then clear all values onwards.

I am sure that many other people may have better suggestions/changes for this, so please post in comments and I will update, or edit it yourself!

Example in C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace RedoUndoApp
{
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    public string[] RTBRedoUndo;
    public int StackCount = 0;
    public int OldLength = 0;
    public int ChangeToSave = 5;
    public bool IsRedoUndo = false;

    private void Form1_Load(object sender, EventArgs e)
    {
        RTBRedoUndo = new string[10000];
        RTBRedoUndo[0] = "";
    }

    private void undo_Click(object sender, EventArgs e)
    {
        IsRedoUndo = true;
        if (StackCount > 0 && RTBRedoUndo[StackCount - 1] != null)
        {
            StackCount = StackCount - 1;
            richTextBox1.Text = RTBRedoUndo[StackCount];
        }
    }

    private void redo_Click(object sender, EventArgs e)
    {
        IsRedoUndo = true;
        if (StackCount > 0 && RTBRedoUndo[StackCount + 1] != null)
        {
            StackCount = StackCount + 1;
            richTextBox1.Text = RTBRedoUndo[StackCount];
        }

    }

    private void richTextBox1_TextChanged(object sender, EventArgs e)
    {
        if (IsRedoUndo == false && richTextBox1.Text.Substring(richTextBox1.Text.Length - 1, 1) == " ")//(Math.Abs(richTextBox1.Text.Length - OldLength) >= ChangeToSave && IsRedoUndo == false)
        {
            StackCount = StackCount + 1;
            RTBRedoUndo[StackCount] = richTextBox1.Text;
            OldLength = richTextBox1.Text.Length;
        }
    }

    private void undo_MouseUp(object sender, MouseEventArgs e)
    {
        IsRedoUndo = false;
    }

    private void redo_MouseUp(object sender, MouseEventArgs e)
    {
        IsRedoUndo = false;
    }
}
}
JosephGarrone
  • 4,081
  • 3
  • 38
  • 61
  • I had to convert it to C#. I've never done VB. But it didn't go too well, unfortunately. I'm going to try another method: http://www.codeproject.com/Articles/6947/Implementing-Object-Undo-and-Redo-capabilities-in – Toby Apr 09 '13 at 18:12
  • Thanks. But I'm getting some errors. Could you help me decipher them? – Toby Apr 10 '13 at 01:09
  • if (RTBRedoUndo[StackCount - 1 != null]) error: Cannot implicitly convert type 'bool' to 'int' – Toby Apr 10 '13 at 01:12
  • Sorry, square brackets in the wrong place. Should close square brackets right after "1", and remove from after Null. Code has been edited. – JosephGarrone Apr 10 '13 at 01:43
  • Right. I've changed it. But now, when I try to undo, it crashes. Object reference not set to an instance of an object. This is on line: if (RTBRedoUndo[StackCount - 1] != null) Am I being stupid here? I'm still new to programming, so forgive me if I am. – Toby Apr 10 '13 at 01:46
  • And the same happens if I try to redo. – Toby Apr 10 '13 at 01:53
  • You need to set the array size of the RTBRedoUndo array, Let me look up how to do so in C#. Code has been changed – JosephGarrone Apr 10 '13 at 01:59
  • About to paste major edit. I actually wrote this in Visual Studio and tested. – JosephGarrone Apr 10 '13 at 02:19
  • Again, sorry to sound stupid, but how do I create the FormLoaded event? I can't seem to find out how... @Asryael – Toby Apr 10 '13 at 02:19
  • Sorry, I dont actually know much C# (Above example was first C# application for me). FormLoaded is the VB.net name. Try my above example in a sample application first. – JosephGarrone Apr 10 '13 at 02:22
  • Alright, I found the "Form load" event, but I still get the same error, even though I input the RTBRedoUndo array. – Toby Apr 10 '13 at 02:25
  • No problem! It is very modifiable, simply add in your own conditions for storing undo/redo states and you're set! – JosephGarrone Apr 10 '13 at 02:38
  • The only thing I'd like to be able to do is undo/redo word by word. Is that easy/possible? – Toby Apr 10 '13 at 03:02
  • Yes, simply look for a space as the last character typed in text changed, and store the value. – JosephGarrone Apr 10 '13 at 03:04
  • How is that achieved? Sorry, I'm new to this and need stuff explaining. – Toby Apr 10 '13 at 03:08
  • Simply get the last character in the richtextbox by using richtextbox.text.substring(richtextbox.length-1, 1) and checking if it equals " ". Going out. Might be able to update example later. – JosephGarrone Apr 10 '13 at 03:12
  • Alright. If you could I'd really appreciate it. I'm going to head to bed now anyway. Thanks for your help, bud. – Toby Apr 10 '13 at 03:14
  • Updated. Make sure you get ALL the code. I changed some things around. – JosephGarrone Apr 10 '13 at 08:11
  • It does, just make sure that you have a space after the last word. If you want, you could add a check to see if the last character is a comma, fullstop or dash to allow for all situations. – JosephGarrone Apr 10 '13 at 11:06
  • How is that done? And what I did was type 6 or 7 words, undo them all and then try to redo them. None of them appeared. – Toby Apr 10 '13 at 11:09
  • Make sure you copy my exact code. It works for me. I can't do this whole project for you. Look at my code, and try to understand it. HINT: `if (IsRedoUndo == false && richTextBox1.Text.Substring(richTextBox1.Text.Length - 1, 1) == " ")` – JosephGarrone Apr 10 '13 at 11:13
  • I can't figure it out... Dang. – Toby Apr 10 '13 at 12:18
  • `if (IsRedoUndo == false && (richTextBox1.Text.Substring(richTextBox1.Text.Length - 1, 1) == " " || richTextBox1.Text.Substring(richTextBox1.Text.Length - 1, 1) == "," || so on and so forth))` – JosephGarrone Apr 10 '13 at 13:57
  • Thanks. I've implemented it. – Toby Apr 10 '13 at 22:23
2

One way to do this is use the TextChanged event to periodically store the contents of richtextbox.text in an array or list, as a stack. When you undo, "pop the stack" and copy the most recent version on the stack into richtextbox.text.

TextChange can determine whether a change should be saved onto the stack, whether a new word, line, or character.

xpda
  • 15,585
  • 8
  • 51
  • 82
  • How do I do that? Sorry if that's a stupid question.. I'm still learning programming. – Toby Apr 09 '13 at 01:04
  • The only problem here is you will not have Redo. Once you pop the stack, it is lost. – James Lawruk Apr 09 '13 at 01:10
  • Ah. That's an issue. Realistically, I need both undo and redo. – Toby Apr 09 '13 at 01:12
  • You can keep the "popped" elements in the stack array to be used for redo. It will technically not be a stack, but you just keep a pointer to designate the current "top" and the maximum "top", the difference of which is the number of possible redos. – xpda Apr 09 '13 at 01:18
  • In the textchanged event of the richtextbox, save the text every time it is called.. After you get that working, you can add some tests limit undo/redo to words rather than just characters. – xpda Apr 09 '13 at 01:21
  • Will this work for undoing and redoing steps in a rich text box? http://www.codeproject.com/Articles/6947/Implementing-Object-Undo-and-Redo-capabilities-in – Toby Apr 09 '13 at 01:32
  • Yes, that's the overall idea. The modification history will be pretty simple on the richtextbox. – xpda Apr 09 '13 at 15:01