Last week we introduced the ExecuteInCommandContextAsync() method and saw it in action from a context menu click event. In today’s post we’re going to see how it can be used for a lot more: we’re going to use it to respond to external, operating system-level events (although admittedly we’re handling the event in-process to AutoCAD via .NET).
What we’re actually going to do is fire off a command inside AutoCAD – in our case we’re going to use RECTANG to create square polylines – each time we find that a file has been placed in a particular folder (in our case “c:\temp\files”, although the actually location isn’t particularly important).
Here’s what we’re aiming for – this recording shows AutoCAD on the left and an Explorer window pointed at “c:\temp\files” on the right.
Of course the specific event we’re responding to – in our case the Changed event on a FileSystemWatcher – isn’t really the point. The point is that this mechanism allows you to react to things happening outside AutoCAD, calling AutoCAD commands in reaction.
Here’s the C# code, to show how it’s working. Something to bear in mind… we’re only adding squares until there are as many as files in the tracked folder: more work would be needed to remove squares as files get deleted or moved away. But this is simply an example of hooking such an event into AutoCAD – I didn’t see much point in adding that kind of complexity to the code.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using System;
using System.IO;
namespace CommandFromAppContext
{
public class Commands
{
const int columns = 10;
const string path = "c:\\temp\\files";
private FileSystemWatcher _fsw = null;
private int _fileNum = 0;
private int _filesTotal = 0;
private bool _drawing = false;
[CommandMethod("EC")]
public void EventCommand()
{
var dm = Application.DocumentManager;
var doc = dm.MdiActiveDocument;
if (doc == null)
return;
var ed = doc.Editor;
// We'll start by creating one square for each file in the
// specified location. The nSquares() function uses some
// global state for the index and the total count
_fileNum = 0;
_filesTotal = Directory.GetFiles(path).Length;
nSquares(ed);
// Create a FileSystemWatcher for the path, looking for
// write changes and drawing more squares as needed
if (_fsw == null)
{
_fsw = new FileSystemWatcher(path, "*.*");
_fsw.Changed += (o, s) => nSquaresInContext(dm, ed, path);
_fsw.NotifyFilter = NotifyFilters.LastWrite;
_fsw.EnableRaisingEvents = true;
ed.WriteMessage("\nWatching \"{0}\" for changes.", path);
}
}
[CommandMethod("ECX")]
public void StopEventCommand()
{
var dm = Application.DocumentManager;
var doc = dm.MdiActiveDocument;
if (doc == null)
return;
var ed = doc.Editor;
if (_fsw != null)
{
_fsw.Dispose();
_fsw = null;
}
ed.WriteMessage("\nNo longer watching folder.");
}
#pragma warning disable 1998
private async void nSquaresInContext(
DocumentCollection dc, Editor ed, string path
)
{
// We'll set the total as it may well have changed (hence the
// need for global state rather than using an argument)
_filesTotal = Directory.GetFiles(path).Length;
// Protect the command-calling function with a flag to avoid
// eInvalidInput failures
if (!_drawing)
{
_drawing = true;
// Call our square creation function asynchronously
await dc.ExecuteInCommandContextAsync(
async (o) => nSquares(ed),
null
);
_drawing = false;
}
}
#pragma warning restore 1998
private void nSquares(Editor ed)
{
// Draw squares until we have enough (the total might
// change, hence the need for global state)
for (; _fileNum < _filesTotal; _fileNum++)
{
// Determine the position in our grid
int xoff = _fileNum % columns,
yoff = _fileNum / columns;
// Create our polyline via the RECTANG command
ed.Command(
"._rectang",
String.Format("{0},{1}", xoff, yoff),
String.Format("{0},{1}", xoff + 1, yoff + 1),
"_regen"
);
}
}
}
}
I think this is a really powerful mechanism. I’d be very curious to hear how people anticipate using it with AutoCAD 2016.