上一篇介绍了model实现排序和过滤,实际项目中model/view不止用来显示数据,还会用于实现比较复杂的界面交互,某些情况下需要支持撤销恢复的功能
Qt Undo Framework 介绍Qt的撤销框架是命令模式的一个实现,用于在应用程序中实现撤销/重做功能。
命令模式基于这样一种思想,即应用程序中的所有编辑都是通过创建命令对象的实例来完成的。命令对象将更改应用于文档,并存储在命令堆栈中。此外,每个命令都知道如何撤销其更改,以将文档恢复到之前的状态。只要应用程序只使用命令对象来改变文档的状态,就可以通过向下遍历堆栈并依次对每个命令调用undo来撤销一系列命令。还可以通过向上遍历堆栈并对每个命令调用redo来重做一系列命令。
类该框架由四类组成:
- QUndoCommand是存储在撤消堆栈上的所有命令的基类。它可以应用(重做)或撤消文档中的单个更改。
- QUndoStack是一个列表QUndoCommand对象。它包含对文档执行的所有命令,并且可以通过撤销或重做这些命令来向后或向前滚动文档的状态。
- QUndoGroup 是一组撤消堆栈。当一个应用程序包含多个撤销堆栈时,这是很有用的,通常每个打开的文档都有一个撤销堆栈。QUndoGroup 为组中的所有堆栈提供一对撤消/重做插槽。它将撤销和重做请求转发到活动堆栈,该堆栈与用户当前正在编辑的文档相关联。
- QUndoView 是一个显示撤销堆栈内容的小部件。单击视图中的命令会将文档状态向前或向后滚动到该命令。
该框架支持以下概念:
- Clean state:用于在文档进入和离开已保存到磁盘的状态时发出信号。这通常用于禁用或启用保存操作,以及更新文档的标题栏。
- Command compression:用于将命令序列压缩成单个命令。例如:在文本编辑器中,将单个字符插入到文档中的命令可以压缩成一个插入整个文本部分的命令。这些更大的改变对于用户来说更方便撤销和重做。
- Command macros:一系列命令,所有这些命令都可以在一个步骤中撤消或重做。这简化了编写应用程序的任务,因为一组简单的命令可以组成更复杂的命令。例如,移动文档中一组选定对象的命令可以通过组合一组命令来创建,每个命令移动一个对象。
QUndoStack提供方便的撤消和重做动作可以插入菜单或工具栏的对象。这些动作的文本属性总是反映当它们被触发时什么命令将被撤消或重做。同样的,群体提供撤消和重做操作,其行为总是类似于活动堆栈的撤消和重做操作。
以上是Qt文档的介绍
总结起来就是,所要实现撤销的操作都通过QUndoCommand进行创建,把QUndoCommand添加到撤销堆栈QUndoStack,就可以基于堆栈实现undo和redo的操作,如果有多个撤销堆栈,则还可以使用组QUndoGroup来管理多个堆栈,实现多个堆栈的undo和redo操作
接下来直接来看具体应用吧
Model搭配撤销框架例子使用的还是之前的model,在此基础上搭配Qt的undo框架来实现undo、redo的操作
#include#include "datalistmodel.h" class AddCommand : public QUndoCommand { public: AddCommand(const ModelData &modelData, DataListModel *model, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: int m_index = -1; ModelData m_modelData; DataListModel *m_pModel = nullptr; }; class DeleteCommand : public QUndoCommand { public: DeleteCommand(int index, DataListModel *model, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: int m_index; ModelData m_modelData; DataListModel *m_pModel = nullptr; };
#include "commands.h"
AddCommand::AddCommand(const ModelData &modelData, DataListModel *model, QUndoCommand *parent):
QUndoCommand(parent), m_modelData(modelData), m_pModel(model)
{
Q_ASSERT(m_pModel);
m_index = m_pModel->rowCount();
}
void AddCommand::undo()
{
m_pModel->remove(m_index);
}
void AddCommand::redo()
{
Q_ASSERT(m_pModel);
m_pModel->insert(m_index, m_modelData);
}
DeleteCommand::DeleteCommand(int index, DataListModel *model, QUndoCommand *parent):
QUndoCommand(parent), m_index(index), m_pModel(model)
{
Q_ASSERT(m_pModel);
m_pModel->modelData(m_index, m_modelData);
}
void DeleteCommand::undo()
{
Q_ASSERT(m_pModel);
m_pModel->insert(m_index, m_modelData);
}
void DeleteCommand::redo()
{
Q_ASSERT(m_pModel);
m_pModel->remove(m_index);
}
这里我们对新增和删除的操作进行记录,需要继承QUndoCommand实现新增和删除的command,重写undo()和redo(),首次创建QUndoCommand会执行redo操作,即redo就是具体要记录的操作,如增加command的redo就是insert(),删除的command就是remove(),undo则是撤销的操作,就如前面说明的一样,每个命令需要知道如何撤销其更改,undo就是撤销更改的操作。
#includeclass DataListModel; class QUndoStack; class ModelManager : public QObject { Q_OBJECT public: explicit ModelManager(QObject *parent = nullptr); Q_INVOKABLE DataListModel *getModel() const; Q_INVOKABLE void add(const QVariantMap &dataMap); Q_INVOKABLE void remove(int index); Q_INVOKABLE void undo(); Q_INVOKABLE void redo(); private: DataListModel *m_pDataModel = nullptr; QUndoStack *m_pUndoStack = nullptr; };
#include "modelmanager.h" #include "datalistmodel.h" #include "commands.h" #include "qqml.h" #includeModelManager::ModelManager(QObject *parent): QObject(parent) { m_pUndoStack = new QUndoStack(this); m_pDataModel = new DataListModel(this); qmlRegisterType ("Model", 1, 0, "myModel"); } DataListModel *ModelManager::getModel() const { return m_pDataModel; } void ModelManager::add(const QVariantMap &dataMap) { QString type = dataMap["type"].toString(); QString size = dataMap["size"].toString(); ModelData data(type, size); QUndoCommand *addCommand = new AddCommand(data, m_pDataModel); m_pUndoStack->push(addCommand); } void ModelManager::remove(int index) { QUndoCommand *deleteCommand = new DeleteCommand(index, m_pDataModel); m_pUndoStack->push(deleteCommand); } void ModelManager::undo() { m_pUndoStack->undo(); } void ModelManager::redo() { m_pUndoStack->redo(); }
把QUndoCommand添加到堆栈QUndoStack中,执行QUndoStack的undo和redo即可实现撤销恢复的功能
#include#include #include #include "modelmanager.h" int main(int argc, char *argv[]) { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif QGuiApplication app(argc, argv); ModelManager manager; QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("modelManager", &manager); const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
QML调用
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
ListView {
id: listview
clip: true
width: parent.width
height: parent.height
model: modelManager.getModel()
delegate: Item {
id: delegate
width: listview.width
height: 30
Row {
spacing: 5
Text {
text: type
}
Text {
text: size
color: "red"
}
Button {
height: parent.height
onClicked: {
modelManager.remove(index);
}
}
}
}
}
Row {
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
spacing: 5
Text {
height: 40
text: "type:"
verticalAlignment: Text.AlignVCenter
}
TextField {
id: typeInput
width: 60
}
Text {
height: 40
text: "size:"
verticalAlignment: Text.AlignVCenter
}
TextField {
id: sizeInput
width: 60
}
Button {
text: "add"
onClicked: {
modelManager.add({"type": typeInput.text, "size": sizeInput.text})
}
}
Button {
text: "undo"
onClicked: {
modelManager.undo()
}
}
Button {
text: "redo"
onClicked: {
modelManager.redo()
}
}
}
}
运行效果
结语以上就是一个简单的model/view搭配undo framework实现的撤销恢复功能,undo framework的例子可在Qt Creator例子搜索undo进行查看,例子是基于Graphics实现的,了解其中的用法,在model/view也能使用。 自此,QML与C++model的使用就介绍到此,目前应用到的场景就这么多,以后接触到其他功能再做记录。
完整的demo地址



