Friday, May 20, 2022

Assign a Custom Action to a Keyboard Key by Capturing Windows' Key-Press Message

This article is inspired by this question recently asked in AutoCAD .NET user discussion forum. As usual, I chose to post sample code regarding the topic here for better readability.

One of the famous posts on AutoCAD .NET from Kean Walmsley (March, 2008, 14 years ago!) talked about the topic of filtering Windows message inside AutoCAD.  Because the .NET API provides a way to look up the Windows message, so we, as .NET API programmer, can rather easily capture the Windows message (in this particular discussion, it is the WM_KEYDOWN message) and do things accordingly.

The Application class (Autodesk.AutoCAD.ApplicationServices.Core namespace) exposes PreTranslateMessage event with PreTranslateMessageEventArgs, which supplies a raw Windows meessage. 

Following code sample does what was asked in the question from the .NET forum: using "Delete" key to erase entities. 

First, DeleteKeyHandler class:

using System;
using Autodesk.AutoCAD.ApplicationServices;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
namespace EditorGetMethods
{
    public class DeleteKeyHandler
    {
        const int WM_KEYDOWN = 256;
        const int DELETE_KEY_CODE = 46;
 
        private bool _enabled = false;
        public bool Enabled => _enabled;
        public void EnableMyDelKey(bool enable)
        {
            if (enable)
            {
                CadApp.PreTranslateMessage += CadApp_PreTranslateMessage;
                _enabled = true;
            }
            else
            {
                CadApp.PreTranslateMessage -= CadApp_PreTranslateMessage;
                _enabled=false;
            }
        }
 
        private void CadApp_PreTranslateMessage(object sender, PreTranslateMessageEventArgs e)
        {
            if (e.Message.message==WM_KEYDOWN &&
                e.Message.wParam.ToInt32()==DELETE_KEY_CODE)
            {
                DeleteKeyPressed?.Invoke(this, EventArgs.Empty);
            }
        }
 
        public event EventHandler DeleteKeyPressed;
    }
}

To use it, here is the CommandClass/Method:

sing Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assembly: CommandClass(typeof(EditorGetMethods.MyCommands))]
 
namespace EditorGetMethods
{
    public class MyCommands
    {
        private static DeleteKeyHandler _myDelKey = null;
 
        [CommandMethod("MyDelKey")]
        public static void EraseEntitiesWithDelKey()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            if (_myDelKey==null)
            {
                _myDelKey = new DeleteKeyHandler();
                _myDelKey.DeleteKeyPressed += DelKeyPressed;
            }
            if (!_myDelKey.Enabled)
            {
                _myDelKey.EnableMyDelKey(true);
                ed.WriteMessage("\nDeleteKeyHandler is enabled.");
            }
            else
            {
                _myDelKey.EnableMyDelKey(false);
                ed.WriteMessage("\nDeleteKeyHandler is disabled.");
            }
        }
 
        private static void DelKeyPressed(object sender, EventArgs e)
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            if (dwg == nullreturn;
 
            var ed = dwg.Editor;
            ed.WriteMessage("\nDELETE key was pressed.");
 
            var res = ed.SelectImplied();
            if (res.Status != PromptStatus.OK) return;
            var ids = res.Value.GetObjectIds();
            using (dwg.LockDocument())
            {
                EraseSelectedEntities(ids);
            }
        }
 
        private static void EraseSelectedEntities(ObjectId[] ids)
        {
            using (var tran = ids[0].Database.TransactionManager.StartTransaction())
            {
                foreach (ObjectId id in ids)
                {
                    var ent = tran.GetObject(id, OpenMode.ForWrite);
                    ent.Erase();
                }
                tran.Commit();
            }
        }
    }
}

The code is rather simple and straightforward.

While the code shows how easy we can assign some keys from the keyboard to do particular things quite easily, I would be extremely cautious to do it. For example, in this particular case, it is obvious that erasing work triggered by pressing DELETE key would only work when there are entities in AutoCAD editor are preselected; when DELETE key is pressed, there could be another command is active, which may cause unexpected/unwanted result. Just think: what is the advantage here by pressing DELETE key over pressing "E" (the command ERASE)? None, not to mention the potential unwanted result.

Anyway, with the sample code showed here, we have seen how to capture Windows message sent to AutoCAD and to handle it accordingly for our business need.

Since the code is very simple and works as expected in my a few tests, so I omitted my usual companion video clip.


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.