В прошлой своей статье я пообещал, что напишу еще несколько небольших заметок о разработке плагинов для AutoCAD. На Хабре сведений по этой теме крайне мало — пожалуй, можно и добавить еще пару материалов в обойму. В этой статье я приведу пример создания на ленте AutoCAD новой вкладки с несколькими элементами управления.
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!");
}
}
}
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
.
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 классы, на мой взгляд, обладают не самыми широкими возможностями по настройке своего внешнего вида. Однако базовые вещи, безусловно, есть.
Во-первых, элементы управления можно располагать друг под другом — это особенно удобно, если используются «узкие» элементы вроде выпадающих списков.
Во-вторых, у каждого элемента управления есть свойства, позволяющие изменять его внешний вид. Например, для выпадающего списка можно задать заголовок и ширину, для кнопки — размер (большая или маленькая) и подпись. Кроме того, можно добавить всплывающие подсказки (в примере она добавлена для третьей кнопки).
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()
{
}
}
}
Результат:
На этом статья подходит к концу. В следующий раз напишу о работе со слоями и простыми графическими объектами.
Автор: lostpassword