[CONNECT C++] DgnElementSetTool fence & selection processing

I seek guidance on writing a tool that performs fence processing using DgnElementSetTool.  Unfortunately there is no example delivered with the SDK that illustrates fence processing using DgnElementSetTool (there are some examples that perform fence processing the MDL way).

I'm attempting to write a tool that inherits from DgnElementSetTool .  Its purpose is to add instance data from my Item Types schema to an existing element.  After adding instance data, it creates a label that shows those data...

The tool works fine when picking (i.e. user chooses a single element).  My problem lies with multiple element processing when the element source is a fence or selection set.

When a fence or select set is active, the DgnElementSetTool does its job and prompts for a data point to process the elements.  It arrives at _OnElementModify() and modifies each element (adds an Item instance and creates the label).  However, it doesn't stop!  I'm expecting _OnModifyComplete() to be called, but I don't understand the precursors to that happening.  A consequence of the interminable processing is that the tool adds dozens of labels to each element as I move the mouse, until I explicitly stop the tool with a Reset.

Specifically:

  1. What sequence of events should we expect to see when _DgnElementSetTool processes multiple elements?
  2. Under what circumstances is _OnModifyComplete() called?
  3. What is the correct way to terminate fence processing (i.e. after each member of the ElementAgenda has been processed once)?
  4. What is the correct way to restart the tool?

Parents
  • Jon,

    Maybe you should post your code? One of the main reasons DgnElementSetTool was created was to make it so that tools could be mostly agnostic about where the elements originated, be it single locate, graphic group. named group, fence, or selection set.

    It sounds like you are possibly writing to the DgnModel inside your _OnElementModify (instead of just updating the EditElementHandle)...and also have element dynamics enabled?

    To support fences, you should return USES_FENCE_Required or USES_FENCE_Check from _AllowFence. In _OnElementModify, you should just update the EditElementHandle that is passed to you. _OnElementModify is called for both dynamics and for the final accept, DgnElementSetTool will take care of displaying the modified element or updating the file for you.

    NOTE: If your tool requires dynamics and you also need to distinguish between dynamics and the final accept, you can override _OnRedrawOperation so that it doesn't just call _OnElementModify.

    /*---------------------------------------------------------------------------------**//**
    * Called during complex dynamics. Default implementation calls _OnElementModify and
    * assumes it's not valid to display the modified element using an existing cache
    * representation.
    * @param el IN Current element.
    * @param context IN context for the redraw operation.
    * @param canUseCached OUT whether cached representation is still valid for display.
    * @return SUCCESS to not skip the display of the current element.
    * @see IRedrawOperation
    * @bsimethod
    +---------------+---------------+---------------+---------------+---------------+------*/
    DGNVIEW_EXPORT virtual StatusInt _OnRedrawOperation (EditElementHandleR el, ViewContextR context, bool* canUseCached) override;

    If you don't want dynamics return false from _WantDynamics. You might also want to then override _NeedAcceptPoint if you require an explicit data button to accept the fence, since by default it's not required to accept a fence or selection set when the tool isn't starting dynamics.

    /*---------------------------------------------------------------------------------**//**
    * Called after populating tool's ElementAgenda to see if an explict data point
    * is required before accepting and calling _ProcessAgenda. By default an accept point
    * is required for tools that return true for WantDynamics, or for SOURCE_PICK when
    * the user has Auto-Locate disabled.
    * @return true to delay calling _ProcessAgenda until the next data point.
    * @bsimethod
    +---------------+---------------+---------------+---------------+---------------+------*/
    DGNVIEW_EXPORT virtual bool _NeedAcceptPoint ();

    When the tool operation is complete/accepted, then _OnModifyComplete will be called after _ProcessAgenda. Below is the documention from DgnElementSetTool.h detailing what methods are called when accepting the selected elements:

    The following sequence of member function calls will occur in the case where
    the tool requires a single datapoint to identify the target element and to accept the modification (#_NeedAcceptPoint -> false, #_WantDynamics -> false):
    
    <ul>
    <li>#_OnPostInstall</li>
        <ul>
        <li>#_SetupAndPromptForNextAction -- empty agenda</li>
        </ul>
    
    <li>#_OnDataButton</li>
        <ul>
        <li>#_LocateOneElement</li>
            <ul>
            <li>#_DoLocate</li>
            <li>#_BuildLocateAgenda</li>
            <li>#_ModifyAgendaEntries</li>
                <ul>
                <li>#_FilterAgendaEntries</li>
                <li>#_SetupAndPromptForNextAction -- non-empty agenda</li>
                <li>#_HiliteAgendaEntries</li>
                </ul>
            <li>AnchorPoint = datapoint</li>
            </ul>
        <li>#_ProcessAgenda</li>
            <ul>
            <li>#_SetupForModify -- dynamics=false</li>
            <li>#_OnElementModify</li>
            <li>element is written to cache</li>
            </ul>
        <li>#_OnModifyComplete</li>
            <ul>
            <li>#_OnReinitialize</li>
            </ul>
        </ul>
    
    </ul>

    NOTE: The correct way to restart your tool is to provide an implementation of _OnRestartTool that installs a new instance of your tool (see ExampleModifyTool.cpp)

    /*---------------------------------------------------------------------------------**//**
    * Install a new instance of the tool. Will be called in response to external events
    * such as undo or by the base class from _OnReinitialize when the tool needs to be
    * reset to it's initial state.
    *
    * @bsimethod Bentley Systems
    +---------------+---------------+---------------+---------------+---------------+------*/
    virtual void _OnRestartTool () override
    {
    InstallNewInstance (GetToolId ());
    }
    
    public:
    
    /*---------------------------------------------------------------------------------**//**
    * Method to create and install a new instance of the tool. If InstallTool returns ERROR,
    * the new tool instance will be freed/invalid. Never call delete on RefCounted classes.
    *
    * @bsimethod Bentley Systems
    +---------------+---------------+---------------+---------------+---------------+------*/
    static void InstallNewInstance (int toolId)
    {
    ExampleModifyElementTool* tool = new ExampleModifyElementTool (toolId);
    
    tool->InstallTool ();
    }
    
    /*---------------------------------------------------------------------------------**//**
    * Function that was associated with the command number for starting the tool.
    *
    * @param[in] unparsed Additional input supplied after command string.
    * @bsimethod Bentley Systems
    +---------------+---------------+---------------+---------------+---------------+------*/
    Public void startExampleModifyElementTool (WCharCP unparsed)
    {
    // NOTE: Call the method to create/install the tool, RefCounted classes don't have public constructors...
    ExampleModifyElementTool::InstallNewInstance (CMDNAME_ExampleModifyTool);
    }



    Answer Verified By: Jon Summers 

Reply
  • Jon,

    Maybe you should post your code? One of the main reasons DgnElementSetTool was created was to make it so that tools could be mostly agnostic about where the elements originated, be it single locate, graphic group. named group, fence, or selection set.

    It sounds like you are possibly writing to the DgnModel inside your _OnElementModify (instead of just updating the EditElementHandle)...and also have element dynamics enabled?

    To support fences, you should return USES_FENCE_Required or USES_FENCE_Check from _AllowFence. In _OnElementModify, you should just update the EditElementHandle that is passed to you. _OnElementModify is called for both dynamics and for the final accept, DgnElementSetTool will take care of displaying the modified element or updating the file for you.

    NOTE: If your tool requires dynamics and you also need to distinguish between dynamics and the final accept, you can override _OnRedrawOperation so that it doesn't just call _OnElementModify.

    /*---------------------------------------------------------------------------------**//**
    * Called during complex dynamics. Default implementation calls _OnElementModify and
    * assumes it's not valid to display the modified element using an existing cache
    * representation.
    * @param el IN Current element.
    * @param context IN context for the redraw operation.
    * @param canUseCached OUT whether cached representation is still valid for display.
    * @return SUCCESS to not skip the display of the current element.
    * @see IRedrawOperation
    * @bsimethod
    +---------------+---------------+---------------+---------------+---------------+------*/
    DGNVIEW_EXPORT virtual StatusInt _OnRedrawOperation (EditElementHandleR el, ViewContextR context, bool* canUseCached) override;

    If you don't want dynamics return false from _WantDynamics. You might also want to then override _NeedAcceptPoint if you require an explicit data button to accept the fence, since by default it's not required to accept a fence or selection set when the tool isn't starting dynamics.

    /*---------------------------------------------------------------------------------**//**
    * Called after populating tool's ElementAgenda to see if an explict data point
    * is required before accepting and calling _ProcessAgenda. By default an accept point
    * is required for tools that return true for WantDynamics, or for SOURCE_PICK when
    * the user has Auto-Locate disabled.
    * @return true to delay calling _ProcessAgenda until the next data point.
    * @bsimethod
    +---------------+---------------+---------------+---------------+---------------+------*/
    DGNVIEW_EXPORT virtual bool _NeedAcceptPoint ();

    When the tool operation is complete/accepted, then _OnModifyComplete will be called after _ProcessAgenda. Below is the documention from DgnElementSetTool.h detailing what methods are called when accepting the selected elements:

    The following sequence of member function calls will occur in the case where
    the tool requires a single datapoint to identify the target element and to accept the modification (#_NeedAcceptPoint -> false, #_WantDynamics -> false):
    
    <ul>
    <li>#_OnPostInstall</li>
        <ul>
        <li>#_SetupAndPromptForNextAction -- empty agenda</li>
        </ul>
    
    <li>#_OnDataButton</li>
        <ul>
        <li>#_LocateOneElement</li>
            <ul>
            <li>#_DoLocate</li>
            <li>#_BuildLocateAgenda</li>
            <li>#_ModifyAgendaEntries</li>
                <ul>
                <li>#_FilterAgendaEntries</li>
                <li>#_SetupAndPromptForNextAction -- non-empty agenda</li>
                <li>#_HiliteAgendaEntries</li>
                </ul>
            <li>AnchorPoint = datapoint</li>
            </ul>
        <li>#_ProcessAgenda</li>
            <ul>
            <li>#_SetupForModify -- dynamics=false</li>
            <li>#_OnElementModify</li>
            <li>element is written to cache</li>
            </ul>
        <li>#_OnModifyComplete</li>
            <ul>
            <li>#_OnReinitialize</li>
            </ul>
        </ul>
    
    </ul>

    NOTE: The correct way to restart your tool is to provide an implementation of _OnRestartTool that installs a new instance of your tool (see ExampleModifyTool.cpp)

    /*---------------------------------------------------------------------------------**//**
    * Install a new instance of the tool. Will be called in response to external events
    * such as undo or by the base class from _OnReinitialize when the tool needs to be
    * reset to it's initial state.
    *
    * @bsimethod Bentley Systems
    +---------------+---------------+---------------+---------------+---------------+------*/
    virtual void _OnRestartTool () override
    {
    InstallNewInstance (GetToolId ());
    }
    
    public:
    
    /*---------------------------------------------------------------------------------**//**
    * Method to create and install a new instance of the tool. If InstallTool returns ERROR,
    * the new tool instance will be freed/invalid. Never call delete on RefCounted classes.
    *
    * @bsimethod Bentley Systems
    +---------------+---------------+---------------+---------------+---------------+------*/
    static void InstallNewInstance (int toolId)
    {
    ExampleModifyElementTool* tool = new ExampleModifyElementTool (toolId);
    
    tool->InstallTool ();
    }
    
    /*---------------------------------------------------------------------------------**//**
    * Function that was associated with the command number for starting the tool.
    *
    * @param[in] unparsed Additional input supplied after command string.
    * @bsimethod Bentley Systems
    +---------------+---------------+---------------+---------------+---------------+------*/
    Public void startExampleModifyElementTool (WCharCP unparsed)
    {
    // NOTE: Call the method to create/install the tool, RefCounted classes don't have public constructors...
    ExampleModifyElementTool::InstallNewInstance (CMDNAME_ExampleModifyTool);
    }



    Answer Verified By: Jon Summers 

Children
  • Unknown said:
    If you don't want dynamics return false from _WantDynamics.

    That was the problem.  I turned off dynamics, which I had assumed to be off by default, and now it works OK.

    virtual bool _WantDynamics () override { return false; }

    Unknown said:
    You might also want to then override _NeedAcceptPoint if you require an explicit data button to accept the fence, since by default it's not required to accept a fence or selection set when the tool isn't starting dynamics.

    Good suggestion!

     
    Regards, Jon Summers
    LA Solutions