# Undo/Redo Functionality in DataGridView c#

I have looked at many topics related to this topic, but I could not find or understand for datagridview table. Finally I made the undo / redo implementation myself using datatable lists. This solution is valid for small tables, I think. I don't know if it will work efficient on tables with lots of data. I will share this solution here. Maybe I will contribute to this website where I learned lots of things. Also I am waiting for comments. Is this solution stupid? or does it work? I need your comments for improvement or give up. Thanx.

Firstly we will add a DataGridView and two buttons named "Undo" and "Redo" to our form

These are codes below;

using System.Windows.Forms;

namespace WindowsFormsApp1

{

public partial class Form1 : Form

{

DataTable dt = null;

List<DataTable> dtList = new List<DataTable>(); // This list for recording every movements. If we want to do undo/redo, we will get data from this list to dataGridview

public Form1()

{
InitializeComponent();

dt = GetDataTableFromDGV(dataGridView1);// make datatable at the beginning

dtList.Clear();

}

public DataTable GetDataTableFromDGV(DataGridView dgv)// This methot makes a DataTable from DataGridView control.
{
var dt = new DataTable();

foreach (DataGridViewColumn column in dgv.Columns)

{
}

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;

}

}

return dt;

}

int counterUndo = 2;// This counts clicked redo button

int dtCount = 0;// This keeps DataTable count

bool clickedUndo;// This checks Undo button clicked or not.

private void btn_Undo_Click(object sender, EventArgs e)

{
clickedUndo = true;

dtCount = dtList.Count;

if (dtCount - counterUndo > -1)

{
int undoIndex = dtCount - counterUndo;

datatablaToDataGrid(dataGridView1, dtList[undoIndex]);

counterUndo++;
}
}

private void btn_redo_Click(object sender, EventArgs e)

{
int redoIndex = dtCount - counterUndo + 2;

if (counterUndo > 1&&redoIndex< dtCount)

{
datatablaToDataGrid(dataGridView1, dtList[redoIndex]);

counterUndo--;
}
}

public void datatablaToDataGrid(DataGridView dgv, DataTable datatable)// This methot gets data from DataTable to DataGridView control.

{
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 dataGridView1_CellValidated(object sender, DataGridViewCellEventArgs e)// This event check if the cell value is change or stay same.

{
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 = dt.Rows[r][c].ToString();

if (dgvResult != dtResult)

{

if (clickedUndo)

{

doWhenClickedUndo(counterUndo, dtList);

}

dt = GetDataTableFromDGV(dataGridView1);

counterUndo = 2;
}
}
}

private void doWhenClickedUndo(int _counterUndo, List<DataTable> _dtList)
{
if (_counterUndo != 2)

{
int f = counterUndo - 2;

int lastIndex = _dtList.Count - 1;

int i = lastIndex;

do
{
_dtList.RemoveAt(i);

i--;

} while (i > lastIndex - f);

}

clickedUndo = false;

}

}
}


## 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";

dtStack.Clear();

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

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

foreach (DataGridViewColumn column in dgv.Columns)
{
}

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;
}
}
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)
{
UpdateBtnStatus();

if (UndoRedo)
return;

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.ElementAt(RecordIndex).Rows[r][c].ToString();
if (dgvResult != dtResult)
{
while (RecordIndex > 0)
{
dtStack.Pop();
RecordIndex--;
}

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

• Hi there is no limit undo/redo if you tried. Why is "counterUndo = 2"?.This variable is a tool that use to call the correct index number from the Datatable list when the user presses undo or redo. It is my fault that I did not make enough explanations in the codes. Thanx to you and for your time. I ll use your codes. I want to ask is it right solution to use Datatable for undo/redo. Becuse after every undo/redo action we are get data from DataTable to Datagridview. Is there any disadvantages making this? If the table has lots of data this will this slow down the application? Apr 5, 2021 at 8:53
• I tried your codes. It is working but there is a problem with redo click. It is working after two movements. First movement it do not undo.Could you edit your codes then i will marked as an answer. Apr 5, 2021 at 18:45
• @Gokhan > It is working but there is a problem with redo click. It is working after two movements. Could you let me know the steps you test? The provided example code which I've tried worked well no matter which movements. Apr 5, 2021 at 20:45