手把手教你寫Undo Redo程式

2021-04-15 15:46:44 字數 3896 閱讀 7124

手把手教你寫

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::stack

m_undolist;

std::stack

m_redolist;

};好了現在介面類實現。我們就可以在文件類中使用這個

cundolist

類,並根據

cundolist

類的函式返回指,實現工具欄安裝狀態的改變以及工具欄按鈕的提示資訊。

進一步內容可參考:

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

手把手教你寫Undo Redo程式

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

手把手教你寫ORM(三)

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

手把手教你寫ORM(五)

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