手把手教你寫Undo Redo程式

2021-08-22 05:33:18 字數 3065 閱讀 2370

手把手教你寫undo、redo程式

undo、redo操作是很多具體編輯功能的軟體所不能缺少的。最典型兩種型別就是文字編輯和影象編輯軟體。然而它的實現在某種程度上來說也不是很簡單。我也廢話少說。要在程式中支援undo、redo操作,就需要儲存一些必要的資訊,這個是眾所周知的。如果想支援無限級的undo、redo操作,儲存的資訊就會無限的膨脹,問題來了,如何設計才能使每一步操作儲存的資料盡可能少。

下面我就以影象編輯軟體為例。說明如何在影象編輯中新增undo、redo功能。在我們開始進行編碼設計前,對一些問題進行簡單說明:

1、如何儲存影象編輯操作中的操作資訊。影象編輯可簡單分為兩類:一類是可逆的。也就是我們施加在影象上的操作可以根據操作演算法進行逆操作。比如旋轉,在旋轉某個角度後如果需要undo我們可以直接按相反的方向再旋轉同樣的角度;另一類是不可逆的。這裡的不可逆不是絕對的。比如我們根據某個模板演算法對影象的每個象素進行修改。這時我們就直接把此類操作歸為不可逆。因為即使它可能是可逆的,但是實現起來的難道如果很大,這裡只是為了方便說明。

2、對操作有了基本分類後。我們可以發現不可逆操作的undo、redo功能實現應該比較容易一些。為什麼呢?因為操作不可逆,我們必須在操作前把全部的象素儲存起來。這就相當於對原來的資訊做了乙份拷貝。所有的不可逆操作儲存的資訊可以認為是相同的:都是整個影象象素。此類操作實現簡單,但是**卻高。而對於可逆操作,不同的操作演算法就對應不同的undo、redo。每次操作儲存的資訊不同,但是我們只需要儲存操作的演算法。此類操作實現稍微麻煩。但是所需空間較小。對比兩種操作,正如魚和熊掌不能兼得。

3、在我們開啟一副影象後,通常在軟體的文件類中應該有乙個最基本的影象資料類。所有的操作都是基於此類的資料。而且在我們進行undo、redo操作時,需要傳遞乙個外部(也就是文件的影象資料)作為undo、redo的物件。

好了,我們開始對一些類進行說明。為了把資料資料與影象操作進行分離,我們定義兩個基類:cimagedata和cimageoperation。分別表示影象資料類和影象操作的基類。

class cimagedata

;下面是cimageoperation類的基本定義:

class cimageoperation

;注意cimageoperation是乙個抽象類,因為它並知道具體的影象操作。它的execute函式也需要由派生的具體操作類實現。我下面就給乙個具體操作實現類(以旋轉為例):

class cimageratate : public cimageoperation

virtual bool execute(cimagedata * pdata)

private:

float m_frotateangle;

};注意:這個旋轉操作是可逆的。

怎麼樣你應該理解這個簡單的影象操作框架了吧!下面開始我們真正的undo、redo部分。基於前面第三點所述,我們可以把undo的抽象基類設計如下:

class cundodata

virtual bool undoaction(cimagedata * pdata) = 0;

unsigned int m_tooltip;

};成員m_tooltip所表示的值是乙個字串資源的id,如果我們希望在工具欄的undo、redo按鈕上新增操作提示功能,就可以使用它。預設值是0,表示沒有提示資訊。

函式undoaction是真正的undo、redo實現函式,也是乙個抽象類。它的引數是由外部傳入的undo物件(通常是文件類中的cimagedata物件)。

根據前面第二點的說明,影象的可逆操作我們認為儲存的資料是一樣,都是cimagedata物件。而不可逆操作是不同型別的。所以下面再定義兩個類,分別表示可逆操作的undo類和乙個不可逆的操作類。(不可逆操作很多,仍以旋轉為例)

class cfullimageundo : public cundodata

public:

cimagedata m_undodata;

};cfullimageundo主要是針對不可逆操作的,因為只有這類操作我們才需要儲存整個的影象資料。下面是可逆的旋轉操作:

class cratateundo : public cundodata

virtual bool undoaction(cimagedata * pdata)

private:

float m_frotateangle; //此成員意義與cimageratate中的一樣。

};現在基本的undo類有了。還沒有實現給外部文件類使用的undo/redo列表啦!我們需要儲存所有的undo/redo列表。從使用其他軟體你應該可以感受出:最後的操作總是被最先undo。redo也是這樣的。使用什麼樣的資料結構儲存列表就好實現了。我們也找一種後進先出的列表:棧。我們就來實現這個介面類:(這裡的棧我直接使用了stl的棧工具,其實stl的棧也是封裝stl的duque實現的)

#pragma warning(disable : 4786)

#include

class cundolist

~cundolist()

public:

// 下面兩個函式判斷undo/redo棧是否已經空

bool isundoempty() const

bool isredoempty() const

// 返回undo資料的m_tooltip資料,實現略

unsigned int getundotips() const;

unsigned int getredotips() const;

void addundo(cundodata * pundo);

}void undo(cimagedata * pdata);

void redo(cimagedata * pdata);

void clearundo(); // 清除undo棧,實現略

void clearredo(); // 清除redo棧,實現略

private:

std::stackm_undolist;

std::stackm_redolist;

};好了現在介面類實現。我們就可以在文件類中使用這個cundolist類,並根據cundolist類的函式返回指,實現工具欄安裝狀態的改變以及工具欄按鈕的提示資訊。

進一步內容可參考:

手把手教你寫undo、redo程式(續)

手把手教你寫Undo Redo程式

手把手教你寫 undo redo程式 undo redo 操作是很多具體編輯功能的軟體所不能缺少的。最典型兩種型別就是文字編輯和影象編輯軟體。然而它的實現在某種程度上來說也不是很簡單。我也廢話少說。要在程式中支援 undo redo 操作,就需要儲存一些必要的資訊,這個是眾所周知的。如果想支援無限級...

手把手教你寫ORM(三)

昨天處於暈死狀態,少寫了乙個元件,還需要乙個元件用來專門管理cache的,這裡說道為什麼要分這麼多元件,其實這是習慣問題,很多人喜歡寫乙個很大的dll,不過我比較喜歡拆分,小粒度的專案比較好管理和單獨測試,把用單元測試驗證好了的小組件湊起來除錯和寫成乙個巨大的dll慢慢一行行的追蹤 肯定是前者更加舒...

手把手教你寫ORM(五)

cmmi是魔鬼 繼續上面的內容,這裡我們要實現乙個外掛程式的結構來動態從外部載入資料元件,其好處不用我再多說了,可能有人會發問,外掛程式是aop的拿手好戲你咋個不用?真是暈死,就2行 犯得著引入那麼大一堆東西進來麼?外掛程式,首先要定義介面,當然通過refrection我們無所不能,但是有乙個定義好...