Monday, September 7, 2026

Using Keyword With Editor.GetSelection()

When Editor.GetSelection() is called, AutoCAD behaves exactly as the execution of built-in command "SELECT": user can either directly click entities one at a time to get them selected; or can click anywhere in the editor and drag a selecting window, or a selecting fence...; or user can enter one of the built-in keywords, such as "All", "P", "L"...; if a non-keyword is entered, AutoCAD display an available keyword options:

These built-in keywords are used to change how the selecting behaves. 

Editor.GetSelection() in our code can be used with PromptSelectionOption class, which is not derived from PromptOption base class, as other PromptXXXXXOptions classes, has a Keywords property. We can add custom keywords (make sure to avoid built-in keywords, shown in the picture above, of course). However, there are a few things we need to be aware of:

1. The keywords are not automatically displayed at command line (or at dynamic input context menu). If we add our own custom keywords, they should be shown at command line, so that users are aware of them. So, we need to use Keywords.GetDisplayString() to append the keyword list to the adding and/or removal message of the PromptSelection object.

2. the Boolean argument of the GetDisplayString(bool showNoDefault) should be True, because, unlike in some other PropmptXXXXXOptions that have an AllowNone property, pressing Enter key during Editor.GetSelection() call will end the selecting operation, returning either PromptStatus.OK (when entities are selected), or PromptStatus.Error (no entities selected). That is, press Enter would always ends the selecting operation. Therefore, there is no point to have a default keyword.

3. When user enters a valid custom keyword, the GetSelection() continues its selecting process (i.e. waiting to make valid entity selection until Enter/Esc key is hit). Therefore, if the keyword entering is somehow not associated with how the selecting behaves, the custom keywords are useless. 

Here is the code example of showing custom keywords in the GetSelection() call:

[CommandMethod("SelWithKwd1")]
public static void GetSelectionWithKeyword1()
{
    var dwg = CadApp.DocumentManager.MdiActiveDocument;
    var ed = dwg.Editor;
 
    GetSelectionWithKeywords(ed);
    ed.PostCommandPrompt();
}
 
private static void GetSelectionWithKeywords(Editor ed)
{
    var opt = new PromptSelectionOptions();
    opt.Keywords.Add("All");
    opt.Keywords.Add("LIne");
    opt.Keywords.Add("CIrcle");
    opt.Keywords.Default = "LIne";
    opt.MessageForAdding = $"Select entities, or " + opt.Keywords.GetDisplayString(true) + ": ";
    opt.MessageForRemoval = $"Select entities, or " + opt.Keywords.GetDisplayString(true) + ": ";
 
    var res = ed.GetSelection(opt);
    if (res.Status == PromptStatus.OK)
    {
        ed.WriteMessage($"\nSelected: {res.Value.Count}");
    }
    else if (res.Status == PromptStatus.Keyword)
    {
        // This line will never be hit, because the
        // PromptSelectionResult.Status will never be PromptStatus.Keyword
        ed.WriteMessage($"\nKeywork inputted");
    }
    else
    {
        ed.WriteMessage("\nNothing selected.");
    }
}

Obviously, if adding custom keyworks does not change how Editor.GetSelection() behaves, then adding custom keyword is meaningless. Here is where the KeywordInput and UnknownInput events of the PromptSelectionOptions class come into play.

If one wants to append custom keywords to the built-in keywords, which would change the way how entities get selected. However, this behavior change could be quite difficult to implement. In reality, I have never felt the need to have other way or ways to select entities beyond the built-in approach (clicking on entities, window/fence selecting...). If one really needs to do this, I'd imagine that there is probably need to incorporate KetwordInput and/orUnknownInput events handling with Editor.SelectionAdded/Removed, or even other Editor events handling together. 

In real AutoCAD .NET programming, I often wish that when calling GetSelection(), I can use keywords in the similar way as the keywords in other PromptOption based classes that when a keyword is entered, the GetSelection() is ended in a desired matter (i.e. neither the selection is completed or aborted). For example, the GetSelection() can begin with no filter, or a certain filter; then depending on what have been selected, I may want to give user option to change/add different filter. Or, the selecting is in middle of a longer custom command execution, I'd like give an option to restart the selecting/or loop the selecting with keyword, rather than only allow users to cancel the selecting, which in turn may stop the lengthy custom execution for only starting it all over again. To achieve this goal, we can simply handle the KeyworkInput event properly and stop the selecting operation gracefully. The trick of doing it is to raise an exception in the event handler, and wrap the Editor.GetSelection() call with try...catch... to specifically catch the exception.

For example, in the middle of lengthy custom code execution, I let user to get a selection of either LINE entities, or CIRCLE entities. I could call Editor.GetKeyword() to let user to make the choice before calling Editor.GetSelection() with corresponding filter. But we can eliminate the need to use GetKeyword() entirely by using keyword in Editor.GetSelection(). In this case, user can either go ahead with default filter (say, LINE filter), or enter the keyword to change to another filter and re-run Editor.GetSelection(). Without the keyword option, if wanting to use the other filter, user can only either complete the selection (not desired, obviously), or cancel the selection (then you do not know if user really wants to cancel, or just wants different filter).

The code example showed below is for the scenario where I want user to select either LINE, or CIRCLE by using custom keyword in Editor.GetSelection() operation, so that user can freely switch the selection filter without having to either complete or cancel the selecting process (well, the GetSelection() is indeed cancelled behind the scene, but from the user's point view, the selecting process continues until either the Enter or Esc key is hit to make it complete or cancelled.

[CommandMethod("SelWithKwd2")]
public static void GetSelectionWithKeyword2()
{
    var dwg = CadApp.DocumentManager.MdiActiveDocument;
    var ed = dwg.Editor;
 
    var ids = GetSelectionWithKeywordInputHandler(ed);
    ed.WriteMessage($"\nSelected entities: {ids.Count()}");
    ed.PostCommandPrompt();
}
 
private static IEnumerable<ObjectIdGetSelectionWithKeywordInputHandler(Editor ed)
{
    var selectedIds=new List<ObjectId>();
 
    var selectLine = true;
    while (true)
    {
        SelectionFilter filter;
        if (selectLine)
        {
            filter = new SelectionFilter(new[] { new TypedValue((int)DxfCode.Start, "LINE") });
        }
        else
        {
            filter = new SelectionFilter(new[] { new TypedValue((int)DxfCode.Start, "CIRCLE") });
        }
 
        string addingMsg;
        string removalMsg;
 
        var opt = new PromptSelectionOptions();
        if (selectLine)
        {
            addingMsg = "Select LINE entities:";
            removalMsg = "Remove LINE entities:";
            opt.Keywords.Add("CIrcle");
        }
        else
        {
            addingMsg = "Select CIRCLE entities:";
            removalMsg = "Remove CIRCLE entities:";
            opt.Keywords.Add("LIne");
        }
 
        opt.MessageForAdding = $"{addingMsg}, or {opt.Keywords.GetDisplayString(true)}: ";
        opt.MessageForRemoval = $"{removalMsg}, or {opt.Keywords.GetDisplayString(true)}: ";
 
        opt.KeywordInput += (oe) =>
        {
            if (e.Input.Equals("LIne") || e.Input.Equals("CIrcle"))
            {
                throw new ApplicationException(
                    $"GetSelection() custom keyword: {e.Input}");
            }
        };
 
        try
        {
            var res = ed.GetSelection(optfilter);
            if (res.Status == PromptStatus.OK)
            {
                ed.WriteMessage($"\nSelected: {res.Value.Count}");
                selectedIds.AddRange(res.Value.GetObjectIds());
            }
            else
            {
                ed.WriteMessage("\nSelection cancelled.");
            }
            ed.Regen();
            break;
        }
        catch(ApplicationException ex)
        {
            if (ex.Message.Contains("GetSelection()"))
            {
                selectLine = ex.Message.EndsWith("LIne");
            }
        }
ed.Regen();     }

Noticed: each time when the throwing an ApplicationException disrupts the selection operation, I called Editor.Regen() in order to clear the highlight the Editor.GetSelection() applied to selected entities. Editor.Regen() also called when the selecting is completed/cancelled, which is not needed normally. But because the selecting could be disrupted by throwing exception in the KeywordInput event handler, which would lead to AutoCAD losing tracks of which entities were highlighted, thus the need of calling Editor.Regen(). It might be an issue: if the drawing's current view is very crowded, regen would take time to complete. But since we do selecting on screen with Editor.GetSelection(), it is likely we have already zoomed in enough for easy screen selection, thus hopefully regen would not be a big deal in most cases.

Here is the video clip shows the code in action:




Wednesday, July 29, 2026

A Workaround To The Drag&Drop API Operation in AutoCAD 2027/.NET 10.0

There is a recent discussion in Autodesk's AutoCAD .NET API discussion forum on the topic of broken Drag&Drop operation API in AutoCAD 2027 due to the change from .NET 8.0 to .NET 10.0. The root cause of the issue is the changed .NET support to System.Windows.Forms.IData interface in .NET 8.0 (including older .NET Core/Framework versions) and .NET 10.0. IData interface is used in the Drag$Drop operation as the data transfer "middle man" between the source (where the data is dragged) and the target (where the data is dropped). 

In a scenario like this: on a WinForm UI, user can enter radius and center point of a circle; then user can drag the data and drop onto AutoCAD's editor to have the circle drawn. As programmer, we need to derive a custom class from Autodesk.AutoCAD.Windows.DropTarget class to enable our custom data can be passed from the UI to AutoCAD when Application.DoDragDrop() method is called.

Bellow is the code of the Drag&Drop operation.

1. The WinForm UI:


2. The UI's code-behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace DragAndDrop2027
{
    public partial class DrawForm : Form
    {
        private Point3d _center = new Point3d(1000, 1000, 0);
        public DrawForm()
        {
            InitializeComponent();
            SetCenterTextBoxes();
        }
 
        private void CircleLabel_MouseMove(object senderMouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                try
                {
                    var radius = Convert.ToDouble(RadiusTextBox.Text);
                    CircleDropper.DropCircleEntity(thisradius, _center, CadHelper.CreateCircle);
                }
                catch (System.Exception ex)
                {
                    MessageBox.Show($"Error:\n{ex.Message}");
                }
            }
        }
 
        private void PickPointButton_Click(object senderEventArgs e)
        {
            try
            {
                this.Visible = false;
                if (CadHelper.TrySelectCenterPoint(out Point3d center))
                {
                    _center = centerSetCenterTextBoxes();
                    SetCenterTextBoxes();
                }
            }
            finally
            {
                this.Visible = true;
            }
        }
 
        private void SetCenterTextBoxes()
        {
            XTextBox.Text = $"{_center.X}";
            YTextBox.Text = $"{_center.Y}";
            ZTextBox.Text = $"{_center.Z}";
        }
 
        private void DrawForm_FormClosing(object senderFormClosingEventArgs e)
        {
            e.Cancel = true;
            Visible = false;
        }
    }
}
 
3. Custom class for Drag&Drop operation:
using Autodesk.AutoCAD.Windows;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace DragAndDrop2026
{
    public class CircleInfo
    {
        public double Radius { getset; }
        public Point3d Center { getset; }
    }
 
    public class CircleDropper
    {
        public static ObjectId DropCircleEntity(
            System.Windows.Forms.Form userUi,
            double radiusPoint3d center,
            Func<DocumentPoint3ddoubleObjectIdentityCreation)
        {
            var target = new CircleDropTarget(entityCreation);
 
            CircleInfo circle = new() { Radius = radius, Center = center };
            IDataObject dataObject = new DataObject();
            dataObject.SetData(typeof(CircleInfo), circle);
 
            CadApp.DoDragDrop(userUidataObjectDragDropEffects.Copy, target);
 
            return target.EntityId;
        }
    }
 
    public class CircleDropTarget : DropTarget
    {
        private readonly Func<DocumentPoint3ddoubleObjectId> _createFunction;
 
        public CircleDropTarget(Func<DocumentPoint3ddoubleObjectIdentityCreation)
        {
            _createFunction = entityCreation;
        }
 
        public ObjectId EntityId { private setget; } = ObjectId.Null;
        public override void OnDrop(DragEventArgs e)
        {
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
 
            // Get data passed from Application.DoDragDrop()
            // With .NET 10.0, this GetData(Type) method woul return null
            // if the Type argument is a object Type. This is where this
            // Drag&Drop operation fails
            var data = e.Data.GetData(typeof(CircleInfo));
 
            // The following code would do nothing because the data passed from
            // Application.DoDragDrop() is null, or raise exception if the "data" is
            // not tested for being null
            if (data != null)
            {
                var circle = (CircleInfo)data;
                //Create the entity
                EntityId = _createFunction(dwgcircle.Center, circle.Radius);
 
                CadApp.MainWindow.Focus();
                dwg.Editor.WriteMessage($"\nCircle {EntityId} has been created @{circle.Center}");
            }
        }
    }
 
    public class CadHelper
    {
        public static ObjectId CreateCircle(Document dwgPoint3d positiondouble radius)
        {
            var newId = ObjectId.Null;
            using (dwg.LockDocument())
            {
                using (var tran = dwg.TransactionManager.StartTransaction())
                {
                    var circle = new Circle();
                    circle.Center = position;
                    circle.Radius = radius;
                    circle.SetDatabaseDefaults();
 
                    var space = (BlockTableRecord)tran.GetObject(
                        dwg.Database.CurrentSpaceId, OpenMode.ForWrite);
                    newId = space.AppendEntity(circle);
                    tran.AddNewlyCreatedDBObject(circletrue);
 
                    tran.Commit();
                }
            }
 
            return newId;
        }
 
        public static bool TrySelectCenterPoint(out Point3d center)
        {
            center = Point3d.Origin;
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
            var res = dwg.Editor.GetPoint("\nSelect circle center:");
            if (res.Status == PromptStatus.OK)
            {
                center = res.Value;
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}

4. The command class to run the UI
private static DrawForm _circleForm = null;
 
[CommandMethod("CreateCircle")]
public static void RunMyCommand()
{
    var dwg = CadApp.DocumentManager.MdiActiveDocument;
    var ed = dwg.Editor;
 
    if (_circleForm == null)
    {
        _circleForm = new DrawForm();
    }
    CadApp.ShowModelessDialog(CadApp.MainWindow.Handle, _circleForm);
}

Here is how AutoCAD behaves:

1. With AutoCAD 2026 or earlier (the code is based on .NET 8.0 or .NET Framework), there is no issue. Drag&Drop works as expected;

2. With the custom code built against either .NET 8.0 or .NET 10.0, loaded into AutoCAD 2027, the Drag&Drop operation would either do nothing or cause exception when Application.DoDragDrop() is called, depending on whether proper "try...catch..." or null test is applied.

In the aforementioned forum discussion thread, @ActivistInvestor pointed out the issue was caused by Microsoft's removal of BinaryFormatter from .NET 9, which is used in the Drag&Drop operation of Windows' desktop application. The removal of BinaryFormatter is out of security concerns (see this article).

While BinaryFormatter is removed from .NET 9/10, it can be added into custom development from NuGet package as separate reference, though, thus the suggested possible solution by @ActivistInvestor. However, I tried that and did not succeed. So, the code I showed here only works with AutoCAD 2026 and older, but not AutoCAD 2027. Since Autodesk is going to make AutoCAD 2025/2026 to support .NET 10 (i.e. the underline .NET runtime for AutoCAD 2025 or newer would required .NET 10.0), the Drag&Drop code currently works in AutoCAD2025/2026 would also stop once the .NET 10 upgrade service pack applied to AutoCAD 2025/2026. I suppose.

If some offices have custom AutoCAD plugins that use AutoCAD's Drag&Drop API, this is a bad news. Autodesk's support team has already known this issue being reported. I do not know whether/when fix/change to this broken API will be available. So, I kept exploring possible solution. Guess what, it turns out a rather simple fix/workaround is there to deal with this.

As the comments in the code I showed above (the overridden method OnDrop()), in AutoCAD 2027/.NET 10, the DragDropArgs.Data.GetData(Type t) method would return null if the data passed in is a object type. In Visual Studio's debugging mode, if examining the DragDropArgs' data, one would see that the data exists as "System_Com" object and its details are not available (because of the lack of BinaryFormatter in .NET 10). 

During the debugging, a bell suddenly rang in my head: why do I not try to pass a string value as IDataObject in the Application.DoDragDrop() is called? I went ahead and tried it. Voila, now the call to DragDropArgs.Data.GetData(typeof(string)) in OnDrop() method get the correct return - string value, not null.

So, the fix to this Drag&Drop issue becomes: serializing the data of a custom object into a string value and pass it into IDataObject when calling Application.DoDragDrop() and then deserializing the string value in IDataObject of the OnDrop() method back to the custom class object.

Here is my updated code (see the red lines):

using Autodesk.AutoCAD.Windows;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace DragAndDrop2027
{
    public class CircleInfo
    {
        public double Radius { getset; }
        public Point3d Center { getset; }
 
        public override string ToString()
        {
            return $"{Radius},{Center.X},{Center.Y},{Center.Z}";
        }
    }
 
    public static class CircleInfoExtension
    {
        extension(stringcircleInfoString)
        {
            public CircleInfoToCircleInfo()
            {
                if (circleInfoString != null)
                {
                    var data = circleInfoString.Split(';');
                    if (data.Length==4)
                    {
                        if (double.TryParse(data[0], out double radius))
                        {
                            var xOk = double.TryParse(data[1], out double x);
                            var yOk = double.TryParse(data[2], out double y);
                            var zOk = double.TryParse(data[3], out double z);
 
                            if (xOk && yOk && zOk)
                            {
                                Point3d ptnew Point3d(x,y,z);
                                return new CircleInfo() { Radius = radius, Center = pt };
                            }
                        }
                    }
                }
 
                return null;
            }
        }
    }
 
    public class CircleDropper
    {
        public static ObjectId DropCircleEntity(
            System.Windows.Forms.Form userUi,
            double radiusPoint3d center,
            Func<DocumentPoint3ddoubleObjectIdentityCreation)
        {
            var target = new CircleDropTarget(entityCreation);
 
            CircleInfo circle = new() { Radius = radius, Center = center };
            IDataObject dataObject = new DataObject();
            //dataObject.SetData(typeof(CircleInfo), circle);
            dataObject.SetData($"{circle.ToString()}");
 
            CadApp.DoDragDrop(userUidataObjectDragDropEffects.Copy, target);
 
            return target.EntityId;
        }
    }
 
    public class CircleDropTarget : DropTarget
    {
        private readonly Func<DocumentPoint3ddoubleObjectId> _createFunction;
 
        public CircleDropTarget(Func<DocumentPoint3ddoubleObjectIdentityCreation)
        {
            _createFunction = entityCreation;
        }
 
        public ObjectId EntityId { private setget; } = ObjectId.Null;
        public override void OnDrop(DragEventArgs e)
        {
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
 
            // Get data passed from Application.DoDragDrop()
            // With .NET 10.0, this GetData(Type) method would return null
            // if the Type argument is a object Type. This is where this
            // Drag&Drop operation fails
 
            // Remove this line
            //var data = e.Data.GetData(typeof(CircleInfo));
 
            // Add this line
            var data = e.Data.GetData(typeof(string));
 
            // The following code would do nothing because the data passed from
            // Application.DoDragDrop() is null, or raise exception if the "data" is
            // not tested for being null
            if (data != null)
            {
                // Remove this line
                //var circle = (CircleInfo)data;
 
                // Use the extension method to convert string value t CircleInfo object
                var circle = data.ToString().ToCircleInfo();
 
                //Create the entity
                EntityId = _createFunction(dwgcircle.Center, circle.Radius);
 
                CadApp.MainWindow.Focus();
                dwg.Editor.WriteMessage($"\nCircle {EntityId} has been created @{circle.Center}");
            }
        }
    }
 
    public class CadHelper
    {
        public static ObjectId CreateCircle(Document dwgPoint3d positiondouble radius)
        {
            var newId = ObjectId.Null;
            using (dwg.LockDocument())
            {
                using (var tran = dwg.TransactionManager.StartTransaction())
                {
                    var circle = new Circle();
                    circle.Center = position;
                    circle.Radius = radius;
                    circle.SetDatabaseDefaults();
 
                    var space = (BlockTableRecord)tran.GetObject(
                        dwg.Database.CurrentSpaceId, OpenMode.ForWrite);
                    newId = space.AppendEntity(circle);
                    tran.AddNewlyCreatedDBObject(circletrue);
 
                    tran.Commit();
                }
            }
 
            return newId;
        }
 
        public static bool TrySelectCenterPoint(out Point3d center)
        {
            center = Point3d.Origin;
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
            var res = dwg.Editor.GetPoint("\nSelect circle center:");
            if (res.Status == PromptStatus.OK)
            {
                center = res.Value;
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}

In my code, for simplicity, I just added a ToString()/ToCircleInfo() method to convert the custom class object to/from string. For more complicated custom class, I might serialize the object class as json string, which is quite common code practice for most programmers these days.

Anyways, with this simple workaround, the Drag&Drop API code now works again, regardless AutoCAD versions/.NET versions.

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.