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();
dataGridView1.Rows.Add(20);// Add row
dt = GetDataTableFromDGV(dataGridView1);// make datatable at the beginning
dtList.Clear();
dtList.Add(dt);// Beginning data is added to List
}
public DataTable GetDataTableFromDGV(DataGridView dgv)// This methot makes a DataTable from DataGridView control.
{
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;
}
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);
dtList.Add(dt);
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;
}
}
}
1 Answer 1
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)
{
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();
}
}
-
1\$\begingroup\$ 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? \$\endgroup\$Gokhan– Gokhan2021年04月05日 08:53:14 +00:00Commented Apr 5, 2021 at 8:53
-
\$\begingroup\$ 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. \$\endgroup\$Gokhan– Gokhan2021年04月05日 18:45:06 +00:00Commented Apr 5, 2021 at 18:45
-
\$\begingroup\$ @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. \$\endgroup\$JimmyHu– JimmyHu2021年04月05日 20:45:58 +00:00Commented Apr 5, 2021 at 20:45
-
-
2\$\begingroup\$ I tried now. It is working well.Thank you very much @JimmyHu. I am new in programming i learned now stack class is used in undo redo applications. \$\endgroup\$Gokhan– Gokhan2021年04月06日 16:12:49 +00:00Commented Apr 6, 2021 at 16:12