Меню для nanoCAD
В чате по nanoCAD API 24 ноября была опубликована ссылка на статью по обработке / созданию / внутренней структуре (короче, информации море!) меню внутри nanoCAD. Я параноик тот еще, и потерять подобную информацию не хочу от слова совсем.
Откровенно говоря, хотел все дополнительно проверить - но за две недели так и не сподобился. Но текст дублирую. Я невероятно благодарен автору за собранный и написанный текст, но - уж простите - некоторую часть я снес. Сохраню интригу, кому интересно - добро пожаловать в исходник
Разработчики программных решений для nanoCAD, имея потребность создать для своего плагина меню (здесь и далее под словом «меню» будем понимать вкладки и кнопки в ленточном и/или классическом интерфейсе программы), сталкиваются со следующими проблемами:
- Не понятно, какие файлы создавать и что в них прописывать. Существующие примеры .cfg/.cuix файлов (которые можно найти в папках nanoCAD или попробовать сделать вручную через создание «кнопок» в настройках nanoCAD) содержат в себе очень много не очень понятного текста. Консолидированной информации по этому поводу ни для nanoCAD, ни для других CAD-платформ нами найдено не было.
- Для создания каждой отдельной «кнопки» в меню необходимо прописать достаточно большое количество текста. Хочется иметь какое-то средство автоматизированной генерации обозначенных файлов.
Актуальность этих проблем поднялась в нашем Telegram-чате nanoCAD API.
Обе проблемы постараемся решить в этой статье.
Важно отметить, что в статье будет рассмотрен минимальный рабочий вариант разработки меню с нашей точки зрения. Представленное решение не позиционируется, как идеальное, и мы готовы выслушать комментарии с дополнением. Также обратим внимание, что описываемый подход используется во всех плагинах для nanoCAD, разрабатываемых командой TBS. В качестве «боевого» примера будет использоваться плагин TBS Plus, функционал которого на момент написания статьи содержит больше 70 команд (и соответствующее количество «кнопок» в меню).
В первой части статьи рассмотрим, какие файлы отвечают за формирования меню надстройки, а также принципиальное содержание этих файлов. Во второй части мы расскажем о решении по автоматизации сборки файлов меню, которое применяется в нашей команде, а также поделимся ссылкой на репозиторий с этим решением.
Часть 1. Ручное формирование файлов меню надстройки для nanoCAD
Итак, первое, с чего мы должны начать – создание конфиг файла: создаём обычный текстовый файл с любым именем и расширением “.cfg”. У нас это будет “Test.cfg”.
ВАЖНО – кодировка файла – «UTF-8 with BOM». Замечено, что «UTF-8» в данном случае не подходит.
В этом файле мы пропишем все команды нашего плагина, создадим меню и панель инструментов (toolbar) для классического интерфейса.
Для того, чтобы отслеживать каждый шаг, сразу «подключим» наш файл к программе nanoCAD. Для этого открываем файл «%AppData%\Nanosoft\nanoCAD x64 23.0\Config\nanoCAD.cfg» (с поправкой на версию nanoCAD) и в самый конец добавляем строку, содержащую путь к нашему .cfg файлу, в нашем случай:
1 | #include "C:\Test\Test.cfg" |
ВАЖНО – созданный нами .cfg файл должен «лежать» на диске C. Иначе есть шанс, что он не загрузится в программу.
Далее сохраняем файл «nanoCAD.cfg» без изменения кодировки и закрываем его, он нам больше не понадобится.
Открываем созданный ранее файл («Test.cfg») с помощью любого текстового редактора.
Для начала нам нужно «зарегистрировать» все нужные нам команды. Для этого пишем следующее (переносы строк добавлены исключительно для удобства восприятия):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | [\configman] [\configman\commands] [\configman\commands\TBSPlus_TextCalculator] weight=i10 cmdtype=i1 intername=sTBSPlus_TextCalculator DispName=sКалькулятор текста StatusText=sПалитра, содержащая инструменты для арифметических операций с числами в текстовых объектах BitmapDll=sicons\TBSPlus_TextCalculator.ico [\configman\commands\TBSPlus_LevelingInstrument] weight=i10 cmdtype=i1 intername=sTBSPlus_LevelingInstrument DispName=sНивелир StatusText=sПалитра для измерения превышения и проложения между точками BitmapDll=sicons\TBSPlus_LevelingInstrument.ico |
На скрине даны пояснения за что отвечает каждая строка, более подробно о каждом из параметров можно изучить в настройках nanoCAD на примере любой (в том числе базовой) команды:
Отметим лишь несколько моментов.
- Перед значением имен, описаний, путей и т.д. ставится префикс “s”
- Внутреннее имя – то имя, которое объявлено в нашем приложении (в случае .NET это значение атрибута «CommandMethod») и используется при вызове команды с командной строки. Это же имя будем использовать в нашем файле в дальнейшем, для создания «кнопки» для нашей команды.
- В более сложных случаях для команды можно добавить ключевое слово:
1Keyword=s^C^C(setq a 1)(load"arh_atr")(arh_atr)
- Это значение будет «отправлено на консоль» в момент обращения к команде.
- Путь к иконке. Здесь задан относительный путь (помним про указанный префикс). В папку icons, «лежащую» рядом с нашим «Test.cfg» файлом, сохраним две иконки с соответствующими именами. Естественно, имя иконки может отличаться от внутреннего имени команды, оно должно лишь совпадать с именем, указанным в «Test.cfg» файле. Из нашего опыта разрешение 32х32 хорошо подходит, как для больших, так и для маленьких кнопок. Используемые иконки расположены в архиве, приложенном к статье.
Сохраняем файл (всегда помня о кодировке) и открываем nanoCAD (необязательный шаг). Здесь и далее важно понимать, что наш файл «считывается» программой во время загрузки, поэтому после внесения любого изменения в .cfg файл для того, чтобы увидеть эти изменения в nanoCAD, мы должны перезагрузить программу.
На текущем этапе наши команды были зарегистрированы (можно увидеть в настройках nanoCAD, скрин выше). Теперь при консольном обращении в списке команд мы видим наши команды, во-первых, с иконками, а во-вторых, с полным (в нашем случае - русскоязычным) названием.
Теперь, добавим вкладку с нашими кнопками в классическое меню. Для этого в конец нашего «Test.cfg» добавляем следующее:
1 2 3 4 5 6 7 8 9 10 11 | [\menu] [\menu\Test_Menu] Name=sTBS Plus [\menu\Test_Menu\Инструменты] name=sИнструменты [\menu\Test_Menu\Инструменты\sTBSPlus_TextCalculator] name=sКалькулятор текста Intername=sTBSPlus_TextCalculator [\menu\Test_Menu\Инструменты\sTBSPlus_LevelingInstrument] name=sНивелир Intername=sTBSPlus_LevelingInstrument |
Таким образом, мы создали вкладку “Тест”, в нее добавили подпункт “Инструменты”, а в него уже “вложили” две “кнопки”. Вложенность может быть произвольной и многоуровневой. Важно отметить, что внутреннее имя команды должно совпадать с внутренним именем одной из зарегистрированных в программе команд. Очевидно, команда с таким внутренним именем будет выполнена при нажатии на данную “кнопку”.
Сохраняем файл и открываем nanoCAD (для самопроверки, шаг опять же необязательный).
Как видим, в классическом меню появилась наша вкладка с вложенным подпунктом, в который в свою очередь вложены две “кнопки”.
Последнее, что мы сделаем в нашем «Test.cfg» - создадим панель инструментов (toolbar) с нашими кнопками.
В конец файла «Test.cfg» пишем:
1 2 3 4 5 6 7 8 | [\toolbars] [\toolbars\Test_Инструменты] name=sTest_Инструменты Intername=sTest_Инструменты [\toolbars\Test_Инструменты\TBSPlus_TextCalculator] Intername=sTBSPlus_TextCalculator [\toolbars\Test_Инструменты\TBSPlus_LevelingInstrument] Intername=sTBSPlus_LevelingInstrument |
Мы создали панель инструментов Test_Инструменты и добавили на нее две кнопки.
Сохраняем файл, открываем nanoCAD и видим результат (для отображения панели инструментов нужно переключить интерфейс на классический вид и в меню «Панели инструментов» установить видимость созданной нами панели).
Переходим к формированию ленточного меню.
Основные элементы, о которых пойдёт речь дальше, подписаны на скрине:
Создаём текстовый файл с любым названием и расширением «.cui» (например, «RibbonRoot.cui»). Внутреннее содержание файла будет соответствовать xml разметке; кодировка файла – «UTF-8» или «UTF-8 with BOM».
Открываем созданный файл любым текстовым редактором и пишем в него:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <?xml version="1.0" encoding="utf-8"?> <RibbonRoot> <RibbonPanelSourceCollection> <RibbonPanelSource UID="Инструменты" Text="Инструменты"> <RibbonCommandButton Text="Калькулятор текста" ButtonStyle="LargeWithText" MenuMacroID="TBSPlus_TextCalculator" /> <RibbonCommandButton Text="Нивелир" ButtonStyle="LargeWithText" MenuMacroID="TBSPlus_LevelingInstrument" /> </RibbonPanelSource> </RibbonPanelSourceCollection> <RibbonTabSourceCollection> <RibbonTabSource Text="Test" UID="Test_Tab"> <RibbonPanelSourceReference PanelId="Инструменты" /> </RibbonTabSource> </RibbonTabSourceCollection> </RibbonRoot> |
Разберем состав документа поэлементно.
RibbonRoot – корневой элемент документа.
В него будут вложены два элемента:
Первый из них – это, как можно догадаться из названия, коллекция панелей. В RibbonPanelSourceCollection будем вкладывать элементы типа RibbonPanelSource (описание отдельной панели).
Атрибуты RibbonPanelSource:
- UID – идентификатор, который мы будем использовать в дальнейшем (для простоты примем такое же значение, как для атрибута Text, хотя, естественно, ничего не мешает нам написать здесь любое значение).
- Text – отображаемое название панели.
В элемент RibbonPanelSource у нас вложены элементы типа RibbonCommandButton, описывающие непосредственно кнопки.
Атрибуты RibbonCommandButton:
- Text – отображаемое название
- ButtonStyle – размер кнопки и наличие текста. Варианты "LargeWithText", "LargeWithoutText", “LargeWithHorizontalText”, "SmallWithText", "SmallWithoutText". В нашем примере мы использовали "LargeWithText" – большая с текстом. Значение остальных вариантов можно так же понять из перевода
- MenuMacroID – внутреннее имя выполняемой команды.
Второй элемент, входящий в корень документа – RibbonTabSourceCollection. Опять же из названия можно понять, что это коллекция вкладок. В этот элемент будем вкладывать элементы типа RibbonTabSource, каждый из которых соответствует отдельной вкладке.
Атрибуты RibbonTabSource:
- Text – отображаемое название вкладки
- UID – уникальный идентификатор вкладки. Мы нигде не будем использовать его, но этот атрибут должен быть, поэтому запишем туда какое-либо уникальное значение.
В элемент, соответствующий создаваемой нами вкладке, мы должны вложить необходимые панели, создаваемые выше. Для этого нам потребуется элемент RibbonPanelSourceReference с единственным атрибутом PanelId, ссылающимся на идентификатор (UID) панели.
Итак, сохраняем этот файл.
Далее создаём архив типа «zip» (этот тип проверен на практике, при этом, к примеру «7z», по невыясненной причине не подходит), называем его любым именем с расширением «.cuix» (в нашем примере - «Test.cuix»), в этот архив помещаем созданный ранее файл «RibbonRoot.cui». Таким образом, «.cuix» представляет собой обычный архив, содержащий в себе xml файл, описывающий ленточное меню нашей надстройки.
«Подключим» сформированный файл в nanoCAD. Для этого в файл «Test.cfg» (например, в самое начало файла) добавим две строки:
1 2 | [\ribbon\Test] CUIX=s%CFG_PATH%\Test.cuix |
Здесь «%CFG_PATH%\Test.cuix» – путь к созданному файлу «Test.cuix». При этом «%CFG_PATH%» — это путь к папке с текущим «.cfg» файлом, т.е. «Test.cfg». Такую запись удобно использовать, если файлы «Test.cfg» и «Test.cuix» расположены в одной папке.
Примечание: указанные строки можно было так же добавить и в ранее упоминаемый файл «nanoCAD.cfg» (при этом вместо %CFG_PATH% в общем случае нужно будет указывать абсолютный путь), но в целях минимизации записей в «nanoCAD.cfg» в нашей команде используется именно описанный выше вариант.
Запускаем nanoCAD:
Итак, цель достигнута – создана новая вкладка на ленте, в ней создана одна панель, в которую добавлено две кнопки.
Рассмотрим еще пару полезных примеров.
1. Кнопка с выпадающим списком (RibbonSplitButton)
RibbonSplitButton задается элементом одноименного типа.
Атрибуты RibbonSplitButton:
- Text – отображаемое название кнопки
- Behavior – поведение. В нашем примере будем использовать значение "SplitFollowStaticText" – выпадающая с запоминанием
- ButtonStyle – размер кнопки и наличие текста аналогично атрибуту элемента RibbonCommandButton.
Для того чтобы не регистрировать новых команд и не добавлять новые иконки, будем использовать уже знакомые нам две команды, меняя только их отображаемые имена (атрибут Text).
В элемент RibbonPanelSource вкладываем RibbonSplitButton, в который добавляем кнопки (аналогично предыдущему пункту), которые должны войти в выпадающий список.
1 2 3 4 5 6 7 8 9 10 | <RibbonSplitButton Text="Выпадающий список" Behavior="SplitFollowStaticText" ButtonStyle="LargeWithText"> <RibbonCommandButton Text="Кнопка1" ButtonStyle="LargeWithText" MenuMacroID="TBSPlus_TextCalculator" /> <RibbonCommandButton Text="Кнопка2" ButtonStyle="LargeWithText" MenuMacroID="TBSPlus_LevelingInstrument" /> </RibbonSplitButton> |
Сохраняем файл. Для того, чтобы увидеть результат в случае работы с ленточным интерфейсом не обязательно перезагружать nanoCAD, можно использовать команду «ЛЕНТАОБН».
2. Маленькая кнопка
Компоновка маленьких кнопок (кнопок со значением атрибута ButtonStyle - "SmallWithText" или "SmallWithoutText") производится немного сложнее.
Если мы попробуем использовать ту же логику и напишем так:
То получим не тот результат, который ожидается:
Внешне кнопки выглядят так, как нужно, но они расположены в один ряд. Чаще всего мы используем маленькие кнопки для экономии места, и хотелось бы, чтобы они скомпоновались в одну колонку.
Для этого используем еще два элемента – RibbonRowPanel и RibbonRow. RibbonRowPanel мы вложим в нужное место RibbonPanelSource, а внутрь RibbonRowPanel вложим от одной до трёх маленьких кнопок, которые мы хотим скомпоновать в колонку. При этом каждый вкладываем элемент RibbonCommandButton «обернём» в RibbonRow.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <RibbonRowPanel> <RibbonRow> <RibbonCommandButton Text="Маленькая без текста1" ButtonStyle="SmallWithoutText" MenuMacroID="TBSPlus_LevelingInstrument" /> </RibbonRow> <RibbonRow> <RibbonCommandButton Text="Маленькая без текста2" ButtonStyle="SmallWithoutText" MenuMacroID="TBSPlus_LevelingInstrument" /> </RibbonRow> <RibbonRow> <RibbonCommandButton Text="Маленькая без текста3" ButtonStyle="SmallWithoutText" MenuMacroID="TBSPlus_LevelingInstrument" /> </RibbonRow> </RibbonRowPanel> |
Сохраняем файл, обновляем ленту:
Вынесем часть элементов на отдельную панель:
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 | <?xml version="1.0" encoding="utf-8"?> <RibbonRoot> <RibbonPanelSourceCollection> <RibbonPanelSource UID="Инструменты" Text="Инструменты"> <RibbonCommandButton Text="Калькулятор текста" ButtonStyle="LargeWithText" MenuMacroID="TBSPlus_TextCalculator" /> <RibbonCommandButton Text="Нивелир" ButtonStyle="LargeWithText" MenuMacroID="TBSPlus_LevelingInstrument" /> </RibbonPanelSource> <RibbonPanelSource UID="Панель2" Text="Панель2"> <RibbonSplitButton Text="Выпадающий список" Behavior="SplitFollowStaticText" ButtonStyle="LargeWithText"> <RibbonCommandButton Text="Кнопка1" ButtonStyle="LargeWithText" MenuMacroID="TBSPlus_TextCalculator" /> <RibbonCommandButton Text="Кнопка2" ButtonStyle="LargeWithText" MenuMacroID="TBSPlus_LevelingInstrument" /> </RibbonSplitButton> <RibbonRowPanel> <RibbonRow> <RibbonCommandButton Text="Маленькая без текста1" ButtonStyle="SmallWithoutText" MenuMacroID="TBSPlus_LevelingInstrument" /> </RibbonRow> <RibbonRow> <RibbonCommandButton Text="Маленькая без текста2" ButtonStyle="SmallWithoutText" MenuMacroID="TBSPlus_LevelingInstrument" /> </RibbonRow> <RibbonRow> <RibbonCommandButton Text="Маленькая без текста3" ButtonStyle="SmallWithoutText" MenuMacroID="TBSPlus_LevelingInstrument" /> </RibbonRow> </RibbonRowPanel> </RibbonPanelSource> </RibbonPanelSourceCollection> <RibbonTabSourceCollection> <RibbonTabSource Text="Test" UID="Test_Tab"> <RibbonPanelSourceReference PanelId="Инструменты" /> <RibbonPanelSourceReference PanelId="Панель2" /> </RibbonTabSource> </RibbonTabSourceCollection> </RibbonRoot> |
И результат:
Часть 2. Автоматизированная сборка файлов меню
По ссылке можно найти репозиторий с нашим решением MenuFilesGen.
Концепция программы заключается в генерации .cfg и .cuix файлов по описанной выше структуре на основании .tsv таблицы строго определенной структуры.
Примечание: формат .tsv был выбран как простой для чтения формат, не имеющий недостатка .csv, накладывающего ограничение на использование запятой (или точки с запятой), которая может использоваться в таблице (к примеру, в описании команды).
Структуру таблицы можно изучить на примере уже упомянутого TBS Plus.
Фрагмент таблицы:
Из неочевидного, считаем нужным прокомментировать:
1. «RibbonSplitButton» - используется для объединения нескольких кнопок в RibbonSplitButton. Для этого нужно прописать в столбец одинаковое название для нужных строк. К примеру, для строк 40-42 со скрины имеем:
2. «Не отображать в меню» - если True, команда не будет учтена в меню (используется, к примеру, для тестовых команд на время разработки).
Таким образом, достаточно сформировать таблицу с описанием команд вашего решения, сохранить ее в формате .tsv, запустить MenuFilesGen и указать путь к созданному .tsv файлу. Программа создаст .cfg и .cuix файлы в той же папке. Заметим, что сгенерированные файлы будут иметь название файла с таблицей. Это же название будет и у созданной вкладки. Тут же важно обратить внимание, что одна таблица подразумевает описание одной вкладки.
Дальше остаётся добавить сгенерированные файлы в инсталлятор вашего плагина и «научить» его прописывать путь к вашему .cfg файлу в файл «nanoCAD.cfg». Готово, ваша надстройка теперь обладает интерфейсом, перекомпоновка которого стала очень простой.
Примечание: важно, что .cfg и .cuix файлы у пользователя должны быть расположены в одной папке, в этой же папке должна быть папка “icons”, содержащая набор всех необходимых иконок, при этом имена иконок должны совпадать с внутренними именами команд.
Мы намеренно принимаем такие допущения как использование конкретного формата (.tsv), использование имени этого файла, сохранение новых файлов рядом с исходным и тд, потому что понимаем, что каждый разработчик, кто возьмет MenuFilesGen на вооружение, сможет абсолютно без труда настроить его под себя и сократить количество кликов до минимума.
Таким образом, предложенное в этой статье решение является, как нам кажется, хорошей основой для инструмента, который вы можете довести до идеала с учётом специфики разработки в вашей компании.
Материалы статьи можно изучить подробнее по ссылке.
На случай, если вдруг ссылка перестала работать - исходник продублировал на этом сайте.