AutoCAD 2016: Calling commands from AutoCAD events using .NET

It's time to start looking in more detail at some of the new API capabilities in AutoCAD 2016. To give you a sense of what to expect in terms of a timeline, this week we'll look at a couple of uses for DocumentCollection.ExecuteInCommandContextAsync() and next week we'll look at point cloud floorplan extraction and (hopefully) security and signing.

The first use of ExecuteInCommandContextAsync() I wanted to highlight was one raised in a blog comment a couple of months ago. The idea is simple enough: we want to be able to launch a command reliably from an event handler, in our case the Click event of a ContextMenuExtension's MenuItem. Before now you would have to use Document.SendStringToExecute(), as we saw in this previous post – calling a command in another way would typically lead to an eInvalidInput exception.

There are certainly advantages to avoiding SendStringToExecute() in this scenario: while commands that use the pickfirst selection set are OK – including ones you implement yourself – using Command() or CommandAsync() gives you greater control over which entities you choose to pass entities to the command being called (whether it accepts pickfirst selection or not).

By the way, as mentioned briefly in a comment on the last post, in AutoCAD 2015 you will find the DocumentCollection.BeginExecuteInCommandContext() method, which was the former name for ExecuteInCommandContextAsync() (it's taken from the ObjectARX method it calls through to). If you try to make the below code work in AutoCAD 2015 with the previous method name, you're probably going to hit this error: 'Unknown command: "EXECUTEFUNCTION"'.

Before we look at the code, here's a recording of what we want it to do:

Context menu to scale by 5

It's pretty simple in concept, at least. Here's the C# code that makes it work:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Windows;

using System;

 

namespace ContextMenuApplication

{

  public class Commands : IExtensionApplication

  {

    public void Initialize()

    {

      ScaleMenu.Attach();

    }

    public void Terminate()

    {

      ScaleMenu.Detach();

    }

  }

 

  public class ScaleMenu

  {

    private static ContextMenuExtension cme;

 

    public static void Attach()

    {

      if (cme == null)

      {

        cme = new ContextMenuExtension();

        MenuItem mi = new MenuItem("Scale by 5");

        mi.Click += new EventHandler(OnScale);

        cme.MenuItems.Add(mi);

      }

      RXClass rxc = Entity.GetClass(typeof(Entity));

      Application.AddObjectContextMenuExtension(rxc, cme);

    }

 

    public static void Detach()

    {

      RXClass rxc = Entity.GetClass(typeof(Entity));

      Application.RemoveObjectContextMenuExtension(rxc, cme);

    }

 

    private static async void OnScale(Object o, EventArgs e)

    {

      var dm = Application.DocumentManager;

      var doc = dm.MdiActiveDocument;

      var ed = doc.Editor;

 

      // Get the selected ob
jects

 

      var psr = ed.GetSelection();

      if (psr.Status != PromptStatus.OK)

        return;

 

      try

      {

        // Ask AutoCAD to execute our command in the right context

 

        await dm.ExecuteInCommandContextAsync(

          async (obj) =>

          {

            // Scale the selected objects by 5 relative to 0,0,0

 

            await ed.CommandAsync(

              "._scale", psr.Value, "", Point3d.Origin, 5

            );

          },

          null

        );

      }

      catch (System.Exception ex)

      {

        ed.WriteMessage("\nException: {0}\n", ex.Message);

      }

    }

  }

}

We might have used Editor.Command() rather than Editor.CommandAsync(), but ExecuteInCommandContextAsync() is expecting an asynchronous task to be passed in, so doing so would lead to a warning about the async lambda running synchronously. Ultimately it works comparably, but the above code makes the C# compiler happier, so I've left it that way. I've also chosen to await the call to ExecuteInCommandContextAsync(), although for this type of operation it's probably not strictly needed.

In the next post we're going to take a look at calling AutoCAD commands based on external events: we're going to hook up a FileSystemWatcher to check for changes to a folder and call a command inside AutoCAD each time a file gets created there.

7 responses to “AutoCAD 2016: Calling commands from AutoCAD events using .NET”

  1. great sample code Kean. I had not paid much attention to customizing right click menus before so this was very helpful.

  2. Kean - I am wondering why this has to be done with coding? I was trying to make a simple macro and place it in the right click menu for the join command without success. The Only thing that I could think of is making sure that pickfirst is set and then feeding the selection set to the JOIN command and it should work. Is there an easy (non programming) method to do this? Possible a single line of LISP within the macro...(even though that is programming - i know)
    Thanks
    ~Greg

  3. Kean Walmsley Avatar

    Greg,

    You don't have to use coding to solve this kind of problem, necessarily (it's actually better to use CUI when you can).

    See if these posts help you:

    keanw.com/2014/02/adding-a-context-menu-item-with-an-icon-in-autocad-using-net.html

    keanw.com/2014/02/adding-a-context-menu-item-with-an-icon-for-a-specific-autocad-object-type-using-net.html

    Regards,

    Kean

  4. Pankaj Potdar Avatar

    Hello Kean,
    I dont know if it is off the topic but what is the way to remove event handler?
    I am using below event handler
    AddHandler ed.SelectionAdded, AddressOf testit

    and tried to use
    RemoveHandler ed.SelectionAdded, AddressOf testit

    to remove it. but it does not get removed.
    Is there any other way to do it?

    Thanks,
    Pankaj

    1. Kean Walmsley Avatar

      Hi Pankaj,

      It should work. But please do post the question to an online forum... this isn't the right place.

      Regards,

      Kean

      1. Pankaj Potdar Avatar

        Thanks Kean, I was just wondering if there any other method to do it, anyways I will post it on Online forum.

        Thanks,
        Pankaj

  5. Mr Kean, Thanks for this code. I don't really understand what DocumentCollection.ExecuteInCommandContextAsync() does over and above the ed.CommandAsync() and ed.Command() methods. In fact the ed.CommandAsync() is called right after the ExecuteInCommandContextAsync in the above code. Clarification on this point would be much appreciated. rgds, Ben

Leave a Reply

Your email address will not be published. Required fields are marked *