nanoCAD – Вывод команд с их описанием через NET

В том же чате по nanoCAD API возник вопрос - а как вывалить в ком.строку информацию обо всех зарегистрированных в сборке командах, да еще и с описаниями? Попробую разобраться ;)

Несколько предварительных условий.

  1. nanoCAD 23+
  2. 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-объектов уж не прописывал, это настолько элементарно...

Размещено в .NET, nanoCAD · Метки: , ,



Поделитесь своим мнением


Я не робот.