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来执行。