Как стать автором
Поиск
Написать публикацию
Обновить

Автоматизированное формирование меню надстройки для nanoCAD

Время на прочтение12 мин
Количество просмотров1.6K

Разработчики программных решений для nanoCAD, имея потребность создать для своего плагина меню (здесь и далее под словом «меню» будем понимать вкладки и кнопки в ленточном и/или классическом интерфейсе программы), сталкиваются со следующими проблемами:

  1. Не понятно, какие файлы создавать и что в них прописывать. Существующие примеры .cfg/.cuix файлов (которые можно найти в папках nanoCAD или попробовать сделать вручную через создание «кнопок» в настройках nanoCAD) содержат в себе очень много не очень понятного текста. Консолидированной информации по этому поводу ни для nanoCAD, ни для других CAD-платформ нами найдено не было.

  2. Для создания каждой отдельной «кнопки» в меню необходимо прописать достаточно большое количество текста. Хочется иметь какое-то средство автоматизированной генерации обозначенных файлов.

Актуальность этих проблем поднялась в нашем 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 файлу, в нашем случай: 
#include "C:\Test\Test.cfg"

ВАЖНО – созданный нами .cfg файл должен «лежать» на диске C. Иначе есть шанс, что он не загрузится в программу.

Далее сохраняем файл «nanoCAD.cfg» без изменения кодировки и закрываем его, он нам больше не понадобится.

Открываем созданный ранее файл («Test.cfg») с помощью любого текстового редактора.

Для начала нам нужно «зарегистрировать» все нужные нам команды. Для этого пишем следующее (переносы строк добавлены исключительно для удобства восприятия)

[\configman][\configman\commands]

[\configman\commands\TBSPlus_TextCalculator]

weight=i10

cmdtype=i1intername=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 на примере любой (в том числе базовой) команды:

Отметим лишь несколько моментов.

  1. Перед значением параметров указывается префикс, соответствующий типу данных:
    “s” для всех строковых значений (имен, описаний, путей и т.д.);
    “i” для целочисленных значений;
    “f” для булевых значений (“f0” – false, “f1” - true);

  2. Внутреннее имя – то имя, которое объявлено в нашем приложении (в случае .NET это значение атрибута «CommandMethod») и используется при вызове команды с командной строки. Это же имя будем использовать в нашем файле в дальнейшем, для создания «кнопки» для нашей команды.

  3. В более сложных случаях для команды можно добавить ключевое слово: Keyword=s^C^C(setq a 1)(load"arh_atr")(arh_atr)
    Это значение будет «отправлено на консоль» в момент обращения к команде.

  4. Путь к иконке. Здесь задан относительный путь (помним про указанный префикс). В папку icons, «лежащую» рядом с нашим «Test.cfg» файлом, сохраним две иконки с соответствующими именами. Естественно, имя иконки может отличаться от внутреннего имени команды, оно должно лишь совпадать с именем, указанным в «Test.cfg» файле. Из нашего опыта разрешение 32х32 хорошо подходит, как для больших, так и для маленьких кнопок. Используемые иконки расположены в архиве, приложенном к статье.

Сохраняем файл (всегда помня о кодировке) и открываем nanoCAD (необязательный шаг). Здесь и далее важно понимать, что наш файл «считывается» программой во время загрузки, поэтому после внесения любого изменения в .cfg файл для того, чтобы увидеть эти изменения в nanoCAD, мы должны перезагрузить программу.

На текущем этапе наши команды были зарегистрированы (можно увидеть в настройках nanoCAD, скрин выше). Теперь при консольном обращении в списке команд мы видим наши команды, во-первых, с иконками, а во-вторых, с полным (в нашем случае - русскоязычным) названием.

Теперь, добавим вкладку с нашими кнопками в классическое меню. Для этого в конец нашего «Test.cfg» добавляем следующее:

[\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» пишем:

[\toolbars]

[\toolbars\Test_Инструменты]

name=sTest_Инструмент

Intername=sTest_Инструменты

[\toolbars\Test_Инструменты\TBSPlus_TextCalculator]

Intername=sTBSPlus_TextCalculator

[\toolbars\Test_Инструменты\TBSPlus_LevelingInstrument]

Intername=sTBSPlus_LevelingInstrument

Мы создали панель инструментов Test_Инструменты и добавили на нее две кнопки.

Сохраняем файл, открываем nanoCAD и видим результат (для отображения панели инструментов нужно переключить интерфейс на классический вид и в меню «Панели инструментов» установить видимость созданной нами панели).

UPD:

Как видим, с созданной нами панелью инструментов есть две сложности: во-первых, она не отображается по умолчанию, во-вторых, она не закреплена и после включения видимости отображается в случайном (с той точки зрения, что мы его не определяли) месте на экране. Эти особенности, а также способ их регулирования осветил участник чата nanoCAD API, рассмотрим это решение.

В конец нашего файла «Test.cfg» добавим следующее:

[\toolbarspos]

[\toolbarspos\Test_Инструменты]

DockPosition=sTop 

row=i0 

pos=i1 

InitialVisible=f1

Теперь наша панель инструментов будет отображаться по умолчанию и будет размещена в указанном нами месте (сверху, в первом ряду; на второй позиции). Возможные значения параметров можно посмотреть в настройках интерфейса nanoCAD (русскоязычные названия приведены на скрине):

Переходим к формированию ленточного меню.

Основные элементы, о которых пойдёт речь дальше, подписаны на скрине:

Создаём текстовый файл с любым названием и расширением «.cui» (например, «RibbonRoot.cui»). Внутреннее содержание файла будет соответствовать xml разметке; кодировка файла – «UTF-8» или «UTF-8 with BOM».

Открываем созданный файл любым текстовым редактором и пишем в него:

<?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 и RibbonTabSourceCollection.

Первый из них – это, как можно догадаться из названия, коллекция панелей. В RibbonPanelSourceCollectionбудем вкладывать элементы типа RibbonPanelSource (описание отдельной панели).

Атрибуты RibbonPanelSource:

  1. UID – идентификатор, который мы будем использовать в дальнейшем (для простоты примем такое же значение, как для атрибута Text, хотя, естественно, ничего не мешает нам написать здесь любое значение).

  2. Text – отображаемое название панели.

В элемент RibbonPanelSource у нас вложены элементы типа RibbonCommandButton, описывающие непосредственно кнопки.

Атрибуты RibbonCommandButton:

  1. Text – отображаемое название

  2. ButtonStyle – размер кнопки и наличие текста. Варианты "LargeWithText", "LargeWithoutText", “LargeWithHorizontalText”, "SmallWithText", "SmallWithoutText". В нашем примере мы использовали "LargeWithText" – большая с текстом. Значение остальных вариантов можно так же понять из перевода

  3. MenuMacroID – внутреннее имя выполняемой команды.

Второй элемент, входящий в корень документа – RibbonTabSourceCollection. Опять же из названия можно понять, что это коллекция вкладок. В этот элемент будем вкладывать элементы типа RibbonTabSource, каждый из которых соответствует отдельной вкладке.

Атрибуты RibbonTabSource:

  1. Text – отображаемое название вкладки

  2. UID – уникальный идентификатор вкладки. Мы нигде не будем использовать его, но этот атрибут должен быть, поэтому запишем туда какое-либо уникальное значение.

В элемент, соответствующий создаваемой нами вкладке, мы должны вложить необходимые панели, создаваемые выше. Для этого нам потребуется элемент RibbonPanelSourceReference с единственным атрибутом PanelId, ссылающимся на идентификатор (UID) панели.

Итак, сохраняем этот файл.

Далее создаём архив типа «zip» (этот тип проверен на практике, при этом, к примеру «7z», по невыясненной причине не подходит), называем его любым именем с расширением «.cuix» (в нашем примере - «Test.cuix»), в этот архив помещаем созданный ранее файл «RibbonRoot.cui». Таким образом, «.cuix» представляет собой обычный архив, содержащий в себе xml файл, описывающий ленточное меню нашей надстройки.

«Подключим» сформированный файл в nanoCAD. Для этого в файл «Test.cfg» (например, в самое начало файла) добавим две строки:

[\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:

  1. Text – отображаемое название кнопки

  2. Behavior – поведение. В нашем примере будем использовать значение "SplitFollowStaticText" – выпадающая с запоминанием

  3. ButtonStyle – размер кнопки и наличие текста аналогично атрибуту элемента RibbonCommandButton.

Для того чтобы не регистрировать новых команд и не добавлять новые иконки, будем использовать уже знакомые нам две команды, меняя только их отображаемые имена (атрибут Text).

В элемент RibbonPanelSource вкладываем RibbonSplitButton, в который добавляем кнопки (аналогично предыдущему пункту), которые должны войти в выпадающий список.

<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.

<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>

Сохраняем файл, обновляем ленту:

Вынесем часть элементов на отдельную панель:

<?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 на вооружение, сможет абсолютно без труда настроить его под себя и сократить количество кликов до минимума.

Расскажем, к примеру, как это работает у нас. В проекте MenuFilesGen мы создали несколько конфигураций (по одной на каждый разрабатываемый нами плагин для nanoCAD). Для каждого плагина прописали нужные пути и ссылки:

Теперь для сборки файлов меню выбираем нужную конфигурацию и запускаем проект. Программа сама скачает таблицу в нужном формате и сохранит сгенерированные файлы в нужную папку, откуда их в дальнейшем «заберёт» инсталлятор.

Таким образом, предложенное в этой статье решение является, как нам кажется, хорошей основой для инструмента, который вы можете довести до идеала с учётом специфики разработки в вашей компании.

Материалы статьи можно изучить подробнее по ссылке.

Теги:
Хабы:
Всего голосов 4: ↑4 и ↓0+4
Комментарии0

Публикации

Ближайшие события