作者:阿门
很早以前就想整理一份这样的资料,趁着这次公司要召开研发大会 需要交一篇论文,就写下来了。截了一部分,贴上来让大家指正指正,呵呵。为公司软件实现的撤销恢复机制就不贴出来了,呵呵。。
顺便贺下 公司的第一届研发大会。期待。。。
转载请注明。。
ACAD平台的撤销恢复机制主要就是以ACAD中执行的命令为单位的,下面将从使用这一套机制入手,来试着来分析这一套机制。
如果想使用和了解ACAD的撤销恢复机制,必然要从自定义实体入手,因为,ACAD中所有的操作其实就是对DWG中数据对象的一些操作,只不过是以命令为单位来组织一个操作中的操作对象,所以ACAD的撤销恢复机制的针对对象都是ACAD的数据库对象,而已经封装好的数据库对象我们是无法了解到其内部的运行机制的,但ARX给我们提供了扩展ACAD数据库对象的机会,那就是自定义对象和实体技术,并且也预留也一些接口,用来实现自定义对象和实体的信息的撤销与恢复。首先,稍微介绍一下ARX实现自动撤销恢复与部分撤销恢复的一些接口:
a).AcDbObject::assertWriteEnable(Adesk::Boolean autoUndo = true,Adesk::Boolean recordModified = true);
该函数是用来验证当前操作节点是否以写方式打开而进行操作的,其中每一个参数autoUndo就是标记调用这个接口的成员函数中,是不是会自动进行undo记录。
b).virtual Acad::ErrorStatus AcDbObject::dwgInFields(AcDbDwgFiler * pFiler);
该函数是用来将编档器中的备忘数据恢复到对象中。
c).virtual Acad::ErrorStatus AcDbObject:: dwgOutFields( AcDbDwgFiler* pFiler);
该函数是用来将对象的状态及数据备忘到编档器中。
d).void AcDbObject::disableUndoRecording(Adesk::Boolean disable);
该函数是用来禁用、启用某对象的UNDO操作的。
e).void AcDbDatabase::disableUndoRecording(bool disable)。
该函数是用来禁用、启用整个数据库的UNDO操作的。
f).virtual Acad::ErrorStatus AcDbObject::applyPartialUndo( AcDbDwgFiler* undoFiler, AcRxClass* classObj);
该函数是控制对象局部撤销恢复信息的接口。
g); AcDbDwgFiler * AcDbObject::undoFiler();
该函数是用来返回记录对象撤销恢复记录的编档器
如何实现自动撤销?
当在对象的成员函数中使用assertWriteEnable时参数autoUndo为true则是告诉系统需要自动的实现撤销恢复记录,自动撤销恢复记录中保存的状态信息是在dwgInFields、dwgOutFields接口中对撤销编档器写入的信息;当已写方式打开一个对象时,在第一个调用其对象的成员函数中对assertWriteEnable进行了一次autoUndo参数为true的函数时,系统的UNDO机制会调用一次该对象成员dwgOutFields以备份当前对象的信息(需要备份多少信息,由用户程序自己来决定);当执行UNDO命令时,系统的UNDO机制同样会先进行一次dwgOutFields调用以备份当前对象的信息,然后再调用一次dwgInFields将之前的撤销备份信息写入对象;系统的REDO机制就是利用了UNDO机制,当执行REDO命令时,系统的UNDO机制同样会先进行一次dwgOutFields调用以备份当前对象的信息,然后再调用一次dwgInFields将之前的重做备份信息写入对象;这样就完成了系统的自动撤销恢复;下面附上少许代码:
void AMenLine::SetStartPoint( const AcGePoint3d& pt )
{
assertWriteEnabled(true,true); //自动撤销恢复
m_ptStart = pt; //设置新值
}
void AMenLine::SetEndPoint( const AcGePoint3d& pt )
{
assertWriteEnabled(true,true); //自动撤销恢复
m_ptEnd = pt; //设置新值
}
Acad::ErrorStatus AMenLine::dwgOutFields (AcDbDwgFiler *pFiler) const
{
assertReadEnabled () ;
//----- Save parent class information first.
Acad::ErrorStatus es =AcDbEntity::dwgOutFields (pFiler) ;
if ( es != Acad::eOk )
return (es) ;
//----- Object version number needs to be saved first
if ( (es =pFiler->writeUInt32 (AMenLine::kCurrentVersionNumber)) != Acad::eOk )
return (es) ;
//撤销编档器
if( pFiler->filerType() == AcDb::kUndoFiler )
{
pFiler->writePoint3d(m_ptStart);
pFiler->writePoint3d(m_ptEnd);
}
return (pFiler->filerStatus ()) ;
}
Acad::ErrorStatus AMenLine::dwgInFields (AcDbDwgFiler *pFiler)
{
assertWriteEnabled () ;
//----- Read parent class information first.
Acad::ErrorStatus es =AcDbEntity::dwgInFields (pFiler) ;
if ( es != Acad::eOk )
return (es) ;
//----- Object version number needs to be read first
Adesk::UInt32 version =0 ;
if ( (es =pFiler->readUInt32 (&version)) != Acad::eOk )
return (es) ;
if ( version > AMenLine::kCurrentVersionNumber )
return (Acad::eMakeMeProxy) ;
//撤销编档器
if( pFiler->filerType() == AcDb::kUndoFiler )
{
pFiler->readPoint3d(&m_ptStart);
pFiler->readPoint3d(&m_ptEnd);
}
return (pFiler->filerStatus ()) ;
}
如何实现局部撤销?
当在对象的成员函数中使用assertWriteEnable时参数autoUndo为false则告诉系统将由用户程员自己来实现撤销恢复,而不使用系统的自动记录来实现,从而实现局部的撤销恢复,当用户程序想实现局部撤销的时候,需要在调用assertWriteEnable的成员函数中,调用AcDbDwgFiler * AcDbObject::undoFiler();得到编档器,然后将需要实现撤销恢复的信息,写入编档器中从而完成信息状态的记录,在文档上有介绍,编入的第一个信息,应该是对象所在的AcRxClass指针,用来作判断是否是属于对应实体的信息;当系统在执行UNDO操作的时候,会如若发现有局部信息的记录,会自动调用applyPartialUndo( AcDbDwgFiler* undoFiler, AcRxClass* classObj)接口,所以,用户程序想真正的完成局部撤销的时候,还需要在这个接口中实现局部信息状态的恢复,其中applyPartialUndo接口中的AcDbDwgFiler* undoFiler参数就是保存信息状态时使用AcDbDwgFiler * AcDbObject::undoFiler()得到的那个撤销编档器,AcRxClass* classObj就是你在保存息状态的时候编入的第一个信息值(AcRxClass指针),如果在对象的多个成员函数中,都使用和实现了局部撤消机制,而在当前命令中,有调用这些接口对对象进行修改,那么在系统执行UNDO操作时,会多次调用applyPartialUndo( AcDbDwgFiler* undoFiler, AcRxClass* classObj)接口,从而实现每一个成员函数保存的信息的局部撤销,那么在设计上,你就可以在编档的时候编一个DWORD型的值,用来区分多次调用applyPartialUndo(AcDbDwgFiler* undoFiler, AcRxClass* classObj)接口的一个标记,为不同的成员函数操作导致调用到的applyPartialUndo编号,从而正确的实现多个成员函数局部撤销。与局部撤销对应的就是局部的恢复,其实,在ACAD系统中,它们使用的是同一套机制,看ARX帮助说明,就可以发现,局部撤销是通过在设置属性的SET函数中,调用相对应的接口获取撤销编档器后,把需要撤消的信息,写入编档器备份,等系统执行UNDO命令时,会根据UNDO命令栈中的数据情况,来调用实体的applyPartialUndo,从而使用户程序有机会把备份的值还原,而恢复操作,也是一个样的,它要求用户要编写applyPartialUndo接口时,不能使用直接对对象成员属性赋值的方法,而必须采用也实现了UNDO机制的SET函数(一般情况下就可以利用同一个函数),来实现对应值的设置,因为,在这个设置的过程中,其实又会对现在撤销前的值进行一个备份,当使用系统REDO机制的时候,也同样会调用applyPartialUndo,使用户程序有机会把对应的值重做,这其实就是UNDO机制的一种巧妙应用而已。下面附上少许代码:
void AMenLine::SetStartPoint( const AcGePoint3d& pt )
{
assertWriteEnabled(false,true); //局部撤销恢复
AcDbDwgFiler* pFiler = NULL;
if( (pFiler = AcDbObject::undoFiler()) != NULL )
{
pFiler->writeItem(this->desc()); //写入对象对应的运行时识别类
pFiler->writeItem(1); //为起点所编的号1
pFiler->writePoint3d(m_ptStart); //将起点源值写入撤销编档器
}
m_ptStart = pt; //设置新值
}
void AMenLine::SetEndPoint( const AcGePoint3d& pt )
{
assertWriteEnabled(false,true); //局部撤销恢复
AcDbDwgFiler* pFiler = NULL;
if( (pFiler = AcDbObject::undoFiler()) != NULL )
{
pFiler->writeItem(this->desc()); //写入对象对应的运行时识别类
pFiler->writeItem(1); //为终点所编的号1
pFiler->writePoint3d(m_ptStart); //将终点源值写入撤销编档器
}
m_ptEnd = pt; //设置新值
}
Acad::ErrorStatus AMenLine::applyPartialUndo( AcDbDwgFiler* undoFiler, AcRxClass* classObj )
{
if( this->desc() != classObj )//如果运行时信息不匹配,则调用父类的applyPartialUndo接口
return AcDbEntity::applyPartialUndo(undoFiler,classObj);
int iParam = 0;
undoFiler->readItem(&iParam);
if( 1 == iParam ) //起点的编号
{
AcGePoint3d ptStart;
undoFiler->readPoint3d(&ptStart);
SetStartPoint(ptStart); //调用实现了UNDO机制的接口,用来局部恢复
return Acad::eOk;
}
else if( 2 == iParam ) //终点的编号
{
AcGePoint3d ptEnd;
undoFiler->readPoint3d(&ptEnd);
SetEndPoint(ptEnd); //调用实现了UNDO机制的接口,用来局部恢复
return Acad::eOk;
}
return Acad::eNotImplementedYet;
}
事务机制与撤销恢复机制的关系?
相信细心的开发人员可以发现,事务机制其实就是对撤销恢复机制的灵活利用和扩展,回滚事务其实就是一种命令撤销,向事务添加一个加入了数据库的操作节点时,可以认为是向撤销恢复机制里添加了一个新建命令(顺便说一句,撤销恢复机制只能是对加入到了数据库中的节点有才效,这也是为什么向事务中添加新建节点时,这个节点必须已经加入到了数据库中),用事务打开节点,然后操作,可以看作是向撤销恢复机制添加了一个修改命令。事务机制不过就是对命令撤销恢复机制的一个批处理而已。