Friday, October 16, 2015

Be Careful When Handling Document Events

In one of my resent CAD application development project, I needed to handle Document's event, namely, Document.CommandWillStart/CommandEnded/CommandCancelled events, in order to identify entities being changed by particular commands.

I have done this type of coding quite a few times before, so I did not expect this would cause any issue, although the code in the CommandWillStart/CommandEnded event handlers is rather complicated because of the business requirement.

During the application testing, as it turned out, the event handlers seemed getting lost from time to time, as if no event handler is attached to Document object, even the debug-stepping through clearly shows that Document.CommandWillStart/CommandEnded += [Event Handler] was executed correctly.

Also, when running the code, when the application changes from the state of command event being handled to the state of command event handler being lost, there is no exception being raised so that the application crashes.

I could only guess that this might have something to do with my complicated code inside the event handlers, but could not figure it out what it is and why the attached event handler could be lost, because there is no exception that stops the code execution.

After countless times of stepping through the code, I finally identify a line of code that was supposed to raise an exception in certain conditions. Instead of the code execution being break and an exception being raised, to my surprise, the code execution simply jumped to the end of the command event handler. If the event handler is CommandWillStart, the command simply goes ahead. However, all the code in the event handler would not run any more, as if the event handler was not attached to the Document.

To demonstrate this behaviour, see following code:

public class Commands
{
    private static bool _handlerAdded = false;
    private static int _count = 0;
 
    [CommandMethod("HandleCmd")]
    public static void HandleCommandEvents()
    {
        Document dwg = CadApp.DocumentManager.MdiActiveDocument;
 
        if (!_handlerAdded)
        {
            SetCommandEventHandler(true);
            dwg.Editor.WriteMessage(
                "\nCommandWillStart event handler attached");
            _count = 0;
        }
        else
        {
            SetCommandEventHandler(false);
            dwg.Editor.WriteMessage(
                "\nCommandWillStart event handler detached");
        }
    }
 
    private static void SetCommandEventHandler(bool addHandler)
    {
        Document dwg = CadApp.DocumentManager.MdiActiveDocument;
 
        if (addHandler)
        {
            dwg.CommandWillStart += Dwg_CommandWillStart;
            _handlerAdded = true;
        }
        else
        {
            if (_handlerAdded)
            {
                dwg.CommandWillStart -= Dwg_CommandWillStart;
            }
            _handlerAdded = false;
        }
    }
 
    private static void Dwg_CommandWillStart(object sender, CommandEventArgs e)
    {
        _count++;
        Document dwg = CadApp.DocumentManager.MdiActiveDocument;
        dwg.Editor.WriteMessage(
            "\n====Command {0} will start. Count {1}====", 
            e.GlobalCommandName, _count);
 
        if (_count==3)
        {
            throw new ApplicationException("Error");
        }
    }
}

With above shown code, as we can see, after executing command "HandleCmd" to enable the command event handling, whenever an AutoCAD command starts, the code in the Dwg_CommandWillStart event handler will be called. However, when the third command starts, an exception is thrown, which does not break the code execution (that is, to user, it seems nothing happens). Starting any command thereafter, the command would execute as expected, by the code in Dwg_CommandWillStart event handler is no longer called, as if the event handler is not attached to the document.

After the event handler being lost, event I run "HandleCmd" again, trying to remove and re-attach the event handler, it seemed that the event handler could not be attached to the document any more. The only way to get the event handler hooked up is the close and reopen the drawing.

If I catch the possible exception inside the event handler, like this:

private static void Dwg_CommandWillStart(object sender, CommandEventArgs e)
{
    _count++;
    Document dwg = CadApp.DocumentManager.MdiActiveDocument;
    dwg.Editor.WriteMessage(
        "\n====Command {0} will start. Count {1}====", 
        e.GlobalCommandName, _count);
 
    try
    {
        if (_count == 3)
        {
            throw new ApplicationException("Error");
        }
    }
    catch { }
}

then the event handler will not be lost.

Here what I observed is quite similar to the well-known case of IExtensionApplication.Initialize(): if an exception occurs in the event handler, AutoCAD simply swallows it silently and jump out the event handler; AutoCAD continues functioning, but the event handler attached to the document will be lost and cannot be re-attached. The only way to get the event handler back is to close the document and re-open it.

Therefore, we need to be very careful when writing code in the document command event handler to make sure all possible exception can be caught, just like we do in IExtensionApplication.Initialize().

To be clear: I did not try this with all Document events but 2 (Document.CommandWilLStart/CommandEnded), thus do not know if the handlers to other Document events behave the same way or not. Nor did I tried with Database events.



Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.