000

Index Labels

Handling Document.UnknownCommand Event, 1 of 2

.
In one of my recent article (Custom On Demand Loading), I mentioned of handling Document.UnknownCommand could be used for on demand loading (last paragraph of that article). In my further dealing with UnknownCommand event, I also ran into an interesting issue. So, I decided to write down my experience on dealing with UnknownCommand to share with the community. I split the writing into 2 parts. This is the part 1: yet another on demand loading approach.

When thinking of loading application (DLLs, in the case of AutoCAD .NET add-in), the key is to find a suitable trigger moment when AutoCAD runs.When this trigger is pull off, a set of conditions/configurations should be examined to determine if particular applications/DLLs are already loaded in the AutoCAD process, and loaded them if necessary.

In my previous article, I wanted particular applications being loaded when the currently activated drawing is from specific project (project type, location, client... could be used as the criteria), then the trigger point is DocumentCollection.DocumentBecameCurrent event: the code would examine the drawing's project information to decide what applications should be available. Then if the applications have not been loaded, load them.

Now with handling Document.UnknownCommand we can have more generic on demand loading approach: whenever user enters a command from an application which has not been loaded, AutoCAD fires the UnknownCommand event. This allows our code in the event handler to use the unknown command name against a set of on demand loading configuration data to determine which applications are needed to be loaded. Here, we do not need to check if the application has already been loaded (otherwise, the UnknownCommand would not fire).

Here is a DLL project that is compiled as MyToolA.dll, in which 2 commands are defined:
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;

[assemblyCommandClass(typeof(MyToolA.Commands))]
[assemblyExtensionApplication(typeof(MyToolA.Commands))]

namespace MyToolA
{
    public class Commands : IExtensionApplication
    {
        public void Initialize()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
            ed.WriteMessage(
                "\nLoading MyToolA.dll...done.\n");
        }

        public void Terminate()
        {
            
        }

        [CommandMethod("Command_A1")]
        public static void RunCommandA1()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;

            ed.GetString(
                "\nThis \"Command_A1\" in DLL \"MyToolA.dll\"......" +
                "Press anykey to continue...");
            ed.WriteMessage("\n");
        }

        [CommandMethod("Command_A2")]
        public static void RunCommandA2()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;

            ed.GetString(
                "\nThis \"Command_A2\" in DLL \"MyToolA.dll\"......" +
                "Press anykey to continue...");
            ed.WriteMessage("\n");
        }
    }
}

Then here is another DLL project. compiled as MyToolB.ll, in which another 2 commands are defined:
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;

[assemblyCommandClass(typeof(MyToolB.Commands))]
[assemblyExtensionApplication(typeof(MyToolB.Commands))]

namespace MyToolB
{
    public class Commands : IExtensionApplication
    {
        public void Initialize()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
            ed.WriteMessage(
                "\nLoading MyToolB.dll is...done.\n");
        }

        public void Terminate()
        {

        }

        [CommandMethod("Command_B1")]
        public static void RunCommandA1()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;

            ed.GetString(
                "\nThis \"Command_B1\" in DLL \"MyToolB.dll\"......" +
                "Press anykey to continue...");
            ed.WriteMessage("\n");
        }

        [CommandMethod("Command_B2")]
        public static void RunCommandA2()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;

            ed.GetString(
                "\nThis \"Command_B2\" in DLL \"MyToolB.dll\"......" +
                "Press anykey to continue...");
            ed.WriteMessage("\n");
        }
    }
}

Now, I want these 2 DLLs to be loaded on demand, that is, when one of the commands defined in these 2 DLLs is called in AutoCAD, AutoCAD will load the DLL. So, I first came up with a simple set of auto-loading configuration, which is a Dictionary. That is, each Dictionary entry represents a DLL file name (entry's key) and an array of command names defined in the DLL (entry's value):
using System.Collections.Generic;

namespace HandlingUnknownCommand
{
    public class AutoLoaderSettings : Dictionary<stringstring[]>
    {
        public string GetDllByCommandName(string cmdName)
        {
            foreach (var item in this)
            {
                var cmds = item.Value;
                foreach(var cmd in cmds)
                {
                    if (cmd.ToUpper() == cmdName.ToUpper()) return item.Key;
                }
            }

            return null;
        }

        // A static factory method to generate a set of test AutoLoaderSetting
        public static AutoLoaderSettings CreateTestSettings()
        {
            var settings = new AutoLoaderSettings();

            settings.Add(
                "MyToolA.dll",
                new string[] { "Command_A1""Command_A2" });

            settings.Add(
                "MyToolB.dll",
                new string[] { "Command_B1""Command_B2" });

            return settings;
        }
    }
}

Here comes the auto-loader:
using System.Reflection;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;

[assemblyCommandClass(typeof(HandlingUnknownCommand.MyAutoLoader))]
[assemblyExtensionApplication(typeof(HandlingUnknownCommand.MyAutoLoader))]

namespace HandlingUnknownCommand
{
    public class MyAutoLoader : IExtensionApplication 
    {
        private static bool _autoLoad = false;
        private static AutoLoaderSettings _settings = 
            AutoLoaderSettings.CreateTestSettings();

        public void Initialize()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;

            try
            {
                ed.WriteMessage("\nInitializing MyAutoLoader...");
                CadApp.DocumentManager.DocumentCreated += 
                    DocumentManager_DocumentCreated;
                foreach (Document doc in CadApp.DocumentManager)
                {
                    doc.UnknownCommand += Document_UnknownCommand;
                }
                _autoLoad = true;
                ed.WriteMessage("\nMyAutoLoader initialized successfully.");
            }
            catch(System.Exception ex)
            {
                _autoLoad = false;
                ed.WriteMessage(
                    "\nInitialzing MyAutoLoader failed:\n{0}\n", ex.Message);
            }
        }

        public void Terminate()
        {

        }

        private static void DocumentManager_DocumentCreated(
            object sender, DocumentCollectionEventArgs e)
        {
            e.Document.UnknownCommand += Document_UnknownCommand;
        }

        private static void Document_UnknownCommand(
            object sender, UnknownCommandEventArgs e)
        {
            if (!_autoLoad) return;

            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;

            var cmdName = e.GlobalCommandName;
            var dllFileName = _settings.GetDllByCommandName(cmdName);
            if (!string.IsNullOrEmpty(dllFileName))
            {
                try
                {
                    LoadAssemblyDll(dllFileName);
                    ed.WriteMessage(
                        "\nDll {0} loaded, command {1} resuming...",
                        dllFileName.ToUpper(), cmdName.ToUpper());
                }
                catch (System.Exception ex)
                {
                    ed.WriteMessage(
                        "\nAutoloading DLL failed: {0}\n", ex.Message);
                }    
            }
        } 
        
        private static void LoadAssemblyDll(string dllFileName)
        {
            string path = System.IO.Path.GetDirectoryName(
                Assembly.GetExecutingAssembly().Location);
            string dll = path + "\\" + dllFileName;

            if (!ExtensionLoader.IsLoaded(dll))
            {
                if (System.IO.File.Exists(dll))
                {
                    ExtensionLoader.Load(dll);
                }
                else
                {
                    throw new System.IO.FileNotFoundException(
                        "Cannot find DLL file: " + dllFileName + "!");
                }
            }
        } 
    }
}


Now, see this video clip to see how the code runs.

As we can see, the 2 Dlls that actually define the commands are not preloaded, yet we can enter the commands and let the custom AutoLoader load the Dlls. Another interesting thing is that once the custom AutoLoader loads the Dlls according to configuration, AutoCAD automatically resumes the command (previously unknown whent eh command is issued, but becoming known after the Dll is loaded) execution. An extr message at command line is generated by AutoCAD. See the highlight in this picture:



AutoCAD's this behaviour indicates that if there is any UnknowCommand event handler other than .NET API's built-in one, AutoCAD will try the command again after the custom event handler execution. This allows us to make a command purely be loaded on demand and provide a good user experience: a not loaded command is loaded and executed without interruption, except for the extra line of message at command line. Well, there is a little catch: AutoCAD's auto-command-completion might be a bit annoying, if you have a custom command that similar to an existing AutoCAD command and you have to enter the custom command at command line.

Anyway. simply handling UnknownCommand event would allow us to build a custom Dll/command auto-loading process quite easily.

I'll be talking another interesting issue on handling UnknownCommand event in next post.

Blog Archive

Labels

3D Modeling 3D Sketch Inventor AI Design AI in Manufacturing AI Tools Architecture Artificial Intelligence AutoCAD AutoCAD advice AutoCAD Basics AutoCAD Beginners AutoCAD Civil3D AutoCAD commands AutoCAD efficiency AutoCAD features AutoCAD File Management AutoCAD Layer AutoCAD learning AutoCAD print settings AutoCAD productivity AutoCAD Teaching AutoCAD Techniques AutoCAD tips AutoCAD training. AutoCAD tricks AutoCAD Tutorial AutoCAD workflow AutoCAD Xref Autodesk Autodesk 2025 Autodesk AI Tools Autodesk AutoCAD Autodesk Fusion 360 Autodesk Inventor Autodesk Inventor Frame Generator Autodesk Inventor iLogic Autodesk Recap Autodesk Revit Autodesk Software Autodesk Video Automation Automation Tutorial Basic Commands Basics Beginner Beginner Tips BIM BIM Implementation Block Editor ByLayer CAD comparison CAD Design CAD File Size Reduction CAD line thickness CAD Optimization CAD Productivity CAD software clean CAD file cleaning command Cloud Collaboration command abbreviations Construction Technology Contraints Create resizable blocks CTB STB Data Reference Data Shortcut design software Design Workflow Digital Design Digital Twin Drafting Standards Drawing Automation Dref Dynamic Block Dynamic Block AutoCAD Dynamic Blocks Dynamic doors Dynamic windows eco design editing commands energy efficiency Engineering Engineering Design Engineering Innovation Engineering Technology engineering tools Excel Express Tools External Reference Fast Structural Design Fusion 360 Generative Design green building Grips heavy CAD file Heavy CAD Files iLogic Industry 4.0 Insight Inventor API Inventor Drawing Template Inventor Frame Generator Inventor Graphics Issues Inventor IDW Inventor Tips Keyboard Shortcuts Learn AutoCAD Machine Learning in CAD maintenance command Management Manufacturing Innovation Metal Structure ObjectARX .NET API Organization OVERKILL OVERKILL AutoCAD Palette PDF Plot Style AutoCAD Practice Drawing Printing Quality professional printing Professional Tips PTC Creo PURGE PURGE AutoCAD ReCap reduce CAD file size Resizable Block Revit Revit Best Practices Revit Workflow Ribbon screen shortcut keys Shortcuts Siemens NX Sketch Small Firms Smart Block Smart Factory SolidWorks Steel Structure Design sustainability Sustainable Manufacturing toolbar Tutorial User Interface (UI) Workbook Workspace XLS Xref