none
How to Implement Undo/Redo functionality in a List<T> RRS feed

  • Question

  • Hi everyone, 

    I'm trying to add a Undo/Redo functionality on my program. Like so 

    private List<OrderItem> _orderItems = new List<OrderItem>(); public List<OrderItem> AddToOrder(PRODUCT product) { var item = _orderItems.FirstOrDefault(x => { //Check if the item exist in the list of orders return product != null && x.Model == product.MODEL; }); if (item == null) { //add the order var items = new OrderItem { Id = product.ID, Quantity = 1, Description = product.DESCRIPTION, Model = product.MODEL, UnitPrice = product.UNITPRICE, CurrentCount = product.CURRENTCOUNT, IsTaxable = product.TAXABLE }; _orderItems.Add(items); } else {

    //increase the quantity _orderItems.Where(x => x.Id == product.ID).ToList().ForEach(x=>x.Quantity=x.Quantity +1);      } return _orderItems; }

    The problem lies when the item is already on the list, i am not sure how to track it for undo/redo operation.

    Any idea how to better implement this will be really appreciated.

    Regards



    • Edited by Dikong42 Thursday, January 18, 2018 4:16 PM
    Thursday, January 18, 2018 1:32 PM

All replies

  • You're not going to be able to implement undo/redo simply by using a list. Undo/redo can be difficult (or even impossible) to implement depending upon your architecture. I recommend that you look into the memento pattern for supporting undo at an object level.

    For larger levels of undo a command/message architecture may be needed. For example, adding an order would consist of sending an "add" command. To undo you'd send the corresponding "remove" command. Of course if that order has already started processing then undo may not even be possible. Again, completely depends upon your architecture.

    Given your code, it seems like add to order will add an item or update the quantity. You aren't going to be able to undo that unless you also implement a remove from order logic that does the inverse. But without the original order you aren't going to be able to know that. An undo/redo buffer comes into play here. I would recommend that you read up on how UIs implement undo/redo to get some ideas.


    Michael Taylor http://www.michaeltaylorp3.net

    Thursday, January 18, 2018 3:10 PM
    Moderator
  • Hi, thanks for taking time to read my post. 

    I've just read about the mamento pattern but how about using a Stack<T>.

    I guess i can store the list of orders in a stack i just have some difficulty debugging my code.

    void Main()
    {
    	Stack<List<Customer>> clist=new Stack<List<Customer>>();
    	List<Customer> c = new List<Customer>();
    	c.Add(new Customer {FirstName="A", LastName="B"});
    	clist.Push(c);
    	c.Add(new Customer {FirstName="C", LastName="D"});
    	clist.Push(c);
    	
    }
    
    public class Customer
    {
    	public string FirstName { get; set; }
    	public string LastName { get; set;}
    }

    I am expecting the code to fill the stack with a single customer on the first push, and then two customers on the second push but when trying to see the content of my stack, I received a two customers on the first push and also on the second push. It seems like it was duplicated. Will you be so kind to pinpoint what i'm doing wrong here?

    Regards


    Thursday, January 18, 2018 4:14 PM
  • You're combining a list and stack. Not sure why you'd be doing that. Your first push is adding the c list you created to the stack. You then push it again in the next call. Since this is a ref type you're pushing the same list twice. Both entries in the stack point to the same list. I suspect you just want to push/pop customers.

    var clist = new Stack<Customer>();
    c.Push(new Customer { });
    c.Push(new Customer {});
    
    //Stack now has 2 customers on it

    A stack still isn't going to get any undo/redo implementation working but it could be the start of the infrastructure need to support undo. But pushing customer doesn't really do anything useful here. You aren't specifying what is changing about each customer. Undo is going to require a lot more information that just what a customer would provide. You need to know what operation was done (customer added, updated, etc), what customer(s) were impacted, etc. At a minimum you're going to need some sort of undo type(s) to represent the action(s) in your system and the data need to track it. Here's a very, very simple example.

    public interface IAction
    {
       void Undo ();
    }
    
    public class AddCustomerAction : IAction
    {
       public AddCustomerAction ( int customerId )
       { }
    
       public void Undo ()
       {
          //Logic to remove a customer given the ID passed to the constructor
       }
    }
    
    public class UpdateCustomerAction : IAction
    {
       //Add any parameters need to track what was updated
       public UpdateCustomerAction ( int customerId, string actionPerformed )
       {
       }
    
       public void Undo ()
       {
          //Logic to undo the update to the customer based upon
          //the parameters passed to constructor
       }
    }
    
    //Stack to track the actions
    Stack<IAction> undoStack = new Stack<IAction>();
    
    //Example usage in UI call that adds a new customer
    void AddCustomer ( Customer customer )
    {
       //Update your system with new customer
       ...
    
       //Add the "add" to the stack so it can be undone
       undoStack.Add(new AddCustomerAction(customer.Id));
    }
    
    void UpdateCustomerQuantity ( Customer customer )
    {
       //Update system with new quantity
    
       //Record in undo stack
       undoStack.Add(new UpdateCustomerAction(customer.Id, "UpdateQuantity")
    }
    
    //Implementing the "undo" button
    void OnUndo ()
    {
       if (undostack.Count == 0)
          return;
    
       //Get the last action performed
       var action = undoStack.Pop();
       action.Undo();
    }

    This is a very simplistic approach. There are many different ways to implement it. You might even find a third party undo library that might be helpful. Undo is not a simple process so if you're not already comfortable with data structures, collections and ref types you might want to bone up on them first.


    Michael Taylor http://www.michaeltaylorp3.net

    Thursday, January 18, 2018 4:43 PM
    Moderator
  • Oh, i really don't know it was a ref type. Basically what i'm thinking was saving the whole list in every pop, maybe requires a lot of memory but this is just a simple operation.  So when the users adds something on the list, i will create another list, and so on.

    I'll try your suggestion. 

    Many thanks

    Thursday, January 18, 2018 5:04 PM
  • Of course is a stack collection the class which you need for do and undo. 

    However, read for the rest all Michael wrote. 

    As addition, it can be a memory eater in the way you do it, because you lock all objects from releasing by the GC. So think twice if you do it with an X86 environment.

    (Somehow I get the idea Michael was first starting to write about the stack mechanism before he switched to the collection)

    :-) 


    Success Cor


    Thursday, January 18, 2018 5:11 PM
  • Hello,

     After reading the other replies, did you get a solution? If was my project for an Undo/Redo

    function then I would use List to hold Customer data, and Stack to hold changes per

    Customer Entry, like address or phone numbers. There limits as the other have already

    explained.  You could use a Dynamic Circular Que for undo/redo and use the current index

    as a reference to the most recent update and back track from there for Undo.  There would

    be a lot of overhead in code to maintain a large buffer with any mechanism implemented.

     

     Hope this helps :)

    Friday, January 19, 2018 12:38 AM
  • "If was my project for an Undo/Redo

    function then I would use List to hold Customer data, and Stack to hold changes per

    Customer Entry, like address or phone numbers. "

    Yes, this is exactly what i am trying to do right now, but i do have a different problem.
    Lets say i have a list of order for storing current order, and an order stack for my undo operation. When the user add a new order/entity, i will push that to the stack, when the user add again the same product/order, i will push it again to the stack but i will just update my list showing just showing a single order but will update the order quantity.

    public class Order
    {
    	public int Id { get; set; }
    	public string OrderModel { get; set; }
    	public int Quantity { get; set;}
    }

    Example

    OrderId=1
    OrderModel="Product A"
    ProductQuantity=10

    ProductId = 1
    ProductModel = "Product A"
    ProductQuantity = 5

    Expected Output
    ProductId = 1
    ProductModel = "Product A"
    ProductQuantity = 5


    Since i have two items on my stack, undoing my operation is a bit tricky to me. I need now to merge the objects/entity on my stack and show it as a list. Since stack is a collection as well, i can just cast it to a list. My problem is when i need to undo and merge similar items on my stack.


    • Edited by Dikong42 Friday, January 19, 2018 1:21 PM
    Friday, January 19, 2018 1:20 PM
  • Hi Dikong42,

    What is the similar standard?

    >>My problem is when i need to undo and merge similar items on my stack.

    You could try to foreach the stack collection and compare all of them. If you similar means the same, after compare, delete one of the same one.

    If you want to undo, try to use Stack.Pop method.

    https://msdn.microsoft.com/en-us/library/system.collections.stack.pop.aspx

    Best Regards,

    Wendy


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, January 29, 2018 4:26 PM
    Moderator