Example: Building in Object Dependencies

The following example shows how you can use reactors to establish dependencies among database objects. In this example, when you change one line, the other line changes.

class AsdkObjectToNotify : public AcDbObject
//
// AsdkObjectToNotify - customized AcDbObject for persistent
// reactor to notify.
//
{
public:
    ACRX_DECLARE_MEMBERS(AsdkObjectToNotify);
    AsdkObjectToNotify() {};
    void eLinkage(AcDbObjectId i, double f=1.0)
        {mId=i; mFactor=f; };
    void              modified(const AcDbObject*);
    Acad::ErrorStatus dwgInFields(AcDbDwgFiler*);
    Acad::ErrorStatus dwgOutFields(AcDbDwgFiler*) const;
    Acad::ErrorStatus dxfInFields(AcDbDxfFiler*);
    Acad::ErrorStatus dxfOutFields(AcDbDxfFiler*) const;
private:
    AcDbObjectId mId;
    double mFactor;
};
ACRX_DXF_DEFINE_MEMBERS(AsdkObjectToNotify, AcDbObject, 
    AcDb::kDHL_CURRENT, AcDb::kMReleaseCurrent, 
    0, ASDKOBJECTTONOTIFY, persreac);
// This function is called every time the line it's
// "watching" is modified. When it's called, it opens the
// other line of the pair and changes that line's length to
// match the new length of the line that's just been
// modified.
// 
void
AsdkObjectToNotify::modified(const AcDbObject* pObj)
{
    AcDbLine *pLine = AcDbLine::cast(pObj);
    if (!pLine) {
        const char* cstr = pObj->isA()->name();
        acutPrintf("This is a %s.\n", cstr);
        acutPrintf("I only work with lines. Sorry.\n");
        return;
    }
    acutPrintf("\nReactor attached to %lx calling %lx.\n",
        pLine->objectId(), mId);
    // This open fails during notification caused by a
    // reactor being added to the entity or when this
    // notification is in reaction to a change due to the
    // other line's reactor changing this line. This
    // properly prevents an infinite recursive loop
    // between the two lines and their reactors.
    // 
    AcDbLine *pLine2;
    if (acdbOpenObject((AcDbObject*&)pLine2, mId,
        AcDb::kForWrite) == Acad::eOk)
    {
        // Gets length of line entity we're being notified
        // has just been modified.
        //
        AcGePoint3d p = pLine->startPoint();
        AcGePoint3d q = pLine->endPoint();
        AcGeVector3d v = q-p;
        double len = v.length();
        // Updates other entity to match.
        //
        p = pLine2->startPoint();
        q = pLine2->endPoint();
        v = q-p;
        v = len * mFactor * v.normal();
        pLine2->setEndPoint(p+v);
        pLine2->close();
    }
}
// Files an object's information in.
//
Acad::ErrorStatus
AsdkObjectToNotify::dwgInFields(AcDbDwgFiler* filer)
{
    assertWriteEnabled();
    AcDbObject::dwgInFields(filer);
    filer->readItem(&mFactor);
    filer->readItem((AcDbSoftPointerId*) &mId);
    return filer->filerStatus();
}
// Files an object's information out.
//
Acad::ErrorStatus
AsdkObjectToNotify::dwgOutFields(AcDbDwgFiler* filer) const
{
    assertReadEnabled();
    AcDbObject::dwgOutFields(filer);
    filer->writeItem(mFactor);
    filer->writeItem((AcDbSoftPointerId&)mId);
    return filer->filerStatus();
}
// Files an object's information in from DXF and AutoLISP.
//
Acad::ErrorStatus
AsdkObjectToNotify::dxfInFields(AcDbDxfFiler* filer)
{
    assertWriteEnabled();
    Acad::ErrorStatus es;
    if ((es = AcDbObject::dxfInFields(filer))
        != Acad::eOk)
    {
        return es;
    }
    // Checks if we're at the right subclass data marker.
    //
    if(!filer->atSubclassData("AsdkObjectToNotify")) {
        return Acad::eBadDxfSequence;
    }
    struct resbuf rbIn;
    while (es == Acad::eOk) {
        if ((es = filer->readItem(&rbIn)) == Acad::eOk) {
            if (rbIn.restype == AcDb::kDxfReal) {
                mFactor = rbIn.resval.rreal;
            } else if (rbIn.restype
                == AcDb::kDxfSoftPointerId)
            {
                // ObjectIds are filed in as ads_names.
                // 
                acdbGetObjectId(mId, rbIn.resval.rlname);
            } else {   // invalid group
                return(filer->pushBackItem());
            }
        }
    }
    return filer->filerStatus();
}
// Files an object's information out to DXF and AutoLISP.
//
Acad::ErrorStatus
AsdkObjectToNotify::dxfOutFields(AcDbDxfFiler* filer) const
{
    assertReadEnabled();
    AcDbObject::dxfOutFields(filer);
    filer->writeItem(AcDb::kDxfSubclass,
        "AsdkObjectToNotify");
    filer->writeItem(AcDb::kDxfReal, mFactor);
    filer->writeItem(AcDb::kDxfSoftPointerId, mId);
    return filer->filerStatus();
}
// Creates two lines and two AsdkObjectToNotify objects and
// ties them all together.
//
void
assocLines()
{
    AcDbDatabase *pDb = 
        acdbHostApplicationServices()->workingDatabase();
    AcDbObjectId aId, bId;
    AcDbLine *pLineA = new AcDbLine;
    pLineA->setDatabaseDefaults(pDb);
    pLineA->setStartPoint(AcGePoint3d(1, 1, 0));
    pLineA->setEndPoint(AcGePoint3d(2, 1, 0));
    addToModelSpace(aId, pLineA);
    acutPrintf( "Line A is %lx from 1,1 to 2,1.\n",
        pLineA->objectId());
    AcDbLine *pLineB = new AcDbLine;
    pLineB->setDatabaseDefaults(pDb);
    pLineB->setStartPoint(AcGePoint3d(1, 2, 0));
    pLineB->setEndPoint(AcGePoint3d(2, 2, 0));
    addToModelSpace(bId, pLineB);
    acutPrintf("Line B is %lx from 1,2 to 2,2.\n",
        pLineB->objectId());
    // Opens the named object dictionary, and checks if there is
    // an entry with the key "ASDK_DICT". If not, creates a
    // dictionary and add it.
    // 
    AcDbDictionary *pNamedObj;
    AcDbDictionary *pNameList;
    pDb->getNamedObjectsDictionary(pNamedObj,
        AcDb::kForWrite);
    if (pNamedObj->getAt("ASDK_DICT",
        (AcDbObject*&)pNameList, AcDb::kForWrite)
            == Acad::eKeyNotFound)
    {
        pNameList = new AcDbDictionary;
        AcDbObjectId DictId;
        pNamedObj->setAt("ASDK_DICT", pNameList, DictId);
    }
    pNamedObj->close();
    // Creates the AsdkObjectToNotify for line A.
    //
    AsdkObjectToNotify *pObj = new AsdkObjectToNotify();
    pObj->eLinkage(bId);
    AcDbObjectId objId;
    if ((pNameList->getAt("object_to_notify_A", objId))
        == Acad::eKeyNotFound)
    {
        pNameList->setAt("object_to_notify_A", pObj, objId);
        pObj->close();
    } else {
        delete pObj;
        acutPrintf("object_to_notify_A already exists\n");
    }
    // Sets up persistent reactor link between line A
    // and AsdkObjectToNotify.
    //
    pLineA->addPersistentReactor(objId);
    pLineA->close();
    // Creates the AsdkObjectToNotify for line B.
    //
    pObj = new AsdkObjectToNotify();
    pObj->eLinkage(aId);
    if ((pNameList->getAt("object_to_notify_B", objId))
        == Acad::eKeyNotFound)
    {
        pNameList->setAt("object_to_notify_B", pObj, objId);
        pObj->close();
    } else {
        delete pObj;
        acutPrintf("object_to_notify_B already exists\n");
    }
    pNameList->close();
    // Sets up persistent reactor link between line B
    // and AsdkObjectToNotify.
    //
    pLineB->addPersistentReactor(objId);
    pLineB->close();
}
// Adds an entity to model space, but does not close
// the entity.
//
void
addToModelSpace(AcDbObjectId &objId, AcDbEntity* pEntity)
{
    AcDbBlockTable *pBlockTable;
    AcDbBlockTableRecord *pSpaceRecord;
    acdbHostApplicationServices()->workingDatabase()
        ->getSymbolTable(pBlockTable, AcDb::kForRead);
    pBlockTable->getAt(ACDB_MODEL_SPACE, pSpaceRecord,
        AcDb::kForWrite);
    pBlockTable->close();
    pSpaceRecord->appendAcDbEntity(objId, pEntity);
    pSpaceRecord->close();
    return;
}
// This is the initialization function called from acrxEntryPoint()
// during the kInitAppMsg case.  This function is used to add
// commands to the command stack.
// 
void
initApp()
{
    acedRegCmds->addCommand("ASDK_ALINES", "ASDK_ALINES",
        "ALINES", ACRX_CMD_MODAL, assocLines);
    AsdkObjectToNotify::rxInit();
    acrxBuildClassHierarchy();
}
// This is the clean-up function called from acrxEntryPoint() during
// the kUnloadAppMsg case. This function removes this application's
// command set from the command stack.
// 
void
unloadApp()
{
    acedRegCmds->removeGroup("ASDK_ALINES");
    // Removes the AsdkObjectToNotify class from the ACRX
    // runtime class hierarchy. If this is done while the
    // database is still active, it should cause all objects
    // of class AsdkObjectToNotify to be turned into proxies.
    // 
    deleteAcRxClass(AsdkObjectToNotify::desc());
}
// ObjectARX entry point
//
extern "C" AcRx::AppRetCode
acrxEntryPoint(AcRx::AppMsgCode msg, void* appId)
{
    switch (msg) {
    case AcRx::kInitAppMsg:
        acrxDynamicLinker->unlockApplication(appId);
        acrxDynamicLinker->registerAppMDIAware(appId);
        initApp();
        break;
    case AcRx::kUnloadAppMsg:
        unloadApp();
    }
    return AcRx::kRetOK;
}