当前位置: 首页 > 工具软件 > jQuery Undo > 使用案例 >

插件Undo/Redo两种方式封装使用

蒲深
2023-12-01

在做插件开发时,插件本身的操作要和框架进行交互,一般由插件自身去记录修改的内容,让后将每一次的操作标识指针交给框架,在适当的时候进行触发处理。下面给出大致的伪代码和原理。

一.将单个操作和一组操作一并进行注册

下面给出基本的演示代码如下:


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,而很多对于这种的操作,可以直接注册和记录,不需要向第二种这样在最后要进行一个判断。

大家在实际的使用时可以根据情况进行选取,各有优劣,笔者重在对此进行记录和说明!

 类似资料: