В прошлой своей статье я пообещал, что напишу еще несколько небольших заметок о разработке плагинов для AutoCAD. На Хабре сведений по этой теме крайне мало — пожалуй, можно и добавить еще пару материалов в обойму. В этой статье я приведу пример создания на ленте AutoCAD новой вкладки с несколькими элементами управления.
Когда я в свое время начинал работать с лентой, то больше всего мне помог пример, изложенный здесь. В основном я буду опираться именно на него.
Кроме того, некоторую информацию можно почерпнуть из этого поста Kean Walmsley.
Для начала вспомним, как выглядит лента в AutoCAD:

В верхней части расположен список вкладок ленты (Home, Mesh Modeling, Render...). При выборе вкладки на ленте отображаются элементы управления этой вкладки, сгруппированные в панели (Modeling, Mesh, Solid Editing...).
Ну что же, приступим.
Этому была посвящена прошлая статья. В качестве требуемой версии .NET Framework в приведенных ниже примерах указана .NET Framework 3.5.
В данном случае пригодятся библиотеки AutoCAD .NET API с именами AcMgd.dll, AcDbMgd.dll и AdWindows.dll (не забываем отключать
С точки зрения кода все выглядит очень просто. В AutoCAD .NET API уже имеются классы, отвечающие за работу с лентой. Они находятся в пространстве имен
Чтобы создать новую вкладку на ленте, необходимо:
Собираем проект, запускаем AutoCAD, загружаем с помощью команды NETLOAD наш плагин, выполняем команду TestCommand…

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

6. Взаимодействие с выпадающим списком (
Все элементы выпадающего списка

Чтобы получить приличный выпадающий список, в качестве его элементов можно использовать экземпляры рассмотренного выше класса
В результате увидим вот что:

При необходимости можно использовать заложенные в
При выборе элемента списка
Результат:

Имеющиеся в AutoCAD .NET API классы, на мой взгляд, обладают не самыми широкими возможностями по настройке своего внешнего вида. Однако базовые вещи, безусловно, есть.
Во-первых, элементы управления можно располагать друг под другом — это особенно удобно, если используются «узкие» элементы вроде выпадающих списков.
Во-вторых, у каждого элемента управления есть свойства, позволяющие изменять его внешний вид. Например, для выпадающего списка можно задать заголовок и ширину, для кнопки — размер (большая или маленькая) и подпись. Кроме того, можно добавить всплывающие подсказки (в примере она добавлена для третьей кнопки).
Результат:


На этом статья подходит к концу. В следующий раз напишу о работе со слоями и простыми графическими объектами.
public static string disclaimer = "Автор не является профессиональным разработчиком и не обладает глубокими знаниями AutoCAD. Этот пост – просто небольшой рассказ о создании плагина.";
Введение
Когда я в свое время начинал работать с лентой, то больше всего мне помог пример, изложенный здесь. В основном я буду опираться именно на него.
Кроме того, некоторую информацию можно почерпнуть из этого поста Kean Walmsley.
Для начала вспомним, как выглядит лента в AutoCAD:

В верхней части расположен список вкладок ленты (Home, Mesh Modeling, Render...). При выборе вкладки на ленте отображаются элементы управления этой вкладки, сгруппированные в панели (Modeling, Mesh, Solid Editing...).
Ну что же, приступим.
1. Создание нового проекта плагина
Этому была посвящена прошлая статья. В качестве требуемой версии .NET Framework в приведенных ниже примерах указана .NET Framework 3.5.
Можно сразу добавить каркас кода:
using System;
using Autodesk.AutoCAD.Runtime;
using Autodesk.Windows;
namespace MyAutoCADDll
{
public class Commands : IExtensionApplication
{
// эта функция будет вызываться при выполнении в AutoCAD команды "TestCommand"
[CommandMethod("TestCommand")]
public void MyCommand()
{
}
// Функции Initialize() и Terminate() необходимы, чтобы реализовать интерфейс IExtensionApplication
public void Initialize()
{
}
public void Terminate()
{
}
}
}
2. Добавление ссылок на необходимые библиотеки
В данном случае пригодятся библиотеки AutoCAD .NET API с именами AcMgd.dll, AcDbMgd.dll и AdWindows.dll (не забываем отключать
CopyLocal
!). Кроме того, нужно добавить ссылки на три сборки самой .NET: Presentation Core, Presentation Framework и WindowsBase.3. Собственно написание кода для создания новой вкладки
С точки зрения кода все выглядит очень просто. В AutoCAD .NET API уже имеются классы, отвечающие за работу с лентой. Они находятся в пространстве имен
Autodesk.Windows
(его содержит контейнер AdWindows.dll).Чтобы создать новую вкладку на ленте, необходимо:
- создать элементы интерфейса;
- сгруппировать эти элементы в контейнеры;
- создать панели, на которых будут размещены эти контейнеры;
- создать вкладку, на которой будут размещены эти панели;
- добавить созданную вкладку на ленту AutoCAD.
Код:
// эта функция будет вызываться при выполнении в AutoCAD команды «TestCommand»
[CommandMethod("TestCommand")]
public void MyCommand()
{
// создаем выпадающий список
Autodesk.Windows.RibbonCombo comboBox1 = new RibbonCombo();
comboBox1.Id = "_combobox1";
// создаем кнопку
Autodesk.Windows.RibbonButton button1 = new Autodesk.Windows.RibbonButton();
button1.Id = "_button1";
// создаем контейнер для элементов
Autodesk.Windows.RibbonPanelSource rbPanelSource = new Autodesk.Windows.RibbonPanelSource();
rbPanelSource.Title = "Новая панель элементов";
// добавляем в контейнер элементы управления
rbPanelSource.Items.Add(comboBox1);
rbPanelSource.Items.Add(new RibbonSeparator());
rbPanelSource.Items.Add(button1);
// создаем панель
RibbonPanel rbPanel = new RibbonPanel();
// добавляем на панель контейнер для элементов
rbPanel.Source = rbPanelSource;
// создаем вкладку
RibbonTab rbTab = new RibbonTab();
rbTab.Title = "Новая вкладка";
rbTab.Id = "HabrRibbon";
// добавляем на вкладку панель
rbTab.Panels.Add(rbPanel);
// получаем указатель на ленту AutoCAD
Autodesk.Windows.RibbonControl rbCtrl = ComponentManager.Ribbon;
// добавляем на ленту вкладку
rbCtrl.Tabs.Add(rbTab);
// делаем созданную вкладку активной ("выбранной")
rbTab.IsActive = true;
}
Собираем проект, запускаем AutoCAD, загружаем с помощью команды NETLOAD наш плагин, выполняем команду TestCommand…
Да, не самый впечатляющий результат. :)
Но ничего, чуть позже сделаем вкладку повеселее. А пока разберемся с тем, что уже есть.
4. Поиск элементов на ленте
Для поиска вкладки на ленте можно использовать метод
ComponentManager.Ribbon.FindTab(string id)
. В качестве аргумента необходимо указать Id
вкладки, заданный при ее создании.Существуют аналогичные методы для поиска панели (
ComponentManager.Ribbon.FindPanel(string id, bool SearchActiveTabOnly)
) и прочих элементов управления (ComponentManager.Ribbon.FindItem(string id, bool SearchActiveTabOnly)
).В случае успешного нахождения элемента приведенные функции вернут соответствующий объект, иначе будет возврашено значение
null
.5. Обработка нажатия кнопки
Для привязки обработчика нажатия кнопки служит свойство
CommandHandler
класса RibbonButton
. В этом свойстве необходимо указать метод, реализующий интерфейс System.Windows.Input.ICommand
.В рамках интерфейса
ICommand
класс должен реализовать событие CanExecuteChanged
, а также функции CanExecute
и Execute
.Код примера с обработчиком нажатия кнопки:
using System;
using Autodesk.AutoCAD.Runtime;
using Autodesk.Windows;
namespace MyAutoCADDll
{
public class Commands : IExtensionApplication
{
// эта функция будет вызываться при выполнении в AutoCAD команды "TestCommand"
[CommandMethod("TestCommand")]
public void MyCommand()
{
// создаем выпадающий список
Autodesk.Windows.RibbonCombo comboBox1 = new RibbonCombo();
comboBox1.Id = "_combobox1";
// создаем кнопку
Autodesk.Windows.RibbonButton button1 = new Autodesk.Windows.RibbonButton();
button1.Id = "_button1";
// привязываем к кнопке обработчик нажатия
button1.CommandHandler = new CommandHandler_Button1();
// создаем контейнер для элементов
Autodesk.Windows.RibbonPanelSource rbPanelSource = new Autodesk.Windows.RibbonPanelSource();
rbPanelSource.Title = "Новая панель элементов";
// добавляем в контейнер элементы управления
rbPanelSource.Items.Add(comboBox1);
rbPanelSource.Items.Add(new RibbonSeparator());
rbPanelSource.Items.Add(button1);
// создаем панель
RibbonPanel rbPanel = new RibbonPanel();
// добавляем на панель контейнер для элементов
rbPanel.Source = rbPanelSource;
// создаем вкладку
RibbonTab rbTab = new RibbonTab();
rbTab.Title = "Новая вкладка";
rbTab.Id = "HabrRibbon";
// добавляем на вкладку панель
rbTab.Panels.Add(rbPanel);
// получаем указатель на ленту AutoCAD
Autodesk.Windows.RibbonControl rbCtrl = ComponentManager.Ribbon;
// добавляем на ленту вкладку
rbCtrl.Tabs.Add(rbTab);
// делаем созданную вкладку активной ("выбранной")
rbTab.IsActive = true;
}
// Функции Initialize() и Terminate() необходимы, чтобы реализовать интерфейс IExtensionApplication
public void Initialize()
{
}
public void Terminate()
{
}
}
// обработчик нажатия кнопки
public class CommandHandler_Button1 : System.Windows.Input.ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object param)
{
return true;
}
public void Execute(object parameter)
{
System.Windows.MessageBox.Show("Habr!");
}
}
}
NB:
Событие
В настоящем примере команда доступна всегда, и это состояние не меняется. В моей реальной задаче тоже ни разу не возникало необходимости как-то использовать первые два параметра.)
CanExecuteChanged
оповещает пользователей команды о возможном изменении ее доступности для выполнения (короче говоря, работает она или не работает). Функция CanExecute
позволяет узнать, доступна ли команда для выполнения в данный момент времени. А функция Execute
— это собственно те действия, которые должна выполнять команда, когда ее вызвали.В настоящем примере команда доступна всегда, и это состояние не меняется. В моей реальной задаче тоже ни разу не возникало необходимости как-то использовать первые два параметра.)
Теперь после нажатия кнопки на экране появится окно с сообщением.
6. Взаимодействие с выпадающим списком (RibbonCombo
)
6.1 Добавление, изменение, удаление элементов списка
Все элементы выпадающего списка
RibbonCombo
содержатся в его свойстве Items
. Оно имеет тип System.Collections.ObjectModel.ObservableCollection
, причем в качестве типа содержимого выступает System.Object
. Таким образом, элементом коллекции может быть объект любого класса. К сожалению, если просто добавить в этот массив несколько текстовых строк, то желаемого эффекта мы не получим:comboBox1.Items.Add("добавим");
comboBox1.Items.Add("несколько");
comboBox1.Items.Add("элементов");
Чтобы получить приличный выпадающий список, в качестве его элементов можно использовать экземпляры рассмотренного выше класса
RibbonButton
:Autodesk.Windows.RibbonButton tempRibBut1 = new Autodesk.Windows.RibbonButton();
tempRibBut1.Id = "_temp_button_1";
tempRibBut1.Text = "элемент 1";
tempRibBut1.ShowText = true;
Autodesk.Windows.RibbonButton tempRibBut2 = new Autodesk.Windows.RibbonButton();
tempRibBut2.Id = "_temp_button_2";
tempRibBut2.Text = "элемент 2";
tempRibBut2.ShowText = true;
comboBox1.Items.Add(tempRibBut1);
comboBox1.Items.Add(tempRibBut2);
В результате увидим вот что:
При необходимости можно использовать заложенные в
ObservableCollection
свойства и методы, в частности:- метод
Remove(object item)
— для удаления указанного элемента; - метод
RemoveAt(int index)
— для удаления элемента на указанной позиции; - метод
Clear()
— для удаления всех элементов из коллекции; - свойство
Count
— для получения количества элементов в коллекции.
RibbonButton
хранится в его свойстве Current
.NB:
Обычно элемент списка соотносится с каким-то объектом предметной области. Чтобы иметь возможность быстро определить, с чем сопоставлен тот или иной элемент списка, можно использовать свойство
Тогда при обработке элемента списка можно посмотреть, какой был задан тег:
Можно пойти и еще дальше. Поскольку свойство
После этого можно будет обратиться к любому свойству этого объекта:
При возникновении острого желания экономить строки есть возможность писать конструкции вида
Разумеется, в реальном коде необходимо убеждаться, что полученные значения не равны
Tag
класса RibbonButton
:Autodesk.Windows.RibbonButton tempRibBut1 = new Autodesk.Windows.RibbonButton();
tempRibBut1.Id = "_temp_button_1";
tempRibBut1.Text = "элемент 1";
tempRibBut1.ShowText = true;
tempRibBut1.Tag = "elementTag"; // задаем тег
comboBox1.Items.Add(tempRibBut1);
Тогда при обработке элемента списка можно посмотреть, какой был задан тег:
object obj = comboBox1.Items[0];
string itemTag = (obj as RibbonButton).Tag; // "elementTag"
Можно пойти и еще дальше. Поскольку свойство
Tag
имеет тип System.Object
, в качестве тега может выступать объект любого класса, в том числе и созданного самим программистом:tempRibBut1.Tag = new MyClass("objectDecription");
После этого можно будет обратиться к любому свойству этого объекта:
object obj = comboBox1.Items[0];
MyClass itemTag = (obj as RibbonButton).Tag as MyClass;
string myClassDecription = itemTag.Description;
string myClassDecription = ((comboBox1.Items[0] as RibbonButton).Tag as MyClass).Description
Разумеется, в реальном коде необходимо убеждаться, что полученные значения не равны
null
.6.2 Обработка события выбора элемента списка
При выборе элемента списка
RibbonCombo
генерируется событие CurrentChanged
. Вот простой пример обработчика такого события:// обработчик выбора нового элемента выпадающего списка
public static void comboBox1_CurrentChanged(object o, RibbonPropertyChangedEventArgs args)
{
if (args.NewValue != null)
{
System.Windows.MessageBox.Show((args.NewValue as RibbonButton).Text);
}
}
Полный код примера:
using System;
using Autodesk.AutoCAD.Runtime;
using Autodesk.Windows;
namespace MyAutoCADDll
{
public class Commands : IExtensionApplication
{
// эта функция будет вызываться при выполнении в AutoCAD команды "TestCommand"
[CommandMethod("TestCommand")]
public void MyCommand()
{
// создаем выпадающий список
Autodesk.Windows.RibbonCombo comboBox1 = new RibbonCombo();
comboBox1.Id = "_combobox1";
// добавляем новые элементы в список
Autodesk.Windows.RibbonButton tempRibBut1 = new Autodesk.Windows.RibbonButton();
tempRibBut1.Id = "_temp_button_1";
tempRibBut1.Text = "элемент 1";
tempRibBut1.ShowText = true;
tempRibBut1.Tag = "btn1";
Autodesk.Windows.RibbonButton tempRibBut2 = new Autodesk.Windows.RibbonButton();
tempRibBut2.Id = "_temp_button_2";
tempRibBut2.Text = "элемент 2";
tempRibBut2.ShowText = true;
tempRibBut2.Tag = "btn2";
comboBox1.Items.Add(tempRibBut1);
comboBox1.Items.Add(tempRibBut2);
// привязываем к списку обработчик выбора нового элемента
comboBox1.CurrentChanged += comboBox1_CurrentChanged;
// создаем кнопку
Autodesk.Windows.RibbonButton button1 = new Autodesk.Windows.RibbonButton();
button1.Id = "_button1";
// привязываем к кнопке обработчик нажатия
button1.CommandHandler = new CommandHandler_Button1();
// создаем контейнер для элементов
Autodesk.Windows.RibbonPanelSource rbPanelSource = new Autodesk.Windows.RibbonPanelSource();
rbPanelSource.Title = "Новая панель элементов";
// добавляем в контейнер элементы управления
rbPanelSource.Items.Add(comboBox1);
rbPanelSource.Items.Add(new RibbonSeparator());
rbPanelSource.Items.Add(button1);
// создаем панель
RibbonPanel rbPanel = new RibbonPanel();
// добавляем на панель контейнер для элементов
rbPanel.Source = rbPanelSource;
// создаем вкладку
RibbonTab rbTab = new RibbonTab();
rbTab.Title = "Новая вкладка";
rbTab.Id = "HabrRibbon";
// добавляем на вкладку панель
rbTab.Panels.Add(rbPanel);
// получаем указатель на ленту AutoCAD
Autodesk.Windows.RibbonControl rbCtrl = ComponentManager.Ribbon;
// добавляем на ленту вкладку
rbCtrl.Tabs.Add(rbTab);
// делаем созданную вкладку активной ("выбранной")
rbTab.IsActive = true;
}
// обработчик выбора нового элемента выпадающего списка
public static void comboBox1_CurrentChanged(object o, RibbonPropertyChangedEventArgs args)
{
if (args.NewValue != null)
{
System.Windows.MessageBox.Show((args.NewValue as RibbonButton).Text);
}
}
// Функции Initialize() и Terminate() необходимы, чтобы реализовать интерфейс IExtensionApplication
public void Initialize()
{
}
public void Terminate()
{
}
}
// обработчик нажатия кнопки
public class CommandHandler_Button1 : System.Windows.Input.ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object param)
{
return true;
}
public void Execute(object parameter)
{
System.Windows.MessageBox.Show("Habr!");
}
}
}
Результат:
7. Настройка внешнего вида элементов управления
Имеющиеся в AutoCAD .NET API классы, на мой взгляд, обладают не самыми широкими возможностями по настройке своего внешнего вида. Однако базовые вещи, безусловно, есть.
Во-первых, элементы управления можно располагать друг под другом — это особенно удобно, если используются «узкие» элементы вроде выпадающих списков.
Во-вторых, у каждого элемента управления есть свойства, позволяющие изменять его внешний вид. Например, для выпадающего списка можно задать заголовок и ширину, для кнопки — размер (большая или маленькая) и подпись. Кроме того, можно добавить всплывающие подсказки (в примере она добавлена для третьей кнопки).
Код (обратите внимание, была добавлена ссылка на сборку и пространство имен System.Drawing):
using System;
using System.Drawing;
using Autodesk.AutoCAD.Runtime;
using Autodesk.Windows;
namespace MyAutoCADDll
{
public class Commands : IExtensionApplication
{
// эта функция будет вызываться при выполнении в AutoCAD команды "TestCommand"
[CommandMethod("TestCommand")]
public void MyCommand()
{
// создаем квадратик цвета морской волны (он будет старательно играть роль иконки)
Bitmap bmp = new Bitmap(1, 1);
bmp.SetPixel(0, 0, Color.Aquamarine);
bmp = new Bitmap(bmp, 1024, 1024);
IntPtr hBitmap = bmp.GetHbitmap();
System.Windows.Media.Imaging.BitmapSource bs =
System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
System.Windows.Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
// создаем выпадающие списки
Autodesk.Windows.RibbonCombo comboBox1 = new RibbonCombo();
comboBox1.Id = "_combobox1";
comboBox1.Width = 200;
comboBox1.Text = "Список 1";
comboBox1.ShowText = true;
Autodesk.Windows.RibbonCombo comboBox2 = new RibbonCombo();
comboBox2.Id = "_combobox2";
comboBox2.Width = 200;
comboBox2.Image = bs;
comboBox2.ShowImage = true;
// создаем кнопки
Autodesk.Windows.RibbonButton button1 = new Autodesk.Windows.RibbonButton();
button1.Id = "_button1";
Autodesk.Windows.RibbonButton button2 = new Autodesk.Windows.RibbonButton();
button2.Id = "_button2";
// создаем вертикальные панели, на которых будут размещены друг под другом выпадающие списки и кнопки
Autodesk.Windows.RibbonRowPanel RowPanel1 = new Autodesk.Windows.RibbonRowPanel();
Autodesk.Windows.RibbonRowPanel RowPanel2 = new Autodesk.Windows.RibbonRowPanel();
// размещаем в вертикальных панелях выпадающие списки и кнопки
RowPanel1.Items.Add(comboBox1);
RowPanel1.Items.Add(new RibbonRowBreak());
RowPanel1.Items.Add(comboBox2);
RowPanel2.Items.Add(button1);
RowPanel2.Items.Add(new RibbonRowBreak());
RowPanel2.Items.Add(button2);
// создаем кнопки большого размера
Autodesk.Windows.RibbonButton button3 = new Autodesk.Windows.RibbonButton();
button3.Id = "_button3";
button3.IsToolTipEnabled = true;
button3.ToolTip = "Это большая кнопка";
button3.Size = Autodesk.Windows.RibbonItemSize.Large;
button3.LargeImage = bs;
Autodesk.Windows.RibbonButton button4 = new Autodesk.Windows.RibbonButton();
button4.Id = "_button4";
button4.Text = "^___^";
button4.ShowText = true;
button4.Size = Autodesk.Windows.RibbonItemSize.Large;
button4.LargeImage = bs;
// создаем контейнеры для элементов
Autodesk.Windows.RibbonPanelSource rbPanelSource1 = new Autodesk.Windows.RibbonPanelSource();
rbPanelSource1.Title = "Новая панель элементов";
Autodesk.Windows.RibbonPanelSource rbPanelSource2 = new Autodesk.Windows.RibbonPanelSource();
rbPanelSource2.Title = "Еще одна панель";
// добавляем в контейнеры элементы управления
rbPanelSource1.Items.Add(RowPanel1);
rbPanelSource1.Items.Add(RowPanel2);
rbPanelSource1.Items.Add(new RibbonSeparator());
rbPanelSource1.Items.Add(button3);
rbPanelSource2.Items.Add(button4);
// создаем панели
RibbonPanel rbPanel1 = new RibbonPanel();
RibbonPanel rbPanel2 = new RibbonPanel();
// добавляем на панели контейнеры для элементов
rbPanel1.Source = rbPanelSource1;
rbPanel2.Source = rbPanelSource2;
// создаем вкладку
RibbonTab rbTab = new RibbonTab();
rbTab.Title = "Новая вкладка";
rbTab.Id = "HabrRibbon";
// добавляем на вкладку панели
rbTab.Panels.Add(rbPanel1);
rbTab.Panels.Add(rbPanel2);
// получаем указатель на ленту AutoCAD
Autodesk.Windows.RibbonControl rbCtrl = ComponentManager.Ribbon;
// добавляем на ленту вкладку
rbCtrl.Tabs.Add(rbTab);
// делаем созданную вкладку активной ("выбранной")
rbTab.IsActive = true;
}
// Функции Initialize() и Terminate() необходимы, чтобы реализовать интерфейс IExtensionApplication
public void Initialize()
{
}
public void Terminate()
{
}
}
}
Результат:
На этом статья подходит к концу. В следующий раз напишу о работе со слоями и простыми графическими объектами.