Hi guys,

how to make the default undo/redo in richTextBox support undo/redo multiple actions?????????

Thanks

[EL-Prince]

Recommended Answers

All 12 Replies

This is the default behavior. Just call .Undo() multiple times.

yes richtextbox has inbuilt functions of undo and redo.

Hi guys,

I think I didn't make my point very clear, what I want to do is undo/redo single action at a time, not multiple actions at a time like the inbuilt undo/redo functions.

Thanks

[EL-Prince]

What do you consider a single action?

There's a difference between notepad and MSWord the former support one action undo/redo which means if you edit, delete, insert a new text, and clicked undo it will retrieve the old text before editing, while the later (MSWord) has the ability to undo/redo multi actions till you reach what you want.

I hope I made my point clearer.

Thanks

[EL-Prince]

With notepad when you hit CTRL+Z it does an undo. If you hit CTRL+Z again it doesn't do the next undo sequence, it instead redo's what you just undid.

Now with the richtextbox you can call .Undo() and it undoes the last operation. You can all .Undo() again and it goes back another operation (not redoing the previous undo like notepad) which is exactly like word. This already behaves the way you are expecting...

@sknake
I think you're wrong about this concept, cause this NOT what happens I knew from my reading to support multiple undos/redos you should store the object states in a stake. Undo action used to go to a previous states, and redo action used to go to a next states that happened already, but the problem here I couldn't figure out how to store the richTextBox object states in a stake.

I'd be glad if someone here explain how to do this, this why I submitted this thread in the first place.

Thanks a bunch specially you sknake you helped me a lot in other threads before!!!!

Thanks again,

[EL-Prince]

Ok then upload a sample project demonstrating the undesired behavior of the rtf control. You have indicated from reading it doesn't do what you want but I tested the control earlier and it did. Oddly enough I was in the middle of porting over some c++ code to handle multiple undos since I read the control didn't support it, then I realized the articles I read were dated and no longer accurate.

Here's a sample, but you should tell me how is undo() method support multiple undos by itself with no additional code???

public class UndoStack
{
    // Fields
    internal bool AcceptChanges = true;
    private int actionCountInUndoGroup;
    private Stack<IUndoableOperation> redostack = new Stack<IUndoableOperation>();
    public TextEditorControlBase TextEditorControl = null;
    private int undoGroupDepth;
    private Stack<IUndoableOperation> undostack = new Stack<IUndoableOperation>();

    // Events
    public event EventHandler ActionRedone;

    public event EventHandler ActionUndone;

    // Methods
    public void AssertNoUndoGroupOpen()
    {
        if (this.undoGroupDepth != 0)
        {
            this.undoGroupDepth = 0;
            throw new InvalidOperationException("No undo group should be open at this point");
        }
    }

    public void ClearAll()
    {
        this.AssertNoUndoGroupOpen();
        this.undostack.Clear();
        this.redostack.Clear();
        this.actionCountInUndoGroup = 0;
    }

    public void ClearRedoStack()
    {
        this.redostack.Clear();
    }

    public void EndUndoGroup()
    {
        if (this.undoGroupDepth == 0)
        {
            throw new InvalidOperationException("There are no open undo groups");
        }
        this.undoGroupDepth--;
        if ((this.undoGroupDepth == 0) && (this.actionCountInUndoGroup > 1))
        {
            this.undostack.Push(new UndoQueue(this.undostack, this.actionCountInUndoGroup));
        }
    }

    protected void OnActionRedone()
    {
        if (this.ActionRedone != null)
        {
            this.ActionRedone(null, null);
        }
    }

    protected void OnActionUndone()
    {
        if (this.ActionUndone != null)
        {
            this.ActionUndone(null, null);
        }
    }

    public void Push(IUndoableOperation operation)
    {
        if (operation == null)
        {
            throw new ArgumentNullException("operation");
        }
        if (this.AcceptChanges)
        {
            this.StartUndoGroup();
            this.undostack.Push(operation);
            this.actionCountInUndoGroup++;
            if (this.TextEditorControl != null)
            {
                this.undostack.Push(new UndoableSetCaretPosition(this, this.TextEditorControl.ActiveTextAreaControl.Caret.Position));
                this.actionCountInUndoGroup++;
            }
            this.EndUndoGroup();
            this.ClearRedoStack();
        }
    }

    public void Redo()
    {
        this.AssertNoUndoGroupOpen();
        if (this.redostack.Count > 0)
        {
            IUndoableOperation uedit = this.redostack.Pop();
            this.undostack.Push(uedit);
            uedit.Redo();
            this.OnActionRedone();
        }
    }

    public void StartUndoGroup()
    {
        if (this.undoGroupDepth == 0)
        {
            this.actionCountInUndoGroup = 0;
        }
        this.undoGroupDepth++;
    }

    public void Undo()
    {
        this.AssertNoUndoGroupOpen();
        if (this.undostack.Count > 0)
        {
            IUndoableOperation uedit = this.undostack.Pop();
            this.redostack.Push(uedit);
            uedit.Undo();
            this.OnActionUndone();
        }
    }

    // Properties
    public bool CanRedo
    {
        get
        {
            return (this.redostack.Count > 0);
        }
    }

    public bool CanUndo
    {
        get
        {
            return (this.undostack.Count > 0);
        }
    }

    public int RedoItemCount
    {
        get
        {
            return this.redostack.Count;
        }
    }

    public int UndoItemCount
    {
        get
        {
            return this.undostack.Count;
        }
    }

    // Nested Types
    private class UndoableSetCaretPosition : IUndoableOperation
    {
        // Fields
        private TextLocation pos;
        private TextLocation redoPos;
        private UndoStack stack;

        // Methods
        public UndoableSetCaretPosition(UndoStack stack, TextLocation pos)
        {
            this.stack = stack;
            this.pos = pos;
        }

        public void Redo()
        {
            this.stack.TextEditorControl.ActiveTextAreaControl.Caret.Position = this.redoPos;
        }

        public void Undo()
        {
            this.redoPos = this.stack.TextEditorControl.ActiveTextAreaControl.Caret.Position;
            this.stack.TextEditorControl.ActiveTextAreaControl.Caret.Position = this.pos;
        }
    }
}

Thanks

[El-Prince]

What does that code have to do with a rich text box? Take this for example:

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

namespace daniweb.rtfundo
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      this.BringToFront();
      this.richTextBox1.Focus();
      new Action(DoRTFOperations).BeginInvoke(null, null);
    }

    private void DoRTFOperations()
    {
      char[] series = new char[] { '!', '@', '#', '.' };
      foreach (char c in series)
      {
        for (int i1 = 0; i1 < 10; i1++)
        {
          richTextBox1.Invoke(new MethodInvoker(
            delegate()
          {
            richTextBox1.Focus();
            System.Windows.Forms.SendKeys.SendWait(Convert.ToString(c));
          }));
        }
        richTextBox1.Invoke(new MethodInvoker(
          delegate()
          {
            button1.Focus();
            System.Threading.Thread.Sleep(10);
            richTextBox1.Focus();
          }));
        System.Threading.Thread.Sleep(500);
      }
    }

    private void button2_Click(object sender, EventArgs e)
    {
      Console.WriteLine("Text before undo: " + richTextBox1.Text);
      richTextBox1.Undo();
      Console.WriteLine("Text after undo: " + richTextBox1.Text);
      Console.WriteLine("-");
    }
  }
}

That is more than enough code that isn't relevant to generating multiple operations in a RTF box with user input instead of modifying the text directly. Now lets start an undo series:

Text before undo: !!!!!!!!!!@@@@@@@@@@##########..........
Text after undo: !!!!!!!!!!@@@@@@@@@@##########
-
Text before undo: !!!!!!!!!!@@@@@@@@@@##########
Text after undo: !!!!!!!!!!@@@@@@@@@@
-
Text before undo: !!!!!!!!!!@@@@@@@@@@
Text after undo: !!!!!!!!!!
-
Text before undo: !!!!!!!!!!
Text after undo: 
-
Text before undo: 
Text after undo: 
-

All I did was keep pressing button2, which had the code:

private void button2_Click(object sender, EventArgs e)
    {
      Console.WriteLine("Text before undo: " + richTextBox1.Text);
      richTextBox1.Undo();
      Console.WriteLine("Text after undo: " + richTextBox1.Text);
      Console.WriteLine("-");
    }

That being said I conclude that the RTF box is capable of undoing multiple operations.

@sknake
Hi,

Thanks for sharing I tried the code, and it worked fine, but I couldn't know how you made the undo function works like this I tried to use it against my entered text to the textbox and undo the changes, but no good happened, if you could help me with this I'll be thankful!!!

here's the project link http://uploading.com/files/2693d4me/undoRedoStack.rar/

Thanks,

[EL-Prince]

Every time I go to one of those file upload sites it crashes my browser and causes a memory dump on my machine ... so I would upload it here if you want me to take a look at it :)

Now without seeing your project I can guess what is happening. First off this is from the help file:

The TextBoxBase..::.Undo method does not work with the KeyPress or TextChanged events.

Next -- I don't know what causes the richTextBox to note when a change starts happening or stops, but I know if the control loses focus then it logs a change as complete. If you notice in my code I call focus to the button then back to the RTF control for this reason. When you are testing your application are you focusing another object then bringing the cursor back to the RTF control? You can also call .ClearUndo() to clear the undo buffer backlog which would break multiple undos.

Taking a look at the code for TextBoxBase I can see other operations that change the undo history:

public void ClearUndo()
    {
        if (base.IsHandleCreated)
        {
            base.SendMessage(0xcd, 0, 0);
        }
    }

So 0xcd must be clearing the undo buffer. Text selection seems like it can also clear the undo backlog:

internal virtual void SetSelectedTextInternal(string text, bool clearUndo)
    {
        if (!base.IsHandleCreated)
        {
            this.CreateHandle();
        }
        if (text == null)
        {
            text = "";
        }
        base.SendMessage(0xc5, 0, 0);
        if (clearUndo)
        {
            base.SendMessage(0xc2, 0, text);
            base.SendMessage(0xb9, 0, 0);
            this.ClearUndo();
        }
        else
        {
            base.SendMessage(0xc2, -1, text);
        }
        base.SendMessage(0xc5, this.maxLength, 0);
    }

Which is called from a property:

[SRDescription("TextBoxSelectedTextDescr"), Browsable(false), SRCategory("CatAppearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public virtual string SelectedText
    {
        get
        {
            int num;
            int num2;
            this.GetSelectionStartAndLength(out num, out num2);
            return this.Text.Substring(num, num2);
        }
        set
        {
            this.SetSelectedTextInternal(value, true);
        }
    }

So if you set the text from code it will clear the undo operations (this makes sense .. I knew under the hood it was doing something screwy with undo which is why I posted the sendkeys code for testing the undo).

Take a look at how you're testing the undo functionality and see if you can upload your project to the thread instead of a file upload site. Click on "Go Advanced" next to "Post Quick Reply" and from there you can select "Manage Attachments" and upload your file.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.