思路:
在QList中存储每次操作的记录,包括修改前的数据、修改后的数据和对应的行列信息。
实现undo函数,将当前状态恢复到上一个状态,即将最后一次操作的记录取出来,然后根据其中保存的行列信息和修改前的数据更新表格中对应位置的数据。
实现redo函数,将当前状态恢复到下一个状态(如果有),即将下一个操作记录取出来,然后根据其中保存的行列信息和修改后的数据更新表格中对应位置的数据。
为工具栏上的Undo按钮和redo按钮分别绑定单击事件,实现单击第n次undo撤回第n条和单击第n次redo重做第n条功能。
代码实现:
- 定义操作记录结构体Record:
struct Record {
QModelIndex index; // 行列信息
QVariant oldValue; // 修改前的值
QVariant newValue; // 修改后的值
};
- 在窗口类中定义QList类型变量m_records,并在构造函数中初始化为空列表:
class MyTableView : public QTableView {
...
private:
QList<Record> m_records;
public:
MyTableView(QWidget *parent = nullptr) : QTableView(parent), m_records() {}
};
- 在窗口类中实现record函数用于记录每次操作:
void MyTableView::record(const QModelIndex &index, const QVariant &oldValue, const QVariant &newValue)
{
if (oldValue != newValue) { // 只记录值改变的操作
m_records.append({index, oldValue, newValue}); // 将操作记录添加到列表中
}
}
- 重写dataChanged函数,用于在数据修改时调用record函数记录操作:
void MyTableView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
for (int col = topLeft.column(); col <= bottomRight.column(); ++col) {
QModelIndex index = model()->index(row, col);
QVariant oldValue = model()->data(index, Qt::EditRole);
QVariant newValue = model()->data(index, Qt::DisplayRole);
record(index, oldValue, newValue); // 记录每次修改操作
}
}
QTableView::dataChanged(topLeft, bottomRight, roles); // 调用基类函数更新表格显示
}
- 实现undo函数和redo函数,将最后一条或下一条操作记录取出来,根据其中保存的行列信息和值更新表格中对应位置的数据:
void MyTableView::undo()
{
if (!m_records.isEmpty()) { // 如果有操作记录才执行撤回操作
Record lastRecord = m_records.takeLast();
QStandardItemModel *model = static_cast<QStandardItemModel*>(this->model());
model->setData(lastRecord.index, lastRecord.oldValue, Qt::EditRole); // 恢复修改前的值
}
}
void MyTableView::redo()
{
if (!m_redoRecords.isEmpty()) { // 如果有重做记录才执行重做操作
Record nextRecord = m_redoRecords.takeLast();
QStandardItemModel *model = static_cast<QStandardItemModel*>(this->model());
model->setData(nextRecord.index, nextRecord.newValue, Qt::EditRole); // 恢复修改后的值
}
}
- 重写keyPressEvent函数,为Ctrl+Z和Ctrl+Y绑定undo和redo功能:
void MyTableView::keyPressEvent(QKeyEvent *event)
{
if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Z) {
undo(); // Ctrl+Z执行撤回操作
} else if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_Y) {
redo(); // Ctrl+Y执行重做操作
} else {
QTableView::keyPressEvent(event);
}
}
- 在窗口类中定义两个计数器变量m_undoCount和m_redoCount,并在构造函数中初始化为0。将Undo按钮和redo按钮分别绑定单击事件,实现单击第n次undo撤回第n条和单击第n次redo重做第n条功能:
class MyTableView : public QTableView {
...
private:
QList<Record> m_records;
QList<Record> m_redoRecords;
int m_undoCount;
int m_redoCount;
public:
MyTableView(QWidget *parent = nullptr) : QTableView(parent), m_records(), m_redoRecords(),
m_undoCount(0), m_redoCount(0)
{
QAction *undoAction = new QAction(QIcon(":/images/undo.png"), tr("Undo"), this);
connect(undoAction, &QAction::triggered, this, &MyTableView::undo);
QAction *redoAction = new QAction(QIcon(":/images/redo.png"), tr("Redo"), this);
connect(redoAction, &QAction::triggered, this, &MyTableView::redo);
QToolBar *toolBar = new QToolBar(this);
toolBar->addAction(undoAction);
toolBar->addAction(redoAction);
setCornerButtonEnabled(true); // 显示左上角的按钮
setHorizontalHeaderVisible(true); // 显示水平表头
setVerticalHeaderVisible(true); // 显示垂直表头
}
private slots:
void undo()
{
if (!m_records.isEmpty()) { // 如果有操作记录才执行撤回操作
Record lastRecord = m_records.takeLast();
QStandardItemModel *model = static_cast<QStandardItemModel*>(this->model());
model->setData(lastRecord.index, lastRecord.oldValue, Qt::EditRole); // 恢复修改前的值
++m_undoCount;
m_redoCount = 0;
}
}
void redo()
{
if (!m_redoRecords.isEmpty()) { // 如果有重做记录才执行重做操作
Record nextRecord = m_redoRecords.takeLast();
QStandardItemModel *model = static_cast<QStandardItemModel*>(this->model());
model->setData(nextRecord.index, nextRecord.newValue, Qt::EditRole); // 恢复修改后的值
++m_redoCount;
m_undoCount = 0;
}
}
};
完整代码: