【原创】ACAD中撤销恢复机制的浅析

作者:阿门  

很早以前就想整理一份这样的资料,趁着这次公司要召开研发大会 需要交一篇论文,就写下来了。截了一部分,贴上来让大家指正指正,呵呵。为公司软件实现的撤销恢复机制就不贴出来了,呵呵。。

顺便贺下 公司的第一届研发大会。期待。。。

转载请注明。。

 

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;
}

事务机制与撤销恢复机制的关系?
相信细心的开发人员可以发现,事务机制其实就是对撤销恢复机制的灵活利用和扩展,回滚事务其实就是一种命令撤销,向事务添加一个加入了数据库的操作节点时,可以认为是

向撤销恢复机制里添加了一个新建命令(顺便说一句,撤销恢复机制只能是对加入到了数据库中的节点有才效,这也是为什么向事务中添加新建节点时,这个节点必须已经加入到了

数据库中),用事务打开节点,然后操作,可以看作是向撤销恢复机制添加了一个修改命令。事务机制不过就是对命令撤销恢复机制的一个批处理而已。

posted @ 2013-04-14 12:56  编号一百零二  阅读(1167)  评论(0编辑  收藏  举报