Qt Undo Framework Demo
[email protected]
Abstract. Qt’s Undo Framework is an implementation of the Command Pattern, for implementing undo/redo functionality in applications. The Command pattern is based on the idea that all editing in an application is done by creating instances of command objects. Command objects apply changes to the document and are stored on a command stack. Furthermore, each command knows how to undo its changes to bring the document back to its previous state. As long as the application only uses command objects to change the state of the document, it is possible to undo a sequence of commands by traversing the stack downwards and calling undo on each command in turn. It is also possible to redo a sequence of commands by traversing the stack upwards and calling redo on each command.
Key Words. Qt, Undo/Redo, Command Pattern, Model/View
1. Introduction
在交互应用程序中撤销和重做(Undo/Redo)能力是很重要的。像常见的软件Office,AutoCAD等,有了撤销功能,用户体验更舒服。一般都会使用Command模式来实现这一功能。
命 令模式通过将请求本身变成一个对象来使工具箱对象可向未指定的应用对象提出请求,这个对象可被存储并像其他对象一样被传递。这一模式的关键是一个抽象的 Command类,它定义了一个可执行操作的接口。其最简单的形式是一个抽象的Execute操作。具体的Command子类将接收者作为其一个实例变 量,并实现Execute操作,指定接收者采取动作,而接收者执行该请求所需要的具体信息。在GoF的《Design Patterns》中,给出了Command模式的一般结构,如图1.1所示:
Figure 1.1 Command pattern structure
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录成日志,以及支持可撤销的操作。
支持任意层次的撤销和重做命令的最后一步是定义一个命令历史记录(Command History),或称为已执行的命令列表。从概念上理解,命令的历史记录看起来有如下形状:
Figure 1.2 Command History
每 个圆代表一个Command对象,标有present的对象即为当前命令对象。当我们调用Unexecute()后,标有present的对象将会向左 移;当调用Execute(),标有present的对象将会向右移。重复这个过程,我们可以进行多层次的撤销,层次数只受命令历史记录长度的限制。
在Qt的Undo框架中主要包括以下几个类:
v QUndoCommand:这个类相当于Command模式中的那个抽象基类Command,所有这些命令都被保存到undo栈中,在其派生类中实现undo和redo函数。
v QUndoStack:这个相当于命令历史记录,其中保存了Command对象的列表。
v QUndoGroup:是一个undo stack的组合。
v QUndoView:是显示undo堆栈中内容的一个列表组件,在这个视图中点击命令的名称也可以实现与Undo/Redo按钮相同的作用。
本文通过一个简单的例子来示例Qt中Undo框架,先在简单的List模型中实现,进而在Tree上实现。掌握Qt的这个框架,就可以不用OpenCASCADE的OCAF了,并且Qt的代码用起来还是相对简单清晰的。
2.Example
Qt提供了一个Undo框架的示例,程序还涉及到图形绘制相关的内容,程序效果如下图2.1所示:
Figure 2.1 Qt Undo Framework Example
结合这个示例程序,学习一下Qt的Undo框架,从而写出一个更简单的程序,代码如下所示:
class InsertCommand : public QUndoCommand
{
public:
InsertCommand(const QModelIndex& theIndex, QStringListModel* theModel);
~InsertCommand();
public:
virtual void undo();
virtual void redo();
private:
QModelIndex mIndex;
QStringListModel* mModel;
};
首先,从QUndoCommand派生出一个插件字符串的类InsertCommand,并要实现undo()和redo()这两个虚函数,实现代码如下所示:
void InsertCommand::undo()
{
mModel->removeRows(mIndex.row(), 1);
}
void InsertCommand::redo()
{
mModel->insertRows(mIndex.row(), 1);
mModel->setData(mIndex, QString("Insert string " + QString::number(mIndex.row())));
}
这样在响应工具栏按钮的函数中,只需要生成这个命令,并将命令加入到命令栈中即可,代码如下:
void undoTest::insertString()
{
QModelIndex aIndex = mListView->currentIndex();
mUndoStack->push(new InsertCommand(aIndex, mListModel));
}
程序运行效果如下图2.2所示:
Figure 2.3 Test Qt Undo Framework
通过工具栏上的undo/redo及命令列表中选择,都可以实现命令的回退及重做。完整的程序代码可通过文后链接下载。
3.Conclusion
在
学习C++基本语法后,可以看看GoF的《设计模式》。刚刚接触可能感觉有些抽象,这时可以使用Qt来编写一些程序来练练手。用Qt来编程感觉比MFC要
舒服很多,有些类封装得很直接,易于使用。尽管MFC中也有个Document/View的设计模式,但是Qt中的MVC用起来更直接。通过使用现有的框
架,来理解那些抽象的设计模式,从而加深面向对象的观念,让自己的程序更简单,有趣。
OpenCASCADE的OCAF框架也提供了一个
数据框架,基于这个树形的框架,可以存储层次表示的数据,且也提供了Undo/Redo的支持。基于OCAF框架,可以快速开发出一定功能的专业软件了。
但是要使用OCAF框架,涉及的OpenCASCADE库很多。如果打算开发一个轻量级的三维程序,而又正好选择了Qt来开发GUI,这时就可以考虑使用
Qt的MVC框架及在这个框架上的Undo/Redo功能,这样开发效率可以相对高一些,且程序发布时依赖的动态库也要少很多。
流行的工
厂设计软件中的数据框架多用树形结构,树中每个结点上的属性可以让用户自由扩展,像OCAF中通过TDataStd_Integer添加一些整数属性一
样,及用TDataStd_Name添加名称属性。但是OCAF中添加属性有些局限性,因为每种属性是用GUID来区别的,所以每个结点上同一种属性只能
有一个。
所以用Qt的MVC框架来根据需要实现一个自定义的树形Model,再基于V3d_Viewer实现一个显示三维的View,即可以实现一个简单,但看上去相对专业的CAD建模程序了。
4. References
1. GoF. Design Patterns-Elements of Reusable Object-Oriented Software.机械工业出版社. 2010
2. Qt5.4. Overview of Qt’s Undo Framework. 2014
3. Qt5.4. Undo Framework Example. 2014
4. OpenCASCADE6.8.0. OCAF. 2014
5. OpenCASCADE6.8.0. OCAF White Paper. 2014
6. OpenCASCADE6.8.0. Distribution of Data Through OCAF Tree. 2014
PDF Version and Source code: Qt Undo Framework Demo