说到撤回功能就能想到栈操作,Qt提供了用于撤回功能的类QUndoStack,栈中存放的是QUndoCommand对象。
实现的思路可以简单理解为,在我们的程序中建立一个QUndoStack对象,然后把我们要Undo/Redo的操作实例为QUndoCommand对象,压入栈,然后就是对栈进行操作了。
关于QUndoStack类和QUndoCommand类的使用,在官方文档中搜索“Overview of Qt's Undo Framework”,Qt还提供了一个例子undoframework。
//----------------------------------------------------------------------------------------分隔线
如果没有使用过QUndoStack类和QUndoCommand类,建议先简单阅读一下官方文档,知道其用法。
下面给出一个我自己使用QUndoStack类和QUndoCommand类实现QTableWidget撤回功能的例子。撤回功能需要知道cell的旧值,我的做法是给QTableWidget设置了委托(TableDelegate类),通过委托记录cell的旧值。
直接上代码,环境Qt_5_12_4_MinGW_32_bit。
ModifCommand类
class ModifCommand : public QUndoCommand
{
public:
ModifCommand(QTableWidget *pTable, int row, int col, QString oldValue, QString value, QUndoCommand *parent = nullptr);
void undo() override;
void redo() override;
private:
QTableWidget *m_table;
int m_row;
int m_col;
QString m_value;
QString m_oldValue;
};
ModifCommand::ModifCommand(QTableWidget *pTable, int row, int col, QString oldValue, QString value, QUndoCommand *parent)
: QUndoCommand(parent)
{
m_table = pTable;
m_row = row;
m_col = col;
m_oldValue = oldValue;
m_value = value;
}
void ModifCommand::undo()
{
m_table->item(m_row, m_col)->setText(m_oldValue);
}
void ModifCommand::redo()
{
m_table->item(m_row, m_col)->setText(m_value);
setText(QString("modif %1,%2").arg(m_row+1).arg(m_col+1));
}
TableDelegate类
class TableDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit TableDelegate(QObject *parent = nullptr);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &,
const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const override;
signals:
void beginEdit()const;
};
TableDelegate::TableDelegate(QObject *parent) : QStyledItemDelegate(parent)
{}
// 创建编辑器
QWidget *TableDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex &) const
{
QLineEdit *editor = new QLineEdit(parent);
emit beginEdit();
return editor;
}
// 为编辑器设置数据
void TableDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
QLineEdit *edit = qobject_cast<QLineEdit*>(editor);
if (edit)
{
edit->setText(index.model()->data(index, Qt::EditRole).toString());
return;
}
}
// 将数据写入到模型
void TableDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model, const QModelIndex &index) const
{
QLineEdit *edit = qobject_cast<QLineEdit *>(editor);
if (edit)
{
model->setData(index, edit->text());
return;
}
}
MainWindow类
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
resize(800, 600);
undoStack = new QUndoStack(this);
/* 创建Action */
undoAction = undoStack->createUndoAction(this, tr("&Undo"));
undoAction->setShortcuts(QKeySequence::Undo);
redoAction = undoStack->createRedoAction(this, tr("&Redo"));
redoAction->setShortcuts(QKeySequence::Redo);
QMenu *editMenu = menuBar()->addMenu("&Edit");
editMenu->addAction(undoAction);
editMenu->addAction(redoAction);
menuBar()->show();
/* 创建表格 */
table = new QTableWidget(this);
table->setRowCount(10);
table->setColumnCount(7);
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 7; j++)
{
QTableWidgetItem *item = new QTableWidgetItem("0");
table->setItem(i, j, item);
}
}
table->resize(800, 600);
setCentralWidget(table);
table->show();
/* 给表格设置委托 */
TableDelegate *delegate = new TableDelegate(this);
table->setItemDelegate(delegate);
connect(delegate, &TableDelegate::beginEdit, this, [=]()
{
QTableWidgetItem *item = table->currentItem();
item->setData(Qt::UserRole, item->text()); //jz 保存旧值
});
connect(delegate, &TableDelegate::closeEditor, this, [=]()
{
QTableWidgetItem *item = table->currentItem();
QString oldValue = item->data(Qt::UserRole).toString();
if (item->text() != oldValue)
{
undoStack->push(new ModifCommand(table, item->row(), item->column(), oldValue, item->text()));
}
});
/* 创建UndoView */
undoView = new QUndoView(undoStack);
undoView->setWindowTitle(tr("Command List"));
undoView->show();
undoView->setAttribute(Qt::WA_QuitOnClose, false);
}
//----------------------------------------------------------------------------------------分隔线
上面讲的是QTableWidget实现撤回功能,QTableView实现撤回/重做功能的做法跟QTableWidget基本一样。我提供一个QTableView+QStandardItemModel实现撤回/重做功能的例子,见下文链接。
代码已上传到gitee
tableWidgetUndo: 提供一个示例,QTableWidget实现撤回/重做功能
tableViewUndo: 提供一个示例,QTableView+QStandardItemModel实现撤回/重做功能