回退重做数据管理实现

项目需求

本功能是为了用户操作过程中,可以返回或者重做某个时间节点的操作状态效果的还原。可以让用户点击回退和重做按钮操作,达到某个时间点的操作状态。

前言

​ 关于回退重做的实现之前,需要考虑目前到软件框架的执行流,目前平台操作的实现,是基于event事件进行实现的。而参数数据是基于Json数据进行解析获取的。继承监听类IPlantListerner,即可以通过消息传递数据。

在这里插入图片描述

关于回退重做事件的实现逻辑

用例图

在这里插入图片描述

类图

在这里插入图片描述

关于回退重做功能讲解

​ 软件架构:UI采用MFC技术做的,里面通过Cef插件,嵌入浏览器。面向的客户是:石油化工、设计院之类的,面向于工业模型的window平台软件。

​ 软件中关于操作功能的实现,根据需要可继承event事件类和监听类。而参数的传递是Json数据与字符串的相互转化,外部可传入基于json格式的字符串,event事件内部去解析获取。

​ 回退管理容器定义

typedef list<vector<pair<IReverseProc*, StringUtf8>>> ListReverseProc;
ListReverseProc   m_ReverseActions;//回退容器管理
ListReverseProc   m_ReverseRedoActions;//重做容器管理

​ 用户操作变色操作,把执行的效果数据。通过Add操作,添加到回退管理容器中。执行回退按钮操作,会遍历存储容器中的变色操作指针,执行OnReverse函数操作。在OnReverse函数中,会存储回退重做的数据到容器当中。

关于回退重做数据管理的实现

实现代码分析

  1. 定义回退操作的基类

​ 对于用户需要关注回退重做事件操作,实现是需要继承IReverseProc回退操作基类,并对其回退处理函数(OnReverse)进行实现。

// 任何需要进回退操作的类结构,请重载此类
class IReverseProc
{
public:
	IReverseProc() {}

public:

	/**
	* @brief  回退处理函数
	* @author shixun.zhang
	* @param StringUtf8 sData: 需进行回退的json字符串数据,
		如:{"sta" : {"groupID":1, "backColor":738283,"pos" : {"x":10.0, "y" : 0.0, "z" : 0.0}}}
	* @return eZErr : 成功则返回eZOk,否则返回相应错误代码
	* @note - 本函数使用 sData 将目标对象还原到上一个状态
	*/
	virtual eZErr OnReverse(StringUtf8 sData) = 0;

};

​2. 回退管理容器定义

typedef list<vector<pair<IReverseProc*, StringUtf8>>> ListReverseProc;
ListReverseProc   m_ReverseActions;//回退容器管理
ListReverseProc   m_ReverseRedoActions;//重做容器管理
  1. 增加一个完整的回退动作
eZErr CReverseManager::AddOneReverse(IReverseProc* pOpera, StringUtf8 sData)
{
	if (!pOpera || sData.size() <= 0)
		return eZInvalidData;

	StringUtf8 sReverseState = GetReverseState(sData);//获取执行的类型操作 Undo/Redo
	if (m_bInTransaction)
		return eInTransaction;

	if (m_nMaxReverseStep <= 0)
		return eZOk;
	//添加数据及操作对象指针,存入容器中
	vector<pair<IReverseProc*, string>> vects;
	vects.push_back(std::make_pair(pOpera, sData));

	m_bInTransaction = true;
	if (sReverseState == "Redo")
	{
		m_ReverseRedoActions.push_back(vects);//添加数据及操作对象指针,存入Redo数据管理中

		if (m_ReverseRedoActions.size() > m_nMaxReverseStep)
			m_ReverseRedoActions.erase(m_ReverseRedoActions.begin());
	}
	else
	{
		m_ReverseActions.push_back(vects);//添加数据及操作对象指针,存入Undo数据管理中

		if (m_ReverseActions.size() > m_nMaxReverseStep)//超过最大限制数,会移除首部元素
			m_ReverseActions.erase(m_ReverseActions.begin());
	}

	m_bInTransaction = false;

	return eZOk;
}
  1. 回退内部实现
//执行多个回退动作,并从队列中移除这些回退动作
eZErr CReverseManager::BeginReverses(int nStep)
{
	return __BeginReverses(m_ReverseActions, nStep);
}

//执行多个重做动作,并从队列中移除这些重做动作
eZErr CReverseManager::BeginReversesRedo(int nStep)
{
	return __BeginReverses(m_ReverseRedoActions, nStep);
}

eZErr CReverseManager::__BeginReverses(ListReverseProc& ListResv,int nStep)
{
	// 	if (nStep == 0 || nStep>= ListResv.size())
	// 		return eZInvalidData;

	if (nStep == -1)//传入为-1,则移除所有的回退动作
		nStep = (int)(ListResv.size());
	else if (nStep <= 0 || nStep > ListResv.size())
		nStep = (int)ListResv.size();

	if (m_bInTransaction)
	{
		EndTransaction();
	}

	vector<pair<IReverseProc *, StringUtf8>> ListProc;
	m_bInTransaction = true;
	if (nStep > 0)
	{
		auto IterResv = ListResv.rbegin();
		for (int nResv = nStep; nResv > 0; nResv--, IterResv++)
		{
			for (int i = 0; i < IterResv->size(); i++)
			{
				IReverseProc* pProc = (*IterResv)[i].first;
				if (pProc)
					ListProc.push_back(std::make_pair(pProc, (*IterResv)[i].second));
			}
		}

		for (int i = 0; i < nStep; i++)
			ListResv.erase(prev(ListResv.end()));
	}
	m_bInTransaction = false;

    //遍历指定几步中,回退重做数据管理包含记录的数据操作,恢复效果
 	for (int i = 0; i < ListProc.size();i++)
 	{
 		ListProc[i].first->OnReverse(ListProc[i].second);
 	}
 	ListProc.clear();

	return eZOk;
}

3、从队列中移除多个回退动作 内部实现

//从队列中移除多个回退动作
eZErr CReverseManager::PopReverses(int nStep)
{
	return __PopReversesRedo(m_ReverseActions, nStep);
}

//从队列中移除多个重做动作
eZErr CReverseManager::PopReversesRedo(int nStep)
{
	return __PopReversesRedo(m_ReverseRedoActions,nStep);
}

// 从队列中移除多个回退动作 内部实现
eZErr CReverseManager::__PopReversesRedo(ListReverseProc& ListResv, int nStep)
{
	// 	if (nStep == 0 || nStep >= m_ReverseActions.size())
	// 		return eZInvalidData;

	if (nStep == -1)
		nStep = (int)(ListResv.size());
	else if (nStep <= 0 || nStep > ListResv.size())
		nStep = (int)ListResv.size();

	if (m_bInTransaction)
	{
		EndTransaction();
	}

	m_bInTransaction = true;

	for (int i = 0; i < nStep; i++)
		ListResv.erase(prev(ListResv.end()));

	m_bInTransaction = false;

	return eZOk;
}

基于回退管理的颜色标记操作实现

描述:

​ 颜色标记功能,是对指定组中节点元素进行颜色设置功能,每次对组内元素进行颜色标记时,会清空上一次的标记元素,始终会保留最近的颜色标记。并可根据回退和重置按钮,对节点颜色标记效果的回退和重置。

源码分析

/************************************************************
  节点临时标记
  {
  "func":"ScriptEvent_SelectionSetTempColor"
  }
  
  备注:
  1、支持选中的节点进行临时标记和取消临时标记
  2、当选中另一组(选中多个节点加入到LT_AddToSelection时)标记元素时,会把上一组标记元素还原至元素本身的元素
  3、支持undo/redo操作
  4、右键菜单 支持临时标记功能
************************************************************/

1、执行颜色标记事件

 EnEventExcute CSelectionSetTempColor::excute(EventData* pInEventData, string& retname)
 {
	 bool isClearSelNode = false;
	 list<INode*>* selectionset = m_pModelload->GetSelectionSet();
	 if (!selectionset || selectionset->size() <= 0)
		 return EVENT_OK;

	 Uint32 color = NULL;
	 color = m_pModelload->GetPlantFrameInterface()->GetStructp()->m_Display.m_TempSignColor;
	 if (color == NULL)
	 {
		 //pIModelManageModule->ClearSelectionNode();                           //清空选择集颜色
		 return EVENT_CANCELED;
	 }

	 if (true)
	 {
		 list<INode*> nodeSignList;
		 const std::map<INode*, Uint32> signINodeMap = m_pModelload->GetSignNodeList();
		 //INode of the Sign node map save to list
		 auto iter = signINodeMap.begin();
		 while (iter != signINodeMap.end()) {
			 nodeSignList.push_back(iter->first);
			 iter++;
		 }

		 //0. 存储上次标记的信息
		 if (nodeSignList.size())
		 {
			 PuttoReverseList(&nodeSignList, false, true);
		 }

		 if (selectionset->size())
		 {
			 //1. 存储上次步骤执行的json信息
			 if (m_pModelload->GetPlantFrameInterface()->GetStructp()->m_Display.m_bEnableUndo)
			 {
				 PuttoReverseList(selectionset, false, true);
			 }

			 //2. 取消之前节点的临时标记,并从数据管理中删除
			 for (auto pNode : nodeSignList)
			 {
				 if (m_pModelload->IsINodeAtSignINodeList(pNode))
				 {
					 pNode->UnsetTempColor();
					 m_pModelload->EraseSignINodeFormList(pNode);
				 } 
			 }

		 }


		 //3. 设置当前标记json数据,并记录数据管理
		 for (auto pNode : *selectionset)
		 {
			 pNode->SetTempColor(color, -1);
			 m_pModelload->AddSignINodeToList(pNode, color);//放入undo数据管理
		 }

		 m_pModelload->ClearSelectionNode();                           //清空选择集颜色

		 list<INode*> addNodelist;
		 m_pModelload->notice_web_selection(addNodelist);
	 }

	 m_pModelload->GetPlantFrameInterface()->notice_listener(LT_NodesStateChanged, selectionset, this);

	 return EVENT_OK;
 }

2、取消颜色标记功能

 void CSelectionSetTempColor::undo_excute(EventData* pReturnData)
 {
	 list<INode*>* selectionset = m_pModelload->GetSelectionSet();
	 if (!selectionset)
		 return;

	 for (auto pNode : *selectionset)
	 {
		 pNode->UnsetTempColor();
		 m_pModelload->EraseSignINodeFormList(pNode);
	 }

	 m_pModelload->GetPlantFrameInterface()->notice_listener(LT_NodesStateChanged, selectionset, this);
 }

3、把数据转成json格式的字符串,并存储到回退数据管理中

bool CSelectionSetTempColor::PuttoReverseList(const list<INode*>* selectionset, bool bRedo, bool bPrimary)
 {
	 if (!selectionset || selectionset->size() <= 0)
		 return false;

	 IQBaseBusinessInterface* pBaseBusy = dynamic_cast<IQBaseBusinessInterface*>(m_pModelload->GetPlantFrameInterface()->GetModuleInterface("QBaseBusiness.QWebWnd"));
	 if (!pBaseBusy)
		 return false;

	 if (bPrimary)
	 {
		 pBaseBusy->RemoveReverses("Redo", -1);
	 }

	 {
		 Json::Value DataSet;
		 DataSet[IFSTR_MSG] = "ScriptEvent_SelectionSetTempColor";

		 StringUtf8 sData;
		 m_pModelload->TakeNodesBaseState(*selectionset, sData, NODE_STATE_COLORCHANGED, 4);

		 Json::Value sta;
		 if (bRedo)
		 {
			 sta["UndoState"] = "Redo";
		 }

		 sta[IFSTR_INFO] = sData;
		 DataSet["sta"] = sta;

		 Json::FastWriter fw;
		 string sUndoData = fw.write(DataSet);
		 pBaseBusy->AddOneReverse(this, sUndoData);
		 LOG_DEBUGA(sUndoData.c_str());
	 }
	 return true;
 }

4、回退重做函数的实现

eZErr CSelectionSetTempColor::OnReverse(StringUtf8 sData)
 {
	 //1. 解析undo/redo执行的json信息
	 Json::Value sRes;
	 Json::Reader _mReader;
	 _mReader.parse(sData.c_str(), sRes);

	 if (!sRes.isMember("sta"))
		 return eZInvalidData;

	 Json::Value sta = sRes["sta"];
	 if (!sta.isMember(IFSTR_INFO))
		 return eZInvalidData;

	 StringUtf8 sInfos = sta[IFSTR_INFO].asString();

	 Json::Value sJInfo;
	 Json::Reader _mReaderInfo;
	 _mReaderInfo.parse(sInfos.c_str(), sJInfo);
	 if (!sJInfo.isArray())
		 return eZInvalidData;

	 list<INode*> LNode;
	 map<INode*, int> mapNodeState;
	 map<INode*, Uint32> mapNodeVal;

	 IModeloadInterface* pIModelManageModule = dynamic_cast<IModeloadInterface*>(m_pModelload->GetPlantFrameInterface()->GetModuleInterface(string("MODEL.MODELOAD")));
	 for (int i = 0; i < sJInfo.size(); i++)
	 {
		 auto Node = sJInfo[i];
		 INode* pNode = pIModelManageModule->GetNodeFromIdent(Node["handle"].asString(), 4);
		 if (pNode)
		 {
			 LNode.push_back(pNode);
			 int nState = Node["BaseState"].asInt();
			 mapNodeState[pNode] = nState;
			 Uint32 nVal = 0;
			 if (Node.isMember(IFSTR_COLOR))
			 {
				 string strVal = Node[IFSTR_COLOR].asString();
				 nVal = strtoul(strVal.c_str(), NULL, 10);
			 }
			 mapNodeVal[pNode] = nVal;
		 }
	 }

	 //2.获取执行前,存储的标记节点元素,用于做存储,并做清除数据管理
	 list<INode*> nodeSignList;
	 const std::map<INode*, Uint32> signINodeMap = m_pModelload->GetSignNodeList();
	 if (signINodeMap.size())
	 {
		 //INode of the Sign node map save to list
		 auto iter = signINodeMap.begin();
		 while (iter != signINodeMap.end()) {
			 nodeSignList.push_back(iter->first);
			 iter++;
		 }
	 }

	 list<INode*>* selectionset = m_pModelload->GetSelectionSet();

	 if (LNode.size() > 0)
	 {
		 if (true)	// 加入到redo队列中去
		 {
			 bool bInRedoState = false;
			 if (sta.isMember("UndoState"))
			 {
				 StringUtf8 sInfos = sta["UndoState"].asString();
				 if (sInfos == "Redo")
					 bInRedoState = true;
			 }

			 //3. 存储当前执行节点元素,做数据存储
			 PuttoReverseList(&LNode, !bInRedoState, false);
			 
			 //清除原有标记点颜色
			 for (auto Item : *selectionset)
			 {
				 if (m_pModelload->IsINodeAtSignINodeList(Item))
					 Item->UnsetTempColor();
				 m_pModelload->EraseSignINodeFormList(Item);
			 }
				
		 }

		 //4. 执行undo/redo执行包含的json信息
		 int nState = 0;
		 for (auto Item : LNode)
		 {
			 m_pModelload->AddSignINodeToList(Item, mapNodeVal[Item]);

			 nState = mapNodeState[Item];
			 if ((nState & NODE_STATE_COLORCHANGED) == NODE_STATE_COLORCHANGED)
			 {
				 if (!mapNodeVal[Item])
					 Item->UnsetTempColor();
				 else
					 Item->SetTempColor(mapNodeVal[Item], -1);
			 }
			 else
			 {
					 Item->UnsetTempColor();
			 }
		 }
	 }


	 m_pModelload->GetPlantFrameInterface()->notice_listener(LT_NodesStateChanged, &LNode, this);
	 int bAvailable = -1;
	 if (selectionset && selectionset->size() > 0)
	 {
		 for (auto Item : *selectionset)
		 {
			 if (m_pModelload->IsINodeAtSignINodeList(Item))
			 {
				 bAvailable = 1;
				 break;
			 }
		 }
	 }
	 m_pModelload->GetPlantFrameInterface()->notice_listener(m_event_type, &bAvailable, this);

	 return eZOk;
 }

回退重置事件的实现

回退事件

1、函数定义

// 执行undo动作,默认为1步
// step:undo的步数
// {"func":"ScriptEvent_UserUndo","sta":{"step":1}}
class CUserUndoEvent : public IScriptEvent, public IPlantListerner
{
public:
	CUserUndoEvent(CNetBaseBusiness* pBaseBusiness);
	~CUserUndoEvent(){};
public:
	EnEventExcute excute(EventData* pInEventData, string& retname);

	bool Initialize();
	virtual void OnNoticeCome(Listener_Type type, void*  pData, void* pSender);

private:
	CNetBaseBusiness* m_pBaseBusiness;
};

2、回退功能的实现

EnEventExcute CUserUndoEvent::excute(EventData* pInEventData, string& retname)
{
	int nStep = 1;

	Json::Value jval = pInEventData->m_Var;
	if (jval.isMember("step"))
		nStep = jval["step"].asInt();
	
    //执行回退功能
	EnEventExcute eRet = m_pBaseBusiness->BeginReverses(nStep) ? EVENT_OK : EVENT_FAILED;

    //菜单按钮状态显示的更改
	short bAvailable = m_pBaseBusiness->IsReverseAvaliable() ? 0 : -1;
	m_pBaseBusiness->GetPlantFrameInterface()->notice_listener(m_event_type, &bAvailable, this);

	bAvailable = m_pBaseBusiness->IsReverseRedoAvaliable() ? 0 : -1;
	m_pBaseBusiness->GetPlantFrameInterface()->notice_listener(Event_UserRedo, &bAvailable, this);

	return eRet;
}

重置事件

1、函数定义

// 执行redo动作,默认为1步
// step:redo的步数
// {"func":"ScriptEvent_UserRedo","sta":{"step":1}}
class CUserRedoEvent : public IScriptEvent, public IPlantListerner
{
public:
	CUserRedoEvent(CNetBaseBusiness* pBaseBusiness);
	~CUserRedoEvent(){};
public:
	EnEventExcute excute(EventData* pInEventData, string& retname);

	bool Initialize();
	virtual void OnNoticeCome(Listener_Type type, void*  pData, void* pSender);

private:
	CNetBaseBusiness* m_pBaseBusiness;
};

2、重置功能实现

EnEventExcute CUserRedoEvent::excute(EventData* pInEventData, string& retname)
{
	int nStep = 1;

	Json::Value jval = pInEventData->m_Var;
	if (jval.isMember("step"))
		nStep = jval["step"].asInt();

	EnEventExcute eRet = m_pBaseBusiness->BeginReversesRedo(nStep) ? EVENT_OK : EVENT_FAILED;

	short bAvailable = m_pBaseBusiness->IsReverseRedoAvaliable() ? 0 : -1;
	m_pBaseBusiness->GetPlantFrameInterface()->notice_listener(m_event_type, &bAvailable, this);

	return eRet;
}

目前实现框架的优势和局限

优点

1、对需要关注回退的操作实现,只要继承IReverseProc回退操作基类,每次执行操作,把数据存入到到Undo数据容器中,即可实现操作的回退重做行为效果

2、此回退类实现,目前只嵌入了Event事件操作,但可扩展到其他类形式实现,数据存储只关注数据及IReverseProc派生的类指针。

局限

1、需要关注回退的操作的event事件类,继承IReverseProc后。回退函数的实现,需要程序员根据自己逻辑进行数据解析实现。

2、每次执行操作后,需要对数据做event事件拼接成字符串,并添加到回退数据管理中。实现操作逻辑比较复杂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值