Я просмотрел много тем, связанных с этой темой, но не смог найти или понять таблицу datagridview. Наконец, я сам сделал реализацию отмены / повтора, используя списки данных. Думаю, это решение подходит для небольших таблиц. Я не знаю, будет ли он эффективно работать с таблицами с большим количеством данных. Я поделюсь этим решением здесь. Может быть, я внесу свой вклад в этот сайт, где я многому научился. Также жду комментариев. Это решение глупое? или это работает? Мне нужны ваши комментарии для улучшения, или откажитесь. Спасибо.
Сначала мы добавим в нашу форму DataGridView и две кнопки с именами «Отменить» и «Вернуть».
Это коды ниже;
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 ответ
Обзор
Добро пожаловать в Code Review. Есть несколько предложений, как показано ниже.
Макет и форматирование
Не уверен, почему между фигурными скобками и определением метода много новых строк, if
заявления и for
заявления. Для улучшения читаемости ненужные символы новой строки могут быть удалены.
Магические числа и List<DataTable>
Я понятия не имею, почему начальное значение counterUndo
установлен на 2 (в int counterUndo = 2
) и почему проверка неравенства if (_counterUndo != 2)
необходимо в doWhenClickedUndo
метод. Ограничено ли время отмены действия в 2 раза? Как насчет случая, когда пользователь хочет отменить еще несколько шагов? Чтобы решить эту проблему, я попытался использовать Stack<DataTable>
вместо List<DataTable>
таким образом Push
, Pop
и First
методы доступны (Stack
полезно для поддержания исторической последовательности, например, состояния DataTable
здесь). Следующий код является примером реализации с Stack
класс.
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();
}
}
Привет, нет предела отмены / повтора, если вы пытались. Почему counterUndo = 2?. Эта переменная используется для вызова правильного номера индекса из списка Datatable, когда пользователь нажимает кнопку «Отменить» или «Повторить». Это моя вина, что я не дал достаточно пояснений в кодах. Спасибо вам и за ваше время. Я воспользуюсь вашими кодами. Я хочу спросить, правильное ли решение использовать Datatable для отмены / повтора. Потому что после каждого действия отмены / повтора мы получаем данные из DataTable в Datagridview. Есть ли у этого недостатки? Если в таблице много данных, это замедлит работу приложения?
— Гохан
Я пробовал ваши коды. Он работает, но есть проблема с повторным щелчком. Работает после двух движений. Первое движение не отменяется. Не могли бы вы отредактировать свои коды, тогда я буду отмечен как ответ.
— Гохан
@Gokhan> Он работает, но есть проблема с повторным щелчком. Работает после двух движений. Не могли бы вы сообщить мне, какие шаги вы тестируете? Приведенный пример кода, который я пробовал, работал хорошо, независимо от того, какие движения.
— JimmyHu
Спасибо за внимание. Я добавил картинку по ссылке ниже для ясного объяснения. связьСм. Ссылку.
— Гохан
Я попробовал сейчас. Он работает хорошо. Большое спасибо, @JimmyHu. Я новичок в программировании, и теперь я узнал, что класс стека используется в приложениях отмены повтора.
— Гохан
Шоу 3 больше комментариев