回退重做数据管理实现
项目需求
本功能是为了用户操作过程中,可以返回或者重做某个时间节点的操作状态效果的还原。可以让用户点击回退和重做按钮操作,达到某个时间点的操作状态。
前言
关于回退重做的实现之前,需要考虑目前到软件框架的执行流,目前平台操作的实现,是基于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函数中,会存储回退重做的数据到容器当中。
关于回退重做数据管理的实现
实现代码分析
- 定义回退操作的基类
对于用户需要关注回退重做事件操作,实现是需要继承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;//重做容器管理
- 增加一个完整的回退动作
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;
}
- 回退内部实现
//执行多个回退动作,并从队列中移除这些回退动作
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事件拼接成字符串,并添加到回退数据管理中。实现操作逻辑比较复杂。