Go Premium for a chance to win a PS4. Enter to Win

x
?
Solved

Undo Changes DataGridView

Posted on 2010-08-25
9
Medium Priority
?
7,210 Views
Last Modified: 2013-12-17
Experts,

I`ve spent too much time to figure out a way to undo changes [Delete Row, Add Row, Edit Cells] in a DataGridView. I have context menu with an "Undo" option.

The DataSource for the DataGridView is a regular List object.

I`m using C# .NET 3.5 VS2008

//Conext Menu
  //Edit Row Context Menu
        private void EditRowCM_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            switch (e.ClickedItem.Name)
            {
                case "AddRowTSMI":
                    AddMotRow();
                    break;

                case "DeleteRowTSMI":
                    DeleteMotRow();
                    break;

                case "DeleteSelectedTSMI":
                    DeleteSelected();
                    break;

                case "UndoEditTSMI":
                    UndoEdit();
                    break;

                default:
                    break;
            }
        }

    private void DeleteRow()
        {
            if (this.GridView.SelectedRows.Count > 0)
            {
                if (this.GridView.SelectedRows[0].Index == 0)
                {
                    MessageBox.Show("Cannot edit the first Row");
                }

                else if (this.GridView.SelectedRows[0].Index == this.GridView.Rows.Count - 1)
                {
                    MessageBox.Show("Cannot edit the last Row");
                }

                else
                {
                    this.GridView.Rows.RemoveAt(this.GridView.SelectedRows[0].Index);
                }
            }

            else
            {
                MessageBox.Show("No Row Selected - Or Select the full Row");
            }
        }

        private void DeleteSelected()
        {
            foreach (DataGridViewCell Cell in this.GridView.SelectedCells)
            {
                if (Cell.ColumnIndex == 0)
                {
                }

                else if (Cell.RowIndex == 0)
                {
                }

                else if (Cell.RowIndex == this.GridView.RowCount - 1)
                {
                }

                else
                {
                    Cell.Value = null;
                }
            }
        }

        private void UndoEdit()
        {
                 //What goes in here?
        }

One of the ideas I had was to create a temporary BindingSource which will hold all the information before the edit. When the user clicks the Undo contect menu, the temporary bindingsource becomes the source for the GridView, giving it the original information. Didn`t work!

Any guidance is greatly appreciated.
0
Comment
Question by:San24
  • 4
  • 3
  • 2
9 Comments
 
LVL 41

Expert Comment

by:graye
ID: 33526027
Take a look at the CancelEdit method of the DataGridView
http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.canceledit.aspx 
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 33526166
I don't think that CancelEdit would begin to approach a solution for an undo operation.  You would need to monitor for deleted list objects, added list objects, and modified objects.

You could have classes like this:


internal class UndoManager
{

    private Stack<UndoAction> _undoStack;

    public void Push(UndoAction action)
    {
        _undoStack.Push(action);
    }

    public UndoAction Pop()
    {
        if (_undoStack.Count == 0)
            throw new InvalidOperationException("Stack empty.");

        return _undoStack.Pop();
    }
}

internal class UndoAction
{

    public enum ChangeMode
    {
        Add,
        Delete,
        Modify,
    }

    public UndoAction(object trackObject, ChangeMode change)
    {
        this.TrackObject = trackObject;
        this.Change = change;
    }

    public object TrackObject { get; private set; }
    public ChangeMode Change { get; private set; }
}

Open in new window

0
 

Author Comment

by:San24
ID: 33526238
     Can I do something like this? I was thinking this would work.

       public BindingSource BSTemp = new BindingSource();

        private void DeleteMotRow()
        {
            BSTemp = BS;                  //Make a Temp Binding Source before the Edit
            //Code to Delete the rows
         }

       private void UndoEdit()
        {
            MotGridView.DataSource = null;
            MotGridView.DataSource = BSTemp;  
            BS = BSTemp;
        }
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 96

Expert Comment

by:Bob Learned
ID: 33526253
A BindingSource doesn't store any state information, so making a temporary reference, just makes another copy of the BindingSource, not the underlying data.
0
 

Author Comment

by:San24
ID: 33526295
I didn`t know that. That helps a lot. Maybe I could use the same idea and make a Temp List? What do you think about that idea?
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 33526339
I think that idea sounds good on the surface, but I prefer a more surgical approach.  You might want to allow multiple undo operations, and you would need to keep multiple copies of the list for each undo action.  That kind of operation would require a considerable amount of memory, versus keeping track of individual object changes.
0
 
LVL 41

Expert Comment

by:graye
ID: 33526851
CancelEdit only affects the current cell....
0
 

Author Comment

by:San24
ID: 33533590
@TheLearnedOne - Let me try your approach and get back. I might need some time.
0
 
LVL 96

Accepted Solution

by:
Bob Learned earned 2000 total points
ID: 33534127
I thought of some enhancements to that approach:


using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Forms;

internal class DataGridViewUndoManager
{

    private Stack<UndoAction> _undoStack;
    private Stack<UndoAction> _redoStack;
    private DataGridView _grid;
    private IList _dataSource;

    private object _previousCellValue;

    /// <summary>
    /// Initializes a new instance of the <see cref="DataGridViewUndoManager"/> class to track changes to 
    /// the specified DataGridView.  Provide methods to undo and redo changes.
    /// </summary>
    /// <param name="grid">The grid to track changes for.</param>
    public DataGridViewUndoManager(DataGridView grid)
    {
        // Create undo and redo stacks
        _undoStack = new Stack<UndoAction>();
        _redoStack = new Stack<UndoAction>();

        _grid = grid;

        // Get a reference to the list that the grid is bound to.
        _dataSource = (IList) grid.DataSource;

        // Attach event handlers to the important grid events, to track changes.
        _grid.RowsRemoved += new DataGridViewRowsRemovedEventHandler(_grid_RowsRemoved);
        _grid.RowsAdded += new DataGridViewRowsAddedEventHandler(_grid_RowsAdded);
        _grid.CellBeginEdit += new DataGridViewCellCancelEventHandler(_grid_CellBeginEdit);
        _grid.CellEndEdit += new DataGridViewCellEventHandler(_grid_CellEndEdit);
    }

    /// <summary>
    /// Clears the undo and redo stacks.
    /// </summary>
    public void Clear()
    {
        _undoStack.Clear();
        _redoStack.Clear();
    }

    /// <summary>
    /// Push the undo action onto the stack.
    /// </summary>
    /// <param name="action">The action.</param>
    private void Push(UndoAction action)
    {
        _undoStack.Push(action);
    }

    /// <summary>
    /// Undo the previous change.
    /// </summary>
    public void Undo()
    {
        if (_undoStack.Count == 0)
            throw new InvalidOperationException("Undo stack is empty.");

        // Get the last change from the undo stack.
        UndoAction action = _undoStack.Pop();

        // Add the change to the redo stack.
        _redoStack.Push(action);

        switch (action.Change)
        {

            case UndoAction.ChangeMode.AddRow:
                    this.RemoveRow(action);
                    break;

            case UndoAction.ChangeMode.DeleteRow:
                    this.InsertRow(action);
                    break;

            case UndoAction.ChangeMode.ModifyCell:
                    this.UpdateCell(action);
                    break;

            default:
                throw new InvalidOperationException("Unknown undo action change: " + action.Change);
        }

    }

    /// <summary>
    /// Redo the previous undo change.
    /// </summary>
    public void Redo()
    {
        if (_redoStack.Count == 0)
            throw new InvalidOperationException("Redo stack is empty.");

        // Get the last change from the undo stack.
        UndoAction action = _redoStack.Pop();

        // Add the change to the undo stack.
        _undoStack.Push(action);

        switch (action.Change)
        {

            case UndoAction.ChangeMode.AddRow:
                this.RemoveRow(action);
                break;

            case UndoAction.ChangeMode.DeleteRow:
                this.InsertRow(action);
                break;

            case UndoAction.ChangeMode.ModifyCell:
                this.UpdateCell(action);
                break;

            default:
                throw new InvalidOperationException("Unknown redo action change: " + action.Change);
        }

    }

    /// <summary>
    /// Updates the cell value.
    /// </summary>
    /// <param name="action">The action that was performed.</param>
    private void UpdateCell(UndoAction action)
    {
        // Check for valid arguments (value, row, column).
        if (action.Arguments == null || action.Arguments.Length < 3)
            throw new InvalidOperationException("Invalid arguments");
        object previousCellValue = action.Arguments[0];

        int rowIndex = (int) action.Arguments[1];
        int columnIndex = (int) action.Arguments[2];

        // Restore the previous cell value.
        _grid[columnIndex, rowIndex].Value = previousCellValue;
    }

    /// <summary>
    /// Inserts the row into the grid's list.
    /// </summary>
    /// <param name="action">The action that was performed.</param>
    private void InsertRow(UndoAction action)
    {
        // Check for valid arguments (row).
        if (action.Arguments == null || action.Arguments.Length < 1)
            throw new InvalidOperationException("Invalid arguments");

        int rowIndex = (int) action.Arguments[0];

        // Add previously removed row, at the previous row.
        _dataSource.Insert(rowIndex, action.TrackObject);
    }

    /// <summary>
    /// Removes the row from the grid's list.
    /// </summary>
    /// <param name="action">The action that was performed.</param>
    private void RemoveRow(UndoAction action)
    {
        // Check for valid arguments (row).
        if (action.Arguments == null || action.Arguments.Length < 1)
            throw new InvalidOperationException("Invalid arguments");

        int rowIndex = (int) action.Arguments[0];

        // Remove previously added row.
        _dataSource.RemoveAt(rowIndex);
    }

    private void _grid_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
    {
        // Store the previous cell value.
        _previousCellValue = _grid[e.ColumnIndex, e.RowIndex].Value;
    }

    private void _grid_CellEndEdit(object sender, DataGridViewCellEventArgs e)
    {
        // Get the current cell value, to compare to the previous cell value.
        object cellValue = _grid[e.ColumnIndex, e.RowIndex].Value;

        if (!_previousCellValue.Equals(cellValue))
        {
            // If the cell value changed, push ModifyCell action on the stack, with the previous cell value to restore.
            this.Push(new UndoAction(_grid.Rows[e.RowIndex].DataBoundItem, UndoAction.ChangeMode.ModifyCell,
                _previousCellValue, e.RowIndex, e.ColumnIndex));
        }
    }

    private void _grid_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
    {
        // Push an AddRow change to the stack.
        this.Push(new UndoAction(_grid.Rows[e.RowIndex].DataBoundItem, UndoAction.ChangeMode.AddRow, e.RowIndex));
    }

    private void _grid_RowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
    {
        // Push a DeleteRow change to the stack.
        this.Push(new UndoAction(_grid.Rows[e.RowIndex].DataBoundItem, UndoAction.ChangeMode.DeleteRow, e.RowIndex));
    }

    private class UndoAction
    {

        public enum ChangeMode
        {
            AddRow,
            DeleteRow,
            ModifyCell,
        }

        public UndoAction(object trackObject, ChangeMode change, params object[] arguments)
        {
            this.TrackObject = trackObject;
            this.Change = change;
            this.Arguments = arguments;
        }

        public object TrackObject { get; private set; }
        public ChangeMode Change { get; private set; }
        public object[] Arguments { get; private set; }

    }

}

Open in new window

0

Featured Post

Free Tool: Path Explorer

An intuitive utility to help find the CSS path to UI elements on a webpage. These paths are used frequently in a variety of front-end development and QA automation tasks.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

For those of you who don't follow the news, or just happen to live under rocks, Microsoft Research released a beta SDK (http://www.microsoft.com/en-us/download/details.aspx?id=27876) for the Xbox 360 Kinect. If you don't know what a Kinect is (http:…
A long time ago (May 2011), I have written an article showing you how to create a DLL using Visual Studio 2005 to be hosted in SQL Server 2005. That was valid at that time and it is still valid if you are still using these versions. You can still re…
Is your data getting by on basic protection measures? In today’s climate of debilitating malware and ransomware—like WannaCry—that may not be enough. You need to establish more than basics, like a recovery plan that protects both data and endpoints.…
Look below the covers at a subform control , and the form that is inside it. Explore properties and see how easy it is to aggregate, get statistics, and synchronize results for your data. A Microsoft Access subform is used to show relevant calcul…
Suggested Courses

916 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question