winforms – Undo/Redo Functionality in DataGridView c#

Review

Welcome to Code Review. There are few suggestions as below.

Layout and formatting

Not sure why many newlines between the curly brackets and the method definition, if statements and for statements. For improving readability, those unnecessary newlines can be removed.

Magic numbers and List<DataTable>

I have no idea about why the initial value of counterUndo is set to 2 (in int counterUndo = 2) and why the inequality check if (_counterUndo != 2) is needed in doWhenClickedUndo method. Is the times of undo operation limited in 2? How about the case of more steps the user wants to undo? To solve this issue, I tried to use Stack<DataTable> instead of List<DataTable> so that the Push, Pop and First methods is available (Stack is useful for maintaining the historical sequence like the states of DataTable here). The following code is as an example implementation with Stack class.

public partial class Form1 : Form
{
    Stack<DataTable> dtStack = new Stack<DataTable>();
    int RecordIndex = 0;
    bool UndoRedo = false;

    public Form1()
    {
        InitializeComponent();

        //    Construct Columns
        dataGridView1.ColumnCount = 1;
        dataGridView1.Columns(0).Name = "0";

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

        dtStack.Clear();

        dtStack.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()
    {
        if (RecordIndex == this.dtStack.Count - 1)
            this.btn_Undo.Enabled = false;
        else
            this.btn_Undo.Enabled = true;

        if (RecordIndex == 0)
            this.btn_redo.Enabled = false;
        else
            this.btn_redo.Enabled = true;
    }

    private void btn_Undo_Click(object sender, EventArgs e)
    {
        UndoRedo = true;
        datatablaToDataGrid(dataGridView1, dtStack.ToList()(++RecordIndex));
        UpdateBtnStatus();
        UndoRedo = false;
    }

    private void btn_redo_Click(object sender, EventArgs e)
    {
        UndoRedo = true;
        datatablaToDataGrid(dataGridView1, dtStack.ToList()(--RecordIndex));
        UpdateBtnStatus();
        UndoRedo = false;
    }

    private void dataGridView1_CellValidated(object sender, DataGridViewCellEventArgs e)
    {
        if (UndoRedo)
            return;

        while (RecordIndex != 0)
        {
            dtStack.Pop();
            RecordIndex--;
        }

        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 = dtStack.First().Rows(r)(c).ToString();
            if (dgvResult != dtResult)
            {
                dtStack.Push(GetDataTableFromDGV(dataGridView1));
            }
        }
        UpdateBtnStatus();
    }
}