下面给出基本的演示代码如下:
enum struct UndoItemType
{
kUndoItemTypeSigle,
kUndoItemTypeMutil
};
class IUndoItem {
public:
virtual ~IUndoItem() {}
virtual void Undo() {}
virtual void Redo() {}
virtual wstring GetDescr() { return wstring(); }
//The default size is 1,means one operation include 1 item.
virtual int GetSize() { return 1; }
virtual UndoItemType GetUndoItemType() { return UndoItemType::kUndoItemTypeSigle; }
};
class CBM_UndoItem :public IUndoItem
{
public:
CBM_UndoItem(CBM_TreeView * pBMTreeView, FPD_Object pEditDict, FPD_Object pParentDict,
FPD_Object pPreDict, FPD_Object pNextDict, BMNEWTYPE eType);
~CBM_UndoItem();
void Undo();
void Redo();
wstring GetDescr() {return m_csDescr;}
void SetDescr(wstring csDescr){m_csDescr = csDescr;}
void SetDictTitle(wstring csNewTitle, wstring csOldTitle);
void UndoMoveNode();
void DealExpandedItem(FPD_Object pDict);
CPtrArray m_ptrExpandDictArray;
private:
CBM_TreeView * m_pBMTreeView;
FPD_Object m_pEditDict;
FPD_Object m_pParentDict;
FPD_Object m_pPrevDict;
FPD_Object m_pNextDict;
BMNEWTYPE m_eType;
std::wstring m_csDescr;
std::wstring m_csDictNewTitle;
std::wstring m_csDictOldTitle;
};
class CBM_UndoItems :public IUndoItem
{
public:
CBM_UndoItems() {}
~CBM_UndoItems() {}
UndoItemType GetUndoItemType()override { return UndoItemType::kUndoItemTypeMutil; }
int GetSize()override { return m_vecUndoItem.size(); }
public:
vector<std::unique_ptr<CBM_UndoItem> > m_vecUndoItem;
};
class CBM_UndoHandler
{
public:
CBM_UndoHandler();
~CBM_UndoHandler();
static void Undo(FS_LPVOID clientData) ;
static void Redo(FS_LPVOID clientData) ;
wstring GetDescr() ;
public:
static vector<std::unique_ptr<IUndoItem> > m_vecUndoItem;
};
void _gBookmarkAddUndoItem(FR_Document pDoc, FS_LPCWSTR sDesc, IUndoItem* pUndoItem)
{
FR_UndoRedoCallbacksRec urc;
memset(&urc, 0x00, sizeof(urc));
urc.lStructSize = sizeof(FR_UndoRedoCallbacksRec);
urc.clientData = pUndoItem;
urc.OnUndo = CBM_UndoHandler::Undo;
urc.OnRedo = CBM_UndoHandler::Redo;
FRAppAddUndoRedoItem(sDesc, pDoc, FALSE, &urc);
}
CBM_UndoHandler::CBM_UndoHandler()
{
}
CBM_UndoHandler::~CBM_UndoHandler()
{
}
void CBM_UndoHandler::Undo(FS_LPVOID clientData)
{
IUndoItem*pUndoItem = (IUndoItem*)clientData;
int size = pUndoItem->GetSize();
if (!size)return;
UndoItemType undoType = pUndoItem->GetUndoItemType();
if (undoType == UndoItemType::kUndoItemTypeSigle)
{
for (auto rit = m_vecUndoItem.rbegin(); rit != m_vecUndoItem.rend(); ++rit) {
auto& undo_item = *rit;
if (undo_item.get() == clientData)
{
undo_item->Undo();
return;
}
}
}
else
{
for (auto rit = m_vecUndoItem.rbegin(); rit != m_vecUndoItem.rend(); ++rit) {
auto& undo_item = *rit;
if (undo_item.get() == clientData)
{
CBM_UndoHandler*pBM_UndoMultiItem = (CBM_UndoHandler*)undo_item.get();
for (auto ritor = pBM_UndoMultiItem->m_vecUndoItem.rbegin(); ritor != pBM_UndoMultiItem->m_vecUndoItem.rend(); ++ritor)
{
auto& undo_MultiItem = *ritor;
undo_MultiItem->Undo();
}
return;
}
}
}
}
void CBM_UndoHandler::Redo(FS_LPVOID clientData)
{
IUndoItem*pUndoItem = (IUndoItem*)clientData;
int size = pUndoItem->GetSize();
if (!size)return;
UndoItemType undoType = pUndoItem->GetUndoItemType();
if (undoType == UndoItemType::kUndoItemTypeSigle)
{
for (auto rit = m_vecUndoItem.rbegin(); rit != m_vecUndoItem.rend(); ++rit)
{
auto& undo_item = *rit;
if (undo_item.get() == clientData)
{
undo_item->Redo();
return;
}
}
}
else
{
for (auto it = m_vecUndoItem.begin(); it != m_vecUndoItem.end(); ++it)
{
auto& undo_item = *it;
if (undo_item.get() == clientData)
{
CBM_UndoHandler*pBM_UndoMultiItem = (CBM_UndoHandler*)undo_item.get();
for (auto itor = pBM_UndoMultiItem->m_vecUndoItem.begin(); itor != pBM_UndoMultiItem->m_vecUndoItem.end(); ++itor)
{
auto& undo_MultiItem = *itor;
undo_MultiItem->Redo();
}
return;
}
}
}
}
wstring CBM_UndoHandler::GetDescr()
{
//暂不考虑分Sigle和Multi,Multi默认为空
for (auto rit = m_vecUndoItem.rbegin(); rit != m_vecUndoItem.rend(); ++rit) {
auto& undo_item = *rit;
if (undo_item)
return undo_item->GetDescr();
}
return L"";
}
上面这种是将单一的操作和一组的操作都记录到全局的CBM_UndoHandler::m_vecUndoItem,单一的操作使用:
auto newItem = std::make_unique<CBM_UndoItem>(this,pNewBMDict,pParent,pAfter,pBefore,BM_ADD);
if(newItem)
{
newItem->SetDescr(IDS_STRING_UNDO_CREATE);
_gBookmarkAddUndoItem(FRAppGetActiveDocOfPDDoc(), newItem->GetDescr().c_str(), newItem.get());
CBM_UndoHandler::m_vecUndoItem.push_back(std::move(newItem));
}
而一组操作的使用为:
auto undoItems = std::make_unique<CBM_UndoItems>();
auto ImportBookmarkUndo = [&](FPD_Object pNewParentDict, FPD_Object pParent, FPD_Object pAfterImport, FPD_Object pBefore) {
auto newItem = std::make_unique<CBM_UndoItem>(this, pNewParentDict, pParent, pAfterImport, pBefore, BM_ADD);
if (newItem)
{
newItem->SetDescr(IDS_STRING_UNDO_CREATE);
if (undoItems != nullptr)
{
undoItems->m_vecUndoItem.push_back(std::move(newItem));
}
}
};
//导入可能会导致根节点的改变,进行前后的比较判断,未使用回调的方式进行处理
FPD_Object oldRootValue= CBM_PanelToolHandler::GetTreeRoot(pDoc);
std::unique_ptr<IPDF_Bookmark> pBookmark = FX_CreateBookmark(pDoc);
pBookmark->ImportBookmark(pathName, pAfter, ImportBookmarkUndo);
if (undoItems.get()->GetSize())
{
_gBookmarkAddUndoItem(FRAppGetActiveDocOfPDDoc(), undoItems->GetDescr().c_str(), undoItems.get());
CBM_UndoHandler::m_vecUndoItem.push_back(std::move(undoItems));
}
可以看到单一的操作,是直接记录到全局的CBM_UndoHandler::m_vecUndoItem中,而一组的操作是记录到自己的类成员中,然后是记录到全局的CBM_UndoHandler::m_vecUndoItem中。二者都从IUndoItem继承而来,并且提供了一个GetUndoItemType的虚函数来表示是单一操作还是一组操作,通过对传递的参数来调用GetUndoItemType便可以知道当前是哪种类型。这里提几点:
1.IUndoItem也可以改成纯虚函数,那么CBM_UndoItem和CBM_UndoItems都要进行实现,并且这两个类可以加上final来修改,不允许继承。也可以加上override关键字来修饰实现的虚函数。
2.这里对虚函数在做一点延伸说明:如果一个函数为虚函数,那么编译器是没有办法立马知道它的地址的,而对于一般的函数编译器将C++中的函数先转换成C中的函数,立马有固定的地址,哪怕传的是class C*p=nullptr,p->Fun()这也是没有问题的,因为类成员的函数地址已经是固定的,相当于为常量,而对于虚函数必须进过转换和查表来计算。虚函数被编译器隐藏起来,对外面不可见。我们可以一般的函数对用理解为下面的伪代码:
const int pfun1=aClass1;
const int pfun2=aClass2;
const int pfun3=aClass3;
//编译期
void* GetFunAddress(Class* pClassPointer,bool virtualFun)
{
if(!virtualFun)
{
if(pClassPointer==Class1)
return pfun1;
if(pClassPointer==Class2)
return pfun2;
if(pClassPointer==Class3)
return pfun3;
.....
}
//找到对应的类成员,也就是虚函数表,通过虚函数表进行查询得到真正的函数地址!!!
IntArray& vFunPtr=pClassPointer->m_pFunTable;
return vFunPtr[* pClassPointer];
}
一般的函数在没有运行哪怕是Null,都已经知道了要执行的函数地址,但是对于虚函数,编译器是不能在编译器是不能知道调用的是哪个函数,必须要知道本身的值才能确定,这在一定程度上保证了程序的灵活性。
3.
需要注意一点的是,上面的导入在进行Redo的时候是按照相反的顺序,更加符合人眼的视觉,一组里面的操作有多个,则再次按照正常的来
比如导入了三个书签:
Add
1--》2---》3,他们三个的操作放在一个CBM_UndoItems的vector<std::unique_ptr<CBM_UndoItem> > m_vecUndoItem;中,在Undo的时候,是按照删除3--》删除2--》删除1的操作,而再次Redo的时候,则要按照迭代器的正向遍历来进行处理,如果是速度很快看不出来,但是数量庞大,或者中间有提示的话,显示就会存在问题;
如果说数目只要一个的话,也不存在正反向的问题;
这中也是最直观简单的,不需要提供虚函数来进行辅助的区别,可以看成是归一化的处理。在Undo/Redo根据数量来判断就可以,它们都属于组中的成员。而在上面的情况中,单个的Item是直接由Undo/Redo的操作,而CBM_UndoItems需要通过类成员的一次执行Undo/Redo来完成一组的Undo/Redo操作。这点来说这种情况会简答些;但是从注册记录的角度看,第一种的可能会简单些,应为第一种只用一个Item,而很多对于这种的操作,可以直接注册和记录,不需要向第二种这样在最后要进行一个判断。