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:
It’s pretty simple in concept, at least. Here’s the C# code that makes it work:
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.
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:
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 objects
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);
}
}
}
}
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.
No comments:
Post a Comment