1
\$\begingroup\$

While answering this code review question, I proposed a solution with List<DataTable>. I am trying to encapsulate this data structure into a "DataGridView Undo/Redo Manager" class DataGridViewManager and there are several build-in methods including IsUndoable, IsRedoable, Push, Redo, Undo, etc. The following methods are implemented.

  • IsUndoable method handles the process for checking if the previous states is available.
  • IsRedoable method handles the process for checking if the next states is available.
  • Push method handles the process for pushing new states.

The experimental implementation

The experimental implementation of DataGridViewManager is as below.

using System;
using System.Data;
using System.Windows.Forms;

class DataGridViewManager
{
    Stack<DataTable> dtStack = new Stack<DataTable>();
    private int RecordIndex = 0;

    public DataGridViewManager()
    { }

    public DataGridViewManager Push(DataTable dataTable)
    {
        ClearUnnecessaryHistory();
        this.dtStack.Push(dataTable);
        return this;
    }

    public DataTable Redo()
    {
        return dtStack.ToList()[--RecordIndex];
    }

    public DataTable Undo()
    {
        return dtStack.ToList()[++RecordIndex];
    }

    public DataTable GetCurrentState()
    {
        return dtStack.ElementAt(RecordIndex);
    }

    public bool IsUndoable()
    {
        if (RecordIndex == this.dtStack.Count - 1)
            return false;
        else
            return true;
    }

    public bool IsRedoable()
    {
        if (RecordIndex == 0)
            return false;
        else
            return true;
    }

    private void ClearUnnecessaryHistory()
    {
        while (RecordIndex > 0)
        {
            dtStack.Pop();
            RecordIndex--;
        }
        return;
    }
}

Test cases

The usage of DataGridViewManager class is like:

using System;
using System.Data;
using System.Windows.Forms;

public partial class Form1 : Form
{
    DataGridViewManager DataGridViewManager1 = new DataGridViewManager();

    public Form1()
    {
        InitializeComponent();

        //    Construct Columns
        dataGridView1.ColumnCount = 1;
        dataGridView1.Columns[0].Name = "0";

        dataGridView1.Rows.Add(20);// Add row

        DataGridViewManager1.Push(GetDataTableFromDGV(dataGridView1));
        UpdateBtnStatus();
    }

    public DataTable GetDataTableFromDGV(DataGridView dgv)
    {
        var dt = new DataTable();

        foreach (DataGridViewColumn column in dgv.Columns)
        {
            dt.Columns.Add(column.Name);
        }

        object[] cellValues = new object[dgv.Columns.Count];

        foreach (DataGridViewRow row in dgv.Rows)
        {
            for (int i = 0; i < row.Cells.Count; i++)
            {
                cellValues[i] = row.Cells[i].Value;
            }
            dt.Rows.Add(cellValues);
        }
        return dt;
    }

    public void datatablaToDataGrid(DataGridView dgv, DataTable datatable)
    {
        for (int i = 0; i < datatable.Rows.Count; i++)
        {
            for (int j = 0; j < datatable.Columns.Count; j++)
            {
                dgv.Rows[i].Cells[j].Value = datatable.Rows[i][j].ToString();
            }
        }
    }

    private void UpdateBtnStatus()
    {
        btn_Redo.Enabled = DataGridViewManager1.IsRedoable();
        btn_Undo.Enabled = DataGridViewManager1.IsUndoable();
        return;
    }

    private void btn_Undo_Click(object sender, EventArgs e)
    {
        datatablaToDataGrid(dataGridView1, DataGridViewManager1.Undo());
        UpdateBtnStatus();
    }

    private void btn_Redo_Click(object sender, EventArgs e)
    {
        datatablaToDataGrid(dataGridView1, DataGridViewManager1.Redo());
        UpdateBtnStatus();
    }

    private void dataGridView1_CellValidated(object sender, DataGridViewCellEventArgs e)
    {
        UpdateBtnStatus();

        DataGridView dgv = (DataGridView)sender;
        int r = e.RowIndex;
        int c = e.ColumnIndex;
        if (dgv.Rows[r].Cells[c].Value != null)
        {
            string dgvResult = dgv.Rows[r].Cells[c].Value.ToString();
            string dtResult = DataGridViewManager1.GetCurrentState().Rows[r][c].ToString();
            if (dgvResult != dtResult)
            {
                DataGridViewManager1.Push(GetDataTableFromDGV(dataGridView1));
            }
        }
        UpdateBtnStatus();
    }
}

All suggestions are welcome.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ As for me, making DataTable copy on each cell edit isn't a good idea. What if I have 10x1000 table, then i edit it for few hours? At least every single editing/redo/undo operation would be laggy. I suggest the other approach: store/restore only changed data e.g. by cell value as object. Also what if I'm using a List or BindingList as data storage not DataTable. Attaching DT with .DataSoure = or DataBindings.Add(...) looks like not supported too. \$\endgroup\$
    – aepot
    Apr 8 at 18:26
  • 2
    \$\begingroup\$ When I see the word "Undo" I think about Command Pattern. Also, I see how DataGridViewManager can be generalized to UndoRedoStack that looks like one here and operates using commands that encapsulate data table or data table item change \$\endgroup\$
    – xevepisis
    Apr 9 at 9:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.