2017-06-16 读春哥代码:do,redo,undo的前世今生

1.do

用户点击绘图操作,鼠标左键第二次按下的时候,View类拦截了该消息,挨个询问所有的CMD类是否要处理LButtonDown消息。当询问到DrectCmd时,DrectCmd发现自己需要响应该消息,于是调用了自己的OnLButtonDown函数。OnLButtonDown函数判断是否是第二次点击左键,发现是的,那么绘图工作就完成了于是调用自己_Finished()函数完成这个绘图操作。那么,这个_Finished()是如何工作的呢?先看源代码:

void CDRectCmd::_Finished()
{
	if (m_Drawstep != step_FixRightBottom)
	{
		// 选择画矩形功能,然后在画图区直接双击,会触发这个断言 孙彦春 2015-12-23
		//ASSERT(FALSE);
		_GiveUp();
		return;
	}

	ASSERT(m_ptLeftTop.IsValidateData());
	ASSERT(m_ptRightBottom.IsValidateData());

	double _nX = _GetLeft(m_ptLeftTop, m_ptRightBottom);
	double _nY = _GetBottom(m_ptLeftTop, m_ptRightBottom);
	double _nWidth = fabs(m_ptLeftTop.x - m_ptRightBottom.x);
	double _nHeight = fabs(m_ptLeftTop.y - m_ptRightBottom.y);

	DPoint2 _pts[5] = 
	{
		DPoint2(_nX, _nY),
		DPoint2(_nX + _nWidth, _nY),
		DPoint2(_nX + _nWidth, _nY + _nHeight),
		DPoint2(_nX, _nY + _nHeight),
		DPoint2(_nX, _nY),
	};

	CNtMultLine* _pMultLine = new CNtMultLine;
	_pMultLine->SetStartPt(_pts[0]);
	_pMultLine->AddTailLine(&_pts[1], 4);

	CDrawOperater* _pDrawOperater = new CDrawOperater;
	_pDrawOperater->m_pGeoMetry = _pMultLine;
	_pDrawOperater->Do();

	SetModifiedFlag();
	m_ptLeftTop.SetInValidate();
	m_ptRightBottom.SetInValidate();
	m_ptMouseMove.SetInValidate();
	m_Drawstep = step_NotBegin;
	SetActive(false);
}
基本的过程是,先根据之前的两次点击鼠标,计算出画出的图形_pMultLine,然后new一个CDrawOperater指针_pDrawOperater,然后通过这个_pDrawOperater来操作数据最终完成绘图操作。软件几乎所有的工作流程都是这个思想,先由view或者frame类拦截用户的消息,然后交给对应的cmd,cmd再调用operater操作数据。

_pDrawOperater->Do()背后发生的事情远不是看起来这么简单,看源代码:

COperater::OperaterType CDrawOperater::Do()
{
	__super::Do();
	ASSERT(m_pGeoMetry);
	CFileCmd* _pFileCmd = GetFileCmd();
	_pFileCmd->m_pMemFile->m_pGeoGroup->push_back(m_pGeoMetry);
	m_strGeoType = m_pGeoMetry->GetName();
	m_pGeoMetry = NULL;
	return OT_DrawGeometry;
}

注意这里的,它调用了基类的Do函数,让我们看看基类的Do函数干了什么:

COperater::OperaterType COperater::Do()
{
	ASSERT(GetState() == Status_notbegin);
	SetState(Status_done);
	COpCollectCmd* _pOpCollectCmd = GetOpCollectCmd();
	ASSERT(_pOpCollectCmd);
	_pOpCollectCmd->push_back(this);
	return OT_Invalidate;
}

也就是说,

_pOpCollectCmd->push_back(this)这里,将CDrawOperater的指针加入了m_OperaterList。所有的操作都像这样保存在这个list中,方便进行撤销和重做操作。

void COpCollectCmd::push_back(COperater* pOperater_)
{
	ASSERT(pOperater_ && pOperater_->GetState() == COperater::Status_done);
	if (m_OperaterList.size() == 0)
		m_OperaterIt = m_OperaterList.rbegin();

	if (m_OperaterIt != m_OperaterList.rbegin())
	{
		int _nConut = 0;
		for (OperaterList::reverse_iterator _rIt = m_OperaterList.rbegin(); 
			_rIt != m_OperaterIt; ++_rIt)
		{
			++_nConut;
			COperater* _pOperater = (*_rIt);
			delete _pOperater;
		}
		for (int _i = 0; _i != _nConut; ++_i)
		{
			m_OperaterList.pop_back();
		}
	}

	m_OperaterList.push_back(pOperater_);
	m_OperaterIt = m_OperaterList.rbegin();
}

每次编辑过后,m_OperaterIt都指向m_OperaterList的末尾。undo操作会让m_OperaterIt往回移动,redo操作会让m_OperaterIt往前移动。当do的时候发现m_OperaterIt没有指向末尾,说明属于撤销之后重做,需要从m_OperaterList删掉准备redo的operater。下面将具体揭秘undo和redo的操作过程。


2.undo

总的流程是frame类拦截到undo消息后找到COpCollectCmd来处理,然后COpCollectCmd找到了CDrawOperater来执行undo操作。看代码:

void COpCollectCmd::OnUnDo()
{
	if (!IsUndoValidate())
	{
		ShowPrompt(_T("cantundo"));
		return ;
	}
	COperater* _pOperater = *m_OperaterIt;
	COperater::OperaterType _OT_type = _pOperater->Undo();
	_NotiFyChange(_OT_type);
	++m_OperaterIt;
	UpdateMainView();
}

m_OperaterIt迭代器当前所指向的operater指针所指向的operater来执行undo操作,并且往左移动迭代器(m_OperaterIt是反向迭代器,所以++是往左移动)。执行undo操作后,要更新视图。再看 CDrawOperater::Undo()具体的执行过程,先调用基类的undo操作,貌似没有什么实质性的操作,将状态设置为undo。

COperater::OperaterType COperater::Undo()
{
	SetState(Status_undo);
	COpCollectCmd* _pOpCollectCmd = GetOpCollectCmd();
	ASSERT(_pOpCollectCmd);
	//_pOpCollectCmd->pop_back(this);
	return OT_Invalidate;
}

再继续看CDrawOperater::Undo(),将Group中的最后一个图形指针保存到m_pGeoMetry(m_pGeoMetry是CDrawOperater的成员,是一个指向图形的指针)。然后删除Group中最后一个图形的指针。简单的说就是从group中挖出来保存到CDrawOperater中了。

COperater::OperaterType CDrawOperater::Undo()
{
	__super::Undo();
	CFileCmd* _pFileCmd = GetFileCmd();
	CNtGroup* _pGroup = _pFileCmd->m_pMemFile->m_pGeoGroup;
	ASSERT(_pGroup->size());
	ASSERT(!m_pGeoMetry);
	m_pGeoMetry = _pGroup->back();
	_pGroup->pop_back();
	return OT_DrawGeometry;
}


3.redo

redo的过程是undo的逆过程,同样是frame类拦截到redo消息后找到COpCollectCmd来处理,然后COpCollectCmd找到了CDrawOperater来执行redo操作。当初是谁do的,后面相关的undo操作和redo操作,也都要找当事人operater来负责处理。

void COpCollectCmd::OnReDo()
{
	if (!IsRedoValidate())
	{
		ShowPrompt(_T("cantredo"));
		return ;
	}
	COperater* _pOperater = *(--m_OperaterIt);
	COperater::OperaterType _OT_type = _pOperater->ReDo();
	_NotiFyChange(_OT_type);
	UpdateMainView();
}

COpCollectCmd中,COperater* _pOperater = *(--m_OperaterIt);是迭代器先向右移动一位,然后再将该位置的内容取出来(是一个指向某个operater的指针)。然后根据这个指针找到对应的operater来执行redo操作。看看_pOperater->ReDo()到底做了什么:也是先调用基类的redo,估计像undo一样没做什么卵事,顶多是将状态设置为done之类的,就不截图了。继续看下去貌似就是把之前undo时候保存的图形指针重新加入肯德基豪华套餐group中。

COperater::OperaterType CDrawOperater::ReDo()
{
	__super::ReDo();

	CFileCmd* _pFileCmd = GetFileCmd();
	_pFileCmd->m_pMemFile->m_pGeoGroup->push_back(m_pGeoMetry);
	m_pGeoMetry = NULL;
	return OT_DrawGeometry;
}

总结一下,就是每发出一些指令commander,基本都会产生对应的operater来操作数据。产生的operater执行do函数的时候会调用基类的do函数将这个operater的指针加入list中,然后用反向迭代器m_OperaterIt来指向这个指针,此时m_pGeoMetry = NULL。(这里的m_pGeoMetry 是保存现场的容器,不同的operater有所不同,例如处理平移操作的COffSetOperater中就使用NtGeometry::CNtGroup* m_pGroup和NtMAD::DPoint2 m_ptOffSet这两个变量来保存现场,各个operater具体的undo和redo也都有所不同)。

若此时发出undo命令,就将指向group中的最后一个图形的指针(这个图形也是当初这个operater加进来的)挖出来保存到m_pGeoMetry 之中,然后迭代器左移一位。迭代器最后指向的一定都是处于done状态下的最后一个operater指针。若发出redo命令,迭代器就向右移动,然后将保存在m_pGeoMetry 中的图形指针加入到group中。很容易理解,undo是当前指向的operater来执行,redo是当前所指向的operater右边的一个operater来执行。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值