nanoCAD – Вывод команд с их описанием через NET
В том же чате по nanoCAD API возник вопрос - а как вывалить в ком.строку информацию обо всех зарегистрированных в сборке командах, да еще и с описаниями? Попробую разобраться
Несколько предварительных условий.
- nanoCAD 23+
- Windows 10
Создаю проект под наник - библиотека классов, NET6, ОС - Windows, все дела... Ну и заодно прописываю поддержку WindowsForms в csproj:
1 2 3 4 5 6 7 8 9 10 | <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0-windows</TargetFramework> <ImplicitUsings>disable</ImplicitUsings> <Nullable>enable</Nullable> <UseWindowsForms>true</UseWindowsForms> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> </PropertyGroup> </Project> |
Я лентяй (и горжусь этим), так что прежде чем что бы то ни было прописывать, создаю сервис сообщений:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | using HostMgd.ApplicationServices; using HostMgd.EditorInput; using System.Windows.Forms; using Application = HostMgd.ApplicationServices.Application; namespace nanoCADCommandsReflection.Infrasructure { public class MessageService { public void ErrorMessage(string message) { MessageBox.Show(message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } public void InfoMessage(string message) { MessageBox.Show(message, "Инфо", MessageBoxButtons.OK, MessageBoxIcon.Information); } public void ConsoleMessage(string message) { Document doc = Application.DocumentManager.MdiActiveDocument; if (doc == null) { InfoMessage(message); } Editor ed = doc.Editor; ed.WriteMessage("\n" + message); } } } |
Теперь уже можно и прописать парочку команд:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using System.ComponentModel; using nanoCADCommandsReflection.Infrasructure; using Teigha.Runtime; namespace nanoCADCommandsReflection.CadCommands { public static class Test1Cmd { [CommandMethod("test1")] [Description("Описание команды " + nameof(Test1Command))] public static void Test1Command() { MessageService msgService = new MessageService(); msgService.ConsoleMessage("Test1 command"); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using nanoCADCommandsReflection.Infrasructure; using System.ComponentModel; using Teigha.Runtime; namespace nanoCADCommandsReflection.CadCommands { public static class Test2Cmd { [CommandMethod("test2")] [Description("Test 2 : Описание команды " + nameof(Test2Command))] public static void Test2Command() { MessageService msgService = new MessageService(); msgService.ConsoleMessage("Test1 command"); } } } |
Теперь слегка притормозить стоит. Нужно получать информацию и об имени команды, и ее описание (которое, кстати, может и отсутствовать). Как бы прямой намек на создание отдельного класса, в котором будет нужная информация:
1 2 3 4 5 6 7 8 9 10 11 | using System.ComponentModel; using Teigha.Runtime; namespace nanoCADCommandsReflection.Infrasructure { internal class CommandInfo { public CommandMethodAttribute MethodAttr { get; set; } public DescriptionAttribute DescriptionAttr { get; set; } } } |
Так, вроде бы подготовительные действия все выполнены, можно приступать к прописыванию (пока что) отдельной команды по получению всей возможной информации о зарегистрированных командах. За основу беру статью, которую подсказал Андрей Разыграев: Программное определение дублированных имен .NET команд
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | using System; using System.ComponentModel; using System.Reflection; using nanoCADCommandsReflection.Infrasructure; using Teigha.Runtime; namespace nanoCADCommandsReflection.CadCommands { public static class GetAllCmdsCmd { [CommandMethod("get-all-commands")] public static void GetAllCommandsCommand() { Assembly asm = Assembly.GetExecutingAssembly(); Type[] expTyped = asm.GetTypes(); foreach (Type t in expTyped) { MethodInfo[] methods = t.GetMethods(); foreach (MethodInfo method in methods) { CommandInfo temp = GetCommandInfo(method); } } } private static CommandInfo GetCommandInfo(MethodInfo method) { return null; } } } |
Пока заглушка, хрен с ней. Пропишу-ка пока вывод информации в ком.строку:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | [CommandMethod("get-all-commands")] public static void GetAllCommandsCommand() { MessageService msgService = new MessageService(); Assembly asm = Assembly.GetExecutingAssembly(); Type[] expTyped = asm.GetTypes(); foreach (Type t in expTyped) { MethodInfo[] methods = t.GetMethods(); foreach (MethodInfo method in methods) { CommandInfo temp = GetCommandInfo(method); if (temp != null) { if (temp.descriptionAttr !=null) { msgService.ConsoleMessage(temp.MethodAttr.GlobalName + " >> " + temp.descriptionAttr.Description ?? ""); } else { msgService.ConsoleMessage(temp.MethodAttr.GlobalName); } } } } } |
Ну и теперь можно уже и получать всю информацию о командах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | private static CommandInfo GetCommandInfo(MethodInfo method) { object[] attrs = method.GetCustomAttributes(true); CommandInfo res = new CommandInfo(); foreach (object attr in attrs) { if (attr is CommandMethodAttribute cmdAttr) { res.MethodAttr = cmdAttr; } else if (attr is DescriptionAttribute descrAttr) { res.descriptionAttr = descrAttr; } } return res.MethodAttr == null ? null : res; } |
Суть прикола проста и незатейлива: получаем какой-то метод, и последовательно проходим по его атрибутам. Командный? Ок, засовываем в соответствующее поле создаваемого экземпляра класса. Описание? В поле!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | [CommandMethod("get-all-commands")] public static void GetAllCommandsCommand() { MessageService msgService = new MessageService(); Assembly asm = Assembly.GetExecutingAssembly(); Type[] expTyped = asm.GetTypes(); foreach (Type t in expTyped) { MethodInfo[] methods = t.GetMethods(); foreach (MethodInfo method in methods) { CommandInfo temp = GetCommandInfo(method); if (temp != null) { if (temp.descriptionAttr !=null) { msgService.ConsoleMessage(temp.MethodAttr.GlobalName + " >> " + temp.descriptionAttr.Description ?? ""); } else { msgService.ConsoleMessage(temp.MethodAttr.GlobalName); } } } } } |
Не, ну команда, конечно, здорово - но при старте вываливать подобную информацию, наверное, было бы более интересно. Так что прописываю инициализацию аддона, а команду сношу, ибо нефих:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | public class ExtensionInitalization :IExtensionApplication { public void Initialize() { MessageService msgService = new MessageService(); Assembly asm = Assembly.GetExecutingAssembly(); Type[] expTyped = asm.GetTypes(); foreach (Type t in expTyped) { MethodInfo[] methods = t.GetMethods(); foreach (MethodInfo method in methods) { CommandInfo temp = GetCommandInfo(method); if (temp != null) { if (temp.descriptionAttr != null) { msgService.ConsoleMessage(temp.MethodAttr.GlobalName + " >> " + temp.descriptionAttr.Description ?? ""); } else { msgService.ConsoleMessage(temp.MethodAttr.GlobalName); } } } } } public void Terminate() { } private CommandInfo GetCommandInfo(MethodInfo method) { object[] attrs = method.GetCustomAttributes(true); CommandInfo res = new CommandInfo(); foreach (object attr in attrs) { if (attr is CommandMethodAttribute cmdAttr) { res.MethodAttr = cmdAttr; } else if (attr is DescriptionAttribute descrAttr) { res.descriptionAttr = descrAttr; } } return res.MethodAttr == null ? null : res; } } |
Ну и для проверки добавлю команду без описания:
1 2 3 4 5 6 7 8 9 | public static class Test3Cmd { [CommandMethod("Test3")] public static void Test3Command() { MessageService msgService = new MessageService(); msgService.InfoMessage(nameof(Test3Command)); } } |
И при старте NC получаю:
1 2 3 | test1 >> Описание команды Test1Command test2 >> Test 2 : Описание команды Test2Command Test3 |
Нуачо, прикольно получилось ))
Линк на гитхаб: https://github.com/kpblc2000/nanoCADCommandsReflection
P.S. Исключение null-объектов уж не прописывал, это настолько элементарно...