Qt Undo Framework Demo
eryar@163.com
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
在交互應(yīng)用程序中撤銷和重做(Undo/Redo)能力是很重要的。像常見(jiàn)的軟件Office,AutoCAD等,有了撤銷功能,用戶體驗(yàn)更舒服。一般都會(huì)使用Command模式來(lái)實(shí)現(xiàn)這一功能。
命令模式通過(guò)將請(qǐng)求本身變成一個(gè)對(duì)象來(lái)使工具箱對(duì)象可向未指定的應(yīng)用對(duì)象提出請(qǐng)求,這個(gè)對(duì)象可被存儲(chǔ)并像其他對(duì)象一樣被傳遞。這一模式的關(guān)鍵是一個(gè)抽象的Command類,它定義了一個(gè)可執(zhí)行操作的接口。其最簡(jiǎn)單的形式是一個(gè)抽象的Execute操作。具體的Command子類將接收者作為其一個(gè)實(shí)例變量,并實(shí)現(xiàn)Execute操作,指定接收者采取動(dòng)作,而接收者執(zhí)行該請(qǐng)求所需要的具體信息。在GoF的《Design Patterns》中,給出了Command模式的一般結(jié)構(gòu),如圖1.1所示:
Figure 1.1 Command pattern structure
將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或記錄成日志,以及支持可撤銷的操作。
支持任意層次的撤銷和重做命令的最后一步是定義一個(gè)命令歷史記錄(Command History),或稱為已執(zhí)行的命令列表。從概念上理解,命令的歷史記錄看起來(lái)有如下形狀:
Figure 1.2 Command History
每個(gè)圓代表一個(gè)Command對(duì)象,標(biāo)有present的對(duì)象即為當(dāng)前命令對(duì)象。當(dāng)我們調(diào)用Unexecute()后,標(biāo)有present的對(duì)象將會(huì)向左移;當(dāng)調(diào)用Execute(),標(biāo)有present的對(duì)象將會(huì)向右移。重復(fù)這個(gè)過(guò)程,我們可以進(jìn)行多層次的撤銷,層次數(shù)只受命令歷史記錄長(zhǎng)度的限制。
在Qt的Undo框架中主要包括以下幾個(gè)類:
v QUndoCommand:這個(gè)類相當(dāng)于Command模式中的那個(gè)抽象基類Command,所有這些命令都被保存到undo棧中,在其派生類中實(shí)現(xiàn)undo和redo函數(shù)。
v QUndoStack:這個(gè)相當(dāng)于命令歷史記錄,其中保存了Command對(duì)象的列表。
v QUndoGroup:是一個(gè)undo stack的組合。
v QUndoView:是顯示undo堆棧中內(nèi)容的一個(gè)列表組件,在這個(gè)視圖中點(diǎn)擊命令的名稱也可以實(shí)現(xiàn)與Undo/Redo按鈕相同的作用。
本文通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)示例Qt中Undo框架,先在簡(jiǎn)單的List模型中實(shí)現(xiàn),進(jìn)而在Tree上實(shí)現(xiàn)。掌握Qt的這個(gè)框架,就可以不用OpenCASCADE的OCAF了,并且Qt的代碼用起來(lái)還是相對(duì)簡(jiǎn)單清晰的。
2.Example
Qt提供了一個(gè)Undo框架的示例,程序還涉及到圖形繪制相關(guān)的內(nèi)容,程序效果如下圖2.1所示:
Figure 2.1 Qt Undo Framework Example
結(jié)合這個(gè)示例程序,學(xué)習(xí)一下Qt的Undo框架,從而寫出一個(gè)更簡(jiǎn)單的程序,代碼如下所示:
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派生出一個(gè)插件字符串的類InsertCommand,并要實(shí)現(xiàn)undo()和redo()這兩個(gè)虛函數(shù),實(shí)現(xiàn)代碼如下所示:
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())));
}
這樣在響應(yīng)工具欄按鈕的函數(shù)中,只需要生成這個(gè)命令,并將命令加入到命令棧中即可,代碼如下:
void undoTest::insertString()
{
QModelIndex aIndex = mListView->currentIndex();
mUndoStack->push(new InsertCommand(aIndex, mListModel));
}
程序運(yùn)行效果如下圖2.2所示:
Figure 2.3 Test Qt Undo Framework
通過(guò)工具欄上的undo/redo及命令列表中選擇,都可以實(shí)現(xiàn)命令的回退及重做。完整的程序代碼可通過(guò)文后鏈接下載。
3.Conclusion
在學(xué)習(xí)C++基本語(yǔ)法后,可以看看GoF的《設(shè)計(jì)模式》。剛剛接觸可能感覺(jué)有些抽象,這時(shí)可以使用Qt來(lái)編寫一些程序來(lái)練練手。用Qt來(lái)編程感覺(jué)比MFC要舒服很多,有些類封裝得很直接,易于使用。盡管MFC中也有個(gè)Document/View的設(shè)計(jì)模式,但是Qt中的MVC用起來(lái)更直接。通過(guò)使用現(xiàn)有的框架,來(lái)理解那些抽象的設(shè)計(jì)模式,從而加深面向?qū)ο蟮挠^念,讓自己的程序更簡(jiǎn)單,有趣。
OpenCASCADE的OCAF框架也提供了一個(gè)數(shù)據(jù)框架,基于這個(gè)樹(shù)形的框架,可以存儲(chǔ)層次表示的數(shù)據(jù),且也提供了Undo/Redo的支持?;贠CAF框架,可以快速開(kāi)發(fā)出一定功能的專業(yè)軟件了。但是要使用OCAF框架,涉及的OpenCASCADE庫(kù)很多。如果打算開(kāi)發(fā)一個(gè)輕量級(jí)的三維程序,而又正好選擇了Qt來(lái)開(kāi)發(fā)GUI,這時(shí)就可以考慮使用Qt的MVC框架及在這個(gè)框架上的Undo/Redo功能,這樣開(kāi)發(fā)效率可以相對(duì)高一些,且程序發(fā)布時(shí)依賴的動(dòng)態(tài)庫(kù)也要少很多。
流行的工廠設(shè)計(jì)軟件中的數(shù)據(jù)框架多用樹(shù)形結(jié)構(gòu),樹(shù)中每個(gè)結(jié)點(diǎn)上的屬性可以讓用戶自由擴(kuò)展,像OCAF中通過(guò)TDataStd_Integer添加一些整數(shù)屬性一樣,及用TDataStd_Name添加名稱屬性。但是OCAF中添加屬性有些局限性,因?yàn)槊糠N屬性是用GUID來(lái)區(qū)別的,所以每個(gè)結(jié)點(diǎn)上同一種屬性只能有一個(gè)。
所以用Qt的MVC框架來(lái)根據(jù)需要實(shí)現(xiàn)一個(gè)自定義的樹(shù)形Model,再基于V3d_Viewer實(shí)現(xiàn)一個(gè)顯示三維的View,即可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單,但看上去相對(duì)專業(yè)的CAD建模程序了。
4. References
1. GoF. Design Patterns-Elements of Reusable Object-Oriented Software.機(jī)械工業(yè)出版社. 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