вторник, 11 декабря 2012 г.

AutoLISP\Visual LISP и диалоговые окна...

Головная боль многих AutoLISP\Visial LISP программистов, разрабатывающих приложения под AutoCAD - создание графического интерфейса - так называемого GUI. Вариант, предлагаемый компанией Autodesk - технология DCL, является весьма ограниченным как в плане удобства его использования, так и в плане возможностей. Порой у программистов возникает желание прибегать к помощи сторонних технологий для построения GUI. Альтернативы могут быть разными: например использование COM или .NET.

Преамбула

В данной заметке я буду писать о .NET по ряду причин:
  1. .NET я знаю намного лучше, чем тот же COM.
  2. В плане возможностей по созданию GUI,  платформа .NET намного превосходит COM как в плане удобства, простоты использования, так и в плане возможностей, поскольку в .NET существует такая технология как WPF, и AutoCAD предоставляет разработчикам возможность использовать её, через недокументированные, статические методы ShowModalWindow и ShowModelessWindow класса Application. Не документированы они (и к сожалению далеко не только они) уже много (как минимум - 5) лет, в виду неполного соответствия официальной документации, той объектной модели, которую эта документация описывает (поэтому время от времени полезно просматривать объектную модель в Object Browser, дабы не пропустить что-то интересное. 
В отличие от ObjectARX и .NET,  все программы, написанные на AutoLISP\Visial LISP (за редким исключением), могут работать без необходимости внесения в их код каких-либо изменений практически в любой версии AutoCAD. 

Если в качестве возможной платформы для удобного создания GUI любой сложности рассматривать .NET, то важно понимать все плюсы и минусы принятия такого решения, дабы объективно оценить затраты. Каждый программист принимает решения исходя из сферы своих интересов, знаний и возможностей. 

Давайте обозначим все плюсы/минусы AutoCAD .NET API...

Минусы:
  • Код нужно компилировать под каждую версию AutoCAD (не путать понятие "версии" и "вертикального решения"). Т.е. компилировать придётся отдельно под 2009-й, 2010-й, 2011-й и т.д. AutoCAD. Как правило, это занимает не так уж и много времени (но всё же занимает). Например, у меня решение из десятка проектов компилируется в течении нескольких секунд. Т.о. перекомпиляция лично для меня не является чем-то страшным (видимо привык уже). Однако для многих LISP программистов данный пункт видится в гораздо более мрачном свете, чем мне.
  • Если в вашем .NET коде будет использоваться код, зависящий от разрядности (x86 или x64) платформы (как правило, это неуправляемый код, написанный не на .NET), то такой .NET плагин вам придётся компилировать под каждую платформу отдельно, вместо того, чтобы компилировать под AnyCPU. Правда, подобные ситуации скорее всего будут встречаться не часто: за несколько лет работы, у меня было всего несколько случаев (не более 5), однако они возможны и поэтому я упоминаю о них. Тут стоит ещё заметить то, что в отличие от .NET, плагины ObjectARX всегда компилируются отдельно под каждую платформу.
  • Компания Autodesk постоянно развивает .NET API (в отличие от AutoLisp и VisualLisp), что порой будет вынуждать вас вносить изменения в свой исходный код, для того, чтобы он успешно компилировался для более новой версии AutoCAD. Как правило, это легко решается за счёт добавления директив препроцессора (я именно так и поступаю), дабы не хранить различные версии исходников под разные версии AutoCAD. С одной стороны не очень приятно вносить временами изменения в код, а с другой - радует то, что .NET API развивается, оптимизируясь и обрастая новым функционалом. Подобного рода изменения приходится вносить в код не намного чаще, чем компилировать плагин под разные платформы (но всё же чаще).
  • AutoCAD имеет встроенную IDE для написания и тестирования AutoLISP\Visial LISP кода. IDE для .NET и ObjectARX в состав AutoCAD не входят. Необходимо устанавливать IDE отдельно - как правило, это MS Visual Studio.
  • Для того, чтобы писать программный код на .NET языке, этот язык конечно же нужно знать. Соответственно, на изучение придётся потратить какое-то время.
  • В .NET сложно защитить свой исходный код от любопытных глаз др. разработчиков, поскольку сама природа CIL позволяет восстановить исходный .NET код на любой, указанный вами .NET язык. Для защиты .NET кода используют, как правило, обфускаторы (коих для .NET имеется предостаточно), задача которых - делать исходный код нечитабельным и запутанным для постороннего человека.
Как видим, минусов хватает... Теперь давайте посмотрим на плюсы.

Плюсы:
  • Приняв решение задействовать .NET, программист автоматически получает в своё распоряжение всю мощь этой платформы, весь её функционал, а не только различные технологии построения GUI: WinForms и WPF. Т.о. посредством .NET можно с лёгкостью добавлять в AutoLISP любой недостающий функционал, подобно тому, как это было сделано с помощью ActiveX - результат известен как Visual LISP, хотя к LISP это можно отнести весьма условно, поскольку за кулисами основную работу выполняет иной механизм. Функции Visual LISP имеют префикс "vla-, vlax-, vlr-".
  • Изучив какой-либо .NET язык, программист автоматически выходит за рамки программирования только лишь AutoCAD - теперь он совершенно спокойно может писать самостоятельные приложения, службы, разрабатывать сайты и т.п. Кроме того, существует высокая вероятность того, что этот код (речь не о плагинах AutoCAD) без перекомпиляции будет работать не только под Windows, но и под Mac OS и Linux, при условии, что для построения GUI использовалась технология WinForms, а не WPF, поскольку платформа Mono существенно покрывает функционал .NET Framework, за небольшим исключением - смотрим здесь.
  • Нередко .NET код работает быстрее, чем код, написанный на AutoLISP. Это обусловлено как реализацией самого AutoLISP, так и рядом ограничений этой версии языка, указанных, например, Tony Tanzillo здесь.
  • Как это ни странно, но один из пунктов, указанный мною в "минусах", является в то же время и "плюсом": компания Autodesk постоянно вкладывает средства в развитие AutoCAD .NET API, ежегодно совершенствуя его. 
  • В мире .NET, в любом выбранном вами .NET языке, вы можете использовать код, написанный в свою очередь на любом другом .NET языке. Совместимость происходит бесшовно, как будто весь код написан на одном и том же языке - том, который выбрали вы. При желании вы с лёгкостью можете даже расширять код, написанный на др. .NET языках за счёт наследования, написания методов расширения и т.п. Т.е. программисты, которые пишут на C#, легко используют в своей работе код, написанный на VB.NET и наоборот - программисты, которые пишут на VB.NET, спокойно работают с кодом, написанным на C#. Это достигается за счёт того, что любой .NET код компилируется в промежуточный код, известный как CIL - это своего рода ассемблер мира .NET. Исходный CIL код легко декомпилировать в исходный код любого .NET языка. Т.о. очень просто перевести исходный код, написанный на VB.NET, на исходный код, написанный на C# (к примеру).
Кстати, на тему плюсов/минусов использования каждого из существующих на сегодняшний день в AutoCAD языков разработки (за исключением D# и JavaScript, которые находятся на стадии тестирования) в своих блогах писали и сотрудники технической поддержки AutoCAD, известной как ADN - здесь. По ссылке указана первая из цикла статей. В конце каждой статьи даётся ссылка на следующую. В начале каждой последующей статьи даётся ссылка на предыдущую.
Т.о. есть над чем задуматься: весомых аргументов "за" и "против" использования .NET хватает с обоих сторон. Что в конечном счёте имеет перевес (если вообще имеет) - это уж каждый решает для себя сам, исходя из сферы своих интересов, свободного времени, возможностей и прочих факторов. 

Теперь давайте  приступим, собственно, к рассмотрению примеров того, как на .NET можно создавать графические интерфейсы (GUI), предназначенные для их использования из функций AutoLISP и VisualLISP...

WPF

В .NET существует две технологии создания GUI: WinForms и WPF. Первая по сути является управляемой оболочкой над "родным" WinAPI, соответственно со всеми её неудобствами и ограничениями, известными программистам, использовавшими WinAPI для решения указанной задачи. 

Технология WPF - это следующий эволюционный виток в сфере технологий создания GUI. Разница между WinAPI и WPF огромна, как по удобству создания GUI, так и по доступным при этом возможностям, а так же по скорости работы. Например, WinAPI (а следовательно и WinForms) обработку графики осуществляет за счёт CPU, в то время как WPF для этого использует GPU и технологию DirectX. WPF использует для построения GUI не растровую, а векторную графику. Это означает, что изображения всегда будут чёткими, не зависимо от того, сколь сильно бы не выполнялось увеличение элементов. WPF так же поддерживает широкий спектр трансформаций элементов и использование различных кистей для заливок.

Разработкой WPF занимались те же программисты, которые создали WinForms. При этом они постарались заранее учесть и устранить все те минусы, которые были присущи WinForms. Они постарались создать платформу, в которой построение GUI было бы настолько простым, насколько просто создавать HTML странички - и им это очень хорошо удалось. WPF обладает простотой HTML и в то же время тем богатством возможностей, которые отсутствуют в  WinForms и в Web дизайне. Например, в WPF вы можете с лёгкостью создавать не только 2D, но и 3D интерфейсы, если в этом возникнет необходимость. В WPF всё ограничивается лишь вашей фантазией - это огромный шаг в сфере разработки GUI.

Для тех, кого в дальнейшем заинтересует технология WPF, я могу порекомендовать следующие книги:
  • "Основы Windows Presentation Foundation" - Крис Андерсон (один из архитекторов WPF).
  • "Windows Presentation Foundation в .NET 4.0 с примерами на C# 2010" - Мэтью Мак-Дональд.
  • "Microsoft Windows Presentation Foundation. Базовый курс" - Чарльз Петцольд.

В технологию WPF изначально положен принцип разделения работ по программированию и по проектированию дизайна: разработку GUI могут выполнять профессиональные дизайнеры, не имеющие каких-либо навыков программирования, в то время как код, который должен связывать объектную модель с GUI пишут программисты. Затем всё это бесшовно компонуется и работает как единое целое. Вся структура WPF пронизана идеями привязок данных, шаблонов, стилей, анимации, мультимедиа и т.п. Разработчик обладает полным контролем над тем, как должны вести себя элементы окна при изменении его размеров - проблемы по данной теме, порой характерные для WinForms, не актуальны для WPF. 

Для разработки графического интерфейса по технологии WPF существуют графические редакторы как в самой MS Visual Studio, так и в специализированных, более функциональных приложениях, таких как целый пакет инструментов Microsoft Expression Studio, или бесплатный редактор KAXAML.

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

Примечание
Технология WPF реализована только для Windows, реализация по Mac OS и Linux отсутствует в виду её трудоёмкости. Т.о. если одно из требований к разрабатываемому приложению - кроссплатформенность, то свой выбор вам следует остановить на WinForms, т.к. эта технология реализована на всех обозначенных выше ОС.

Примечание 2

Я не ставлю перед собой задачи показать, как с помощью WPF создавать навороченные диалоги, которых нельзя создать в DCL. Наоборот, указанными примерами я хочу показать, как в WPF делается то, что обычно делается в DCL, для сопоставления. XAML разметка занимает больше места, чем DCL, но она даёт и больше возможностей в плане контроля над размещением, поведением, отображением и т.п. Всплывающие подсказки, вкладки, редакторы таблиц и т.п. - всё это легко создавать на WPF. В принципе можно и такой пример сделать, но разметка будет уже посложнее - именно этого я хочу избежать, чтобы как можно меньше грузить тех, кто не знаком с WPF.


WPF и AutoLISP\Visual LISP

AutoCAD .NET API предоставляет разработчику возможность создания дополнительных функций, которые могут быть использованы программистами AutoLISP\Visial LISP в своих приложениях.

Согласно документации, такие функции должны в качестве параметра принимать экземпляр типа ResultBuffer, а возвращать - либо ResultBuffer, либо TypedValue. ResultBuffer - это класс, структура которого идентична спискам в AutoLISP\Visial LISP, а TypedValue - это конкретное значение определённого типа.

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

Если AutoLisp\Visial LISP программист решит писать диалоговые окна с помощью .NET, то возможны следующие варианты развития событий (либо их комбинация):
  1. Создать библиотеку (DLL файл), определяющую в своём составе общие диалоговые окна, которые могут быть использованы любой функцией, написанной на AutoLISP\Visial LISP. Причём определяя эти диалоги, вы сразу можете упаковывать в эту функцию и логику поведения таких окон.
  2. Создавать библиотеки, содержащие в себе диалоговые окна, заточенные для решения задач конкретного приложения. Т.е. возможно, что более эти окна нигде не понадобятся, а будут использоваться лишь в одной программе.
  3. Создать библиотеку, в которой реализовать общую логику работы диалоговых окон, в составе которых должен содержаться чётко определённый набор элементов под определёнными именами (помимо прочих). Такой способ позволит работать с произвольным набором диалоговых окон, определённых не в составе этой библиотеки, но поставляемых в виде текстовых XAML файлов. Т.е. логика работы окна определяется в DLL, а визуализация окон определяется XAML разметкой, которую в любой момент можно изменить по своему усмотрению даже в обычном Блокноте.
Откровенно говоря, я не вижу особой нужды в 3-м варианте, но поскольку такая возможность существует, то упоминаю и его. На самом деле этот вариант легко реализовать в п.1 и п.2 с помощью шаблонов и стилей. Однако в данной статье я не буду их демонстрировать, дабы не грузить и без того уже загруженных AutoLisp\Visial LISP программистов.

Поскольку п.2 является частным случаем п.1, то далее я покажу примеры реализации для пунктов п.1 и п.3. Примеры постарался написать как можно проще, порой жертвуя лаконичностью в пользу наглядности, а так же максимально подробно снабжая исходный программный код комментариями. Итак, начнём...

Пример №1
В этом примере давайте напишем такую LISP функцию, которая должна открывать диалоговое окно со списком, в котором перечислен набор различных значений. Пользователь должен выбрать нужные значения, которые и будут возвращены функцией в виде списка. Нюанс в нашей задачке будет в том, что диалоговые окна, открываемые нашей функцией не определены в самой библиотеке, а считываются во время работы из внешних XAML файлов. Причём мы будем указывать, из какого именно XAML файла следует читать диалоговое окно в каждом конкретном случае. Т.о. в случае необходимости мы всегда сможем откорректировать содержимое наших XAML файлов, например в программе Notepad++, обеспечивающей подсветку XML синтаксиса, либо создать новые XAML файлы и разместить их в нужном месте. Пример очень простой, и возможно не самый лучший, но решение своей задачи - формирования диалоговых окон на основе внешней XAML разметки, он демонстрирует на мой взгляд довольно наглядно. Функция будет принимать в качестве параметров два списка: 
  • Первый содержит в себе настройки диалогового окна
  • Второй содержит перечень значений, из которого пользователь должен сформировать свой выбор.

Примечание
Вообще в XAML разметке имеется возможность размещать и произвольный программный код, помещая его в качестве значения атрибута x:Code, но проблема в том, что это будет работать лишь в том случае, когда XAML разметка ассоциируется с частичным классом и будет компилироваться в BAML код, а не сохраняться в виде внешнего XAML файла, как мы будем это делать в данном примере. Это позволяет размещать в XAML необходимую нам логику работы и регистрацию нужных событий. Однако компания Microsoft не рекомендует пользоваться данным атрибутом, поскольку это нарушает основной принцип разделения труда: XAML код должны писать дизайнеры, а программный код - программисты. Хотя на мой взгляд было бы очень удобно, если бы обозначенный выше атрибут работал и в нашем случае - это позволило бы нам полностью избавиться от фиксированного набора правил, которые я укажу чуть ниже...

Итак, давайте придумаем правила, которым должны соответствовать все создаваемые нами XAML файлы, подлежащие обработке через нашу функцию:
  1. Каталог, в котором содержится наш DLL файл, содержит в себе так же и подкаталог xaml, в котором и следует размещать все наши XAML файлы.
  2. Корневым элементом в каждом таком XAML файле у нас должен быть элемент Window.
  3. В XAML разметке обязательно должны иметься следующие элементы (не важно где именно и на каком уровне вложенности):
    - Кнопка (объект Button) с именем btnExit.
    - Кнопка (объект Button) с именем btnAccept.
    - Текстовый блок (объект TextBlock) с именем txtNotes.
    - Список (объект ListBox) с именем listbox.
    - Объект GroupBox с именем listTitle.
  4. Все элементы, перечисленные в п.3 должны быть объявлены с модификатором доступа public, т.е. иметь в своём составе атрибут x:FieldModifier, значению которого присвоено значение public. Это необходимо для того, чтобы к данным элементам был доступ извне.
  5. Функция, создающая экземпляры Window, описанные нами в XAML разметке, должна иметь следующую сигнатуру:
    (bushman:select-items-window '(XamlFileName WindowTitle ListBoxTitle Notes T/nil) '(СписокЭлементов/nil))
    Где:
       XamlFileName - имя внешнего XAML файла, содержащего в себе разметку окна.
       WindowTitle - текст, который должен отображаться в заголовке окна.
       ListBoxTitle - текст, который должен отображаться над списком элементов.
       Notes - текст примечаний. Если текст не нужен, то в качестве значения следует    передавать nil.
       T/nil -T или nil. Если T, то разрешить пользователю выбирать более одного элемента.   Если nil, то пользователь одновременно может выбрать только один элемент.
       СписокЭлементов/nil - это то, что должно отображаться в списке диалогового окна (список, или nil, хотя передавать nil вряд ли имеет смысл). На основе этого списка пользователь будет делать свой выбор, который и будет возвращён функцией. Если выбор не был произведён - возвращаем список из одного элемента - nil.
Для начала, с помощью XAML разметки напишем пару диалоговых окошек. 

Первое окно:

<Window      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="400" d:DesignWidth="250"
             WindowStartupLocation="CenterScreen"
             ResizeMode="NoResize" Title="Окошко" Width="250" Height="400">
    <Grid>
        <!--Настраиваем сетку-->
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <!--Теперь по сетке раскидываем контролы-->
        <GroupBox x:Name="listTitle"  Header="Список элементов:" Margin="5"
                  Padding="5">
            <!--В объекте ListBox указываем, что источником данных для элементов
            списка следует использовать свойство DataContext родительского элемента-->
            <ListBox x:Name="listbox"
                     ItemsSource="{Binding}" SelectionMode="Multiple">
                <!--Шаблон отображения элементов списка-->
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <!--Указываем, что каждый элемент следует отображать
                        в виде текста, который должен считываться из свойства "Value"-->
                        <TextBlock Text="{Binding Path=Value}"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </GroupBox>
        <GroupBox Header="Примечание:" Grid.Row="1" Margin="5" Padding="5">
            <TextBlock x:Name="txtNotes"  TextWrapping="Wrap"/>
        </GroupBox>
        <StackPanel Orientation="Horizontal" Grid.Row="2"
                    HorizontalAlignment="Right">
            <Button x:Name="btnAccept"  Margin="5"
                    Padding="5" IsDefault="True">Принять</Button>
            <Button x:Name="btnExit"  Margin="5"
                    Padding="5" IsCancel="True">Выход</Button>
        </StackPanel>
    </Grid>
</Window>

Сохраним указанную выше XAML разметку в текстовый файл SelectItemsWindow.xaml, указав в качестве кодировки UTF-8. Результат выглядит следующим образом:


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

Второе окно:


<Window      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="400" d:DesignWidth="250"
             WindowStartupLocation="CenterScreen"
             ResizeMode="NoResize" Title="Окошко" Width="500" Height="400">
    <Window.Background>
        <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
            <GradientStop Offset="0" Color="Azure"/>
            <GradientStop Offset="1" Color="Green"/>
        </LinearGradientBrush>
    </Window.Background>
    <Grid>
        <!--Настраиваем сетку-->
        <Grid.RowDefinitions>
            <RowDefinition Height="20*"/>
            <RowDefinition Height="80*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <!--Теперь по сетке раскидываем контролы-->
        <GroupBox x:Name="listTitle"  Header="Список элементов:" Margin="5"
                  Padding="5" Grid.Row="1">
            <!--В объекте ListBox указываем, что источником данных для элементов
            списка следует использовать свойство DataContext родительского элемента-->
            <ListBox x:Name="listbox"
                     ItemsSource="{Binding}" SelectionMode="Multiple">
                <!--Шаблон отображения элементов списка-->
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <!--Указываем, что каждый элемент следует отображать
                        в виде текста, который должен считываться из свойства "Value"-->
                        <TextBlock Text="{Binding Path=Value}"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </GroupBox>
        <GroupBox Header="Подсказка:" Grid.Row="0" Margin="5" Padding="5">
            <TextBlock x:Name="txtNotes"  TextWrapping="Wrap"/>
        </GroupBox>
        <StackPanel Orientation="Horizontal" Grid.Row="2"
                    HorizontalAlignment="Right">
            <Button x:Name="btnAccept"  Margin="5"
                    Padding="5" IsDefault="True">Принять</Button>
            <Button x:Name="btnExit"  Margin="5"
                    Padding="5" IsCancel="True">Выход</Button>
        </StackPanel>
    </Grid>
</Window>

Сохраним указанную выше XAML разметку в текстовый файл SelectItemsWindow2.xaml, указав в качестве кодировки UTF-8.  Результат выглядит следующим образом:



Оба файла включаем в состав нашего проекта, разместив их в подкаталоге xaml и изменяем для них ряд свойств:
  • Build Action = None
  • Copy To Output Directory = Copy always
Теперь пишем исходный код программы. Я постарался максимально подробно снабдить его комментариями, дабы человек, не знающий .NET не терял "нити повествования" в процессе чтения исходников:

// В .NET все типы определены в пространствах имён:
// либо в глобальном, либо в тех, которые назначит программист.
// Для того, чтобы сослаться на тип, нужно указать его полное имя.
// Например, если класс FileInfo определён в пространстве System.IO,
// то полное имя класса выглядит так: System.IO.FileInfo.
//
// Пространства имён позволяют избавиться от конфликтов наименований типов.
// В одном и том же пространстве имён не могут находиться типы с одинаковым
// именем, а в разных пространствах - могут.
//
// Для того, чтобы в коде программ не писать всё время полные имена типов,
// используют директивы "using", которые записывают в самом начале файла.
// Например, если в начале файла исходного кода записать такую строку:
// using System.IO;
// то теперь нет необходимости указывать полное имя типа и вместо
// "System.IO.FileInfo" можно писать просто "FileInfo".
// В разных пространствах имён могут находиться типы с одинаковым именем.
// Например, пространства System.Windows.Shapes и System.IO содержат тип
// с именем Path. Это два разных типа, но их имена совпадают. Если в коде
// добавить записи:
//
// using System.IO;
// using System.Windows.Shapes;
//
// То при попытке обратиться к типу Path, возникнет неоднозначность и компилятор
// сообщит об этом, сгенерировав ошибку. В данной ситуации обратиться к Path
// можно либо указав его полное имя:
// System.IO.Path или System.Windows.Shapes.Path,
// либо используя псевдонимы пространств имён. Выглядит это так:
// using io = System.IO;
// using shp = System.Windows.Shapes;
//
// В данном случае "ip" и "shp" - произвольно назначенные псевдонимы. Теперь
// обратиться к нужному типу можно с помощью этих псевдонимов (вместо полного имени):
// io.Path или shp.Path.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using shp = System.Windows.Shapes;
using System.Windows.Markup;
using System.Windows.Controls;
using io = System.IO;
using acad = Autodesk.AutoCAD.ApplicationServices.Application;
using AcApp = Autodesk.AutoCAD.ApplicationServices;
using AcDb = Autodesk.AutoCAD.DatabaseServices;
using AcEd = Autodesk.AutoCAD.EditorInput;
using AcRtm = Autodesk.AutoCAD.Runtime;

// Для данной сборки, с помощью глобального атрибута, указываем класс, в
// котором определены команды и LISP функции. Это делаем для того, чтобы
// AutoCAD не тратил время на самостоятельный поиск нужного класса по всей
// сборке(т.е. атрибут необязателен, но желателен).
[assembly: AcRtm.CommandClass(typeof(Lisp_Wpf.Commands))]

// Пространство имён (имя назначаем по своему усмотрению), в котором
// размещаем наш класс
namespace Lisp_Wpf {
    // Класс, содержащий в себе наши команды AutoCAD и LISP функции
    public class Commands {
        // Назначаем единое пространство имён для всех наших LISP функций.
        // Оно позволит исключить конфликт наименований.
        const String myNamespace = "bushman";
        AcDb.ResultBuffer result;

        /// <summary>
        /// Данная LISP функция предназначена для открытия любых XAML
        /// файлов, содержащих в себе описание таких экземпляров класса Window, 
        /// каждое из которых в своей XAML разметке, помимо
        /// прочего, содержит набор элементов определённого типа, с определёнными именами:
        /// 1. Объект Button с именем "btnAccept"
        /// 2. Объект Button с именем "btnExit"
        /// 3. Объект ListBox с именем "listbox"
        /// 4. Объект TextBlock с именем "txtNotes"
        /// 5. Всем перечисленным объектам с помощью атрибута x:FieldModifier
        ///    должен быть назначен модификатор доступа "public" (иначе к ним не
        ///    будет доступа программно извне).
        ///   
        /// Синтаксис функции такой:
        /// (bushman:select-items-window '(XamlFileName WindowTitle ListBoxTitle Notes T/nil)
        /// '(СписокЭлементов/nil))
        ///       
        /// Как видим, функции передаётся два аргумента, каждый из которых является списком.
        /// Первый список содержит настройки отображения диалогового окна:
        ///     XamlFileName - имя внешнего XAML файла, содержащего в себе разметку окна.
        ///     WindowTitle - текст, который должен отображаться в заголовке окна.
        ///     ListBoxTitle - текст, который должен отображаться над списком элементов.
        ///     Notes - текст примечаний. Если текст не нужен, то в качестве значения
        ///     следует передавать nil.
        ///     T/nil -T или nil. Если T, то разрешить пользователю выбирать более одного элемента.
        ///         Если nil, то пользователь одновременно может выбрать только один элемент.
        ///     СписокЭлементов/nil - это то, что должно отображаться в списке диалогового окна (список, или nil,
        ///     хотя передавать nil вряд ли имеет смысл).
        ///     На основе этого списка пользователь будет делать свой выбор, который и
        ///     будет возвращён функцией.
        ///    
        ///     Примеры вызова LISP функции:
        /// (bushman:select-items-window '("SelectItemsWindow.xaml" "Выбор сотрудников" "Список сотрудников:"
        /// "Укажите сотрудников вашего отдела." T) '("Вася" "Петя" "Коля" "Паша" "Маша" "Глаша" "Даша" "Саша"))
        ///
        /// (bushman:select-items-window '("SelectItemsWindow2.xaml" "Выбор начальника" "Список сотрудников:"
        /// "Укажите начальника вашего отдела." nil) '("Вася" "Петя" "Коля" "Паша" "Маша" "Глаша" "Даша" "Саша"))
        ///
        /// </summary>
        /// <param name="buffer">Параметры настройки диалогового окна, а так же данные,
        /// для заполнения списка в окне</param>
        /// <returns>Выбранные элементы возвращаются в виде списка. Если выбор не был
        /// произведён - возвращаем список из одного элемента - nil</returns>
        [AcRtm.LispFunction(myNamespace + ":select-items-window")]
        public AcDb.ResultBuffer SelectItemsFromListView(AcDb.ResultBuffer buffer) {
            result = default(AcDb.ResultBuffer);

            // Сначала проверяем, что передано в качестве параметров: должно быть два списка,
            // первый из которых содержит 5 строк (или 4 строки и nil) и одно число)...

            // Преобразую информацию в перечислитель типа TypedValue - так с ним будет проще работать
            IEnumerable<AcDb.TypedValue> _buffer = buffer.Cast<AcDb.TypedValue>();
            // Проверяю, что имеется две открывающиеся и две закрывающиеся скобки. Это конечно не
            // вариант проверки, т.к. скобки могут быть и вложенными, но для нашего примера пойдёт и такой...
            if (_buffer.Count(n => n.TypeCode == 5016) != 2 ||
                _buffer.Count(n => n.TypeCode == 5017) != 2)
                return result;

            // Получаем элементы первого списка: пропускаем начальные открывающиеся скобки (у нас одна) и
            // читаем всё вплоть до первой закрывающейся
            IEnumerable<AcDb.TypedValue> firstList = _buffer.SkipWhile(n => n.TypeCode == 5016)
                .TakeWhile(n => n.TypeCode != 5017);

            // Если количество аргументов не равно 5 - возвращаем nil.
            if (firstList.Count() != 5)
                return result;

            // Если последний аргумент не T и не nil - возвращаем nil.
            if ((firstList.Skip(4).First().TypeCode != 5021 && firstList.Skip(4).First().TypeCode != 5019))
                return result;

            // Определяем режим выбора элементов списка: одиночный или множественный
            SelectionMode mode = firstList.Skip(4).First().TypeCode == 5021 ?
                SelectionMode.Extended : SelectionMode.Single;

            // Считываем значения прочих параметров
            String xamlFileName = firstList.First().Value as String; // Имя XAML файла
            String winTitle = firstList.Skip(1).First().Value as String; // Текст заголовка окна
            String listBoxTitle = firstList.Skip(2).First().Value as String; // Текст заголовка над списком элементов

            AcDb.TypedValue _notes = firstList.Skip(3).First(); // Элемент списка, содержащий в себе примечание
            String notes = _notes.TypeCode == 5019 ? null : _notes.Value as String; // Текст примечания

            // Второй список может быть и пустым, хотя в этом нет смысла, т.к. пользователю будет
            // не из чего выбирать...
            // Получаем элементы второго списка:
            // пропускаем элементы первого списка, затем пропускаем два элемента закрывающейся и
            // открывающейся скобки, после чего читаем всё до первой закрывающейся скобки
            IEnumerable<AcDb.TypedValue> secondList = _buffer.SkipWhile(n => n.TypeCode != 5017)
                .Skip(2).TakeWhile(n => n.TypeCode != 5017);

            // С параметрами разобрались, теперь приступаем к выполнению указанной задачи...

            // Определяем каталог, в котором находится текущий DLL файл.
            String curDir = io.Path.GetDirectoryName(typeof(Lisp_Wpf.Commands).Assembly.Location);

            // Нужные XAML файлы будем искать в подкаталоге "xaml".
            String fileFullName = io.Path.Combine(curDir, "xaml", xamlFileName);

            // Считываем содержимое XAML файла
            // Все элементы в WPF наследуются от  DependencyObject
            DependencyObject depObj = default(DependencyObject);
            // Читаем содержимое XAML файла в поток
            using (io.FileStream fs = new io.FileStream(fileFullName, io.FileMode.Open)) {
                depObj = XamlReader.Load(fs) as DependencyObject;
                fs.Close();// Закрываем поток
            }

            // Если удалось прочитать содержимое, проверяем, является ли результат объектом Window
            if (depObj != null && depObj is Window) {
                // Если является - выполняем приведение к нужному типу
                Window win = (Window)depObj;
                win.Title = winTitle; // Назначаем окну нужный заголовок

                // Передаём второй параметр (список со значениями) в качестве значения для
                // свойства DataContext нашего окна
                win.DataContext = secondList;

                // Ищем объекты нужного нам типа с нужным именем, дабы выполнить
                // регистрацию соответствующих событий...

                // Кнопка выхода
                Button btnExit = win.FindName("btnExit") as Button;
                // ссылку на окно сохраняем в DataContext, чтобы иметь возможность
                // закрыть окно в методе btnExit_Click.
                btnExit.DataContext = win;
                if (btnExit != null)
                    btnExit.Click += btnExit_Click;

                // Кнопка принятия изменений
                Button btnAccept = win.FindName("btnAccept") as Button;
                // ссылку на окно сохраняем в DataContext, чтобы иметь возможность
                // закрыть окно в методе btnExit_Click.
                btnAccept.DataContext = win;
                if (btnAccept != null)
                    btnAccept.Click += btnAccept_Click;

                TextBlock _text = win.FindName("txtNotes") as TextBlock;
                _text.Text = notes;

                ListBox listbox = win.FindName("listbox") as ListBox;
                listbox.SelectionMode = mode;

                GroupBox listTitle = win.FindName("listTitle") as GroupBox;
                listTitle.Header = listBoxTitle;

                // Открываем окно модальным
                acad.ShowModalWindow(win);
                // Или немодальным, если нужно:
                // acad.ShowModelessWindow(win);
            }

            // Если выбор не был произведён - возвращаем список из одного элемента - nil
            if (result == null || result.Cast<AcDb.TypedValue>().Count() == 0)
                result = new AcDb.ResultBuffer(new AcDb.TypedValue(5019));
            return result;
        }

        // Код, который будет выполняться при нажатии кнопки "Принять"
        public void btnAccept_Click(object sender, RoutedEventArgs e) {
            // Закрываем окно
            FrameworkElement element = (FrameworkElement)sender;
            Window win = element.DataContext as Window;
            win.Close();

            // Формируем список из выбранных элементов и возвращаем его
            // вызывающей LISP функции
            ListBox listbox = win.FindName("listbox") as ListBox;

            var selectedItems = listbox.SelectedItems;
            if (selectedItems == null)
                return;
            result = new AcDb.ResultBuffer();
            foreach (var item in selectedItems)
                result.Add((AcDb.TypedValue)item);
        }

        // Код, который будет выполняться при нажатии кнопки "Выход"
        public void btnExit_Click(object sender, RoutedEventArgs e) {
            // Просто закрываем окно
            FrameworkElement element = (FrameworkElement)sender;
            Window win = element.DataContext as Window;
            win.Close();
        }
    }
}

Теперь, скомпилировав этот код и загрузив его в AutoCAD с помощью команды _NETLOAD, мы можем воспользоваться LISP функцией bushman:select-items-window. Оба созданные нами выше XAML файлы соответствуют тем требованиям, которые были сформулированы для нашей функции. В подкаталоге xaml мы можем разместить сколько угодно дополнительных XAML файлов, соответствующих тем же требованиям, обозначенным выше - наша функция будет работать и с ними. Давайте запустим пару раз написанную нами функцию... 

(bushman:select-items-window '("SelectItemsWindow.xaml" "Выбор сотрудников" "Список сотрудников:" "Укажите сотрудников вашего отдела." T) '("Вася" "Петя" "Коля" "Паша" "Маша" "Глаша" "Даша" "Саша"))

В результате откроется диалоговое окно, которое будет выглядеть так:


На скрине видно, что я выбрал три элемента (удерживая клавишу Ctrl). Это возможно благодаря тому, что в списке параметров диалогового окна я указал значение T, разрешая тем самым множественный выбор. Нажав на кнопку Принять, мы получаем такой результат:


Теперь давайте нашей функции укажем другой XAML файл и запретим множественный выбор элементов:

(bushman:select-items-window '("SelectItemsWindow2.xaml" "Выбор начальника" "Список сотрудников:" "Укажите начальника вашего отдела." nil) '("Вася" "Петя" "Коля" "Паша" "Маша" "Глаша" "Даша" "Саша"))

В результате откроется диалоговое окно, которое будет выглядеть так:



Как видим - открылось другое диалоговое окошко. Кроме того, я могу выбрать в списке только одно значение, т.к. в этот раз вместо T в функцию был передан nil. Нажав на кнопку Принять я получаю следующий результат:


Пример №2

В этом примере наше диалоговое окошко будет включено в состав DLL файла при компиляции, в то время как в предыдущем примере оно определялось разметкой внешнего XAML файла.

Делаю диалоговое окошко, аналогичное этому. Идея в том, чтобы диалоговое окошко считывало свои настройки из внешнего файла и сохраняло их обратно. Тем самым окно будет запоминать своё состояние между сессиями работы. Откровенно говоря, само назначение приложения, для которого я пишу пример окна, мне не нравится, поскольку в организации, в котороя я работаю, пишу инструменты как раз противоположные - выявляющие подобного рода "халтурки" :) . Ну да ладно...

Сначала пишу XAML разметку:


<Window x:Class="Lisp_WPF.HalturkaWindow"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="250" d:DesignWidth="250" Title="Подгонометрия"
        SizeToContent="WidthAndHeight" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <GroupBox Header="Диапазон:" Margin="3">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Label>_От</Label>
                <TextBox Grid.Column="1" Margin="3" x:Name="from" x:FieldModifier="public"/>
                <Label Grid.Column="2">_До</Label>
                <TextBox Grid.Column="3" Margin="3" x:Name="to" x:FieldModifier="public"/>
            </Grid>
        </GroupBox>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <GroupBox Margin="3" Padding="3">
                <StackPanel>
                    <RadioButton x:Name="modify" x:FieldModifier="public">_Изменить</RadioButton>
                    <RadioButton x:Name="replace" x:FieldModifier="public">_Заменить</RadioButton>
                </StackPanel>
            </GroupBox>
            <GroupBox Margin="3" Padding="3" Grid.Column="1">
                <StackPanel>
                    <RadioButton x:Name="value" x:FieldModifier="public">Зна_чение</RadioButton>
                    <RadioButton x:Name="coordinates" x:FieldModifier="public">Коо_рдинаты</RadioButton>
                </StackPanel>
            </GroupBox>
        </Grid>
        <StackPanel Grid.Row="2" Margin="5">
            <CheckBox Margin="3" x:Name="plus" x:FieldModifier="public"
                      IsEnabled="{Binding ElementName=value, Path=IsChecked}">
                З_нак "+" у положительных</CheckBox>
            <CheckBox Margin="3" x:Name="textWithNumberOnly" x:FieldModifier="public"
                      IsEnabled="{Binding ElementName=value, Path=IsChecked}">
                _Только текст содержащий число</CheckBox>
            <CheckBox Margin="3" x:Name="amountMonitoring" x:FieldModifier="public"
                      IsEnabled="{Binding ElementName=value, Path=IsChecked}">
                Контроль суммы раз_меров</CheckBox>
        </StackPanel>
            <StackPanel Grid.Row="3" HorizontalAlignment="Right" Orientation="Horizontal">
            <Button Margin="3" Padding="3" IsDefault="True" x:Name="btnAccept"
                    x:FieldModifier="public" Click="btnAccept_Click">_Применить</Button>
            <Button  Margin="3" Padding="3" IsCancel="True" x:Name="btnCancel"
                     x:FieldModifier="public">Отм_ена</Button>
        </StackPanel>
    </Grid>
</Window>


Результат выглядит так:


Теперь для этого окна пишу частичный класс, код которого будет объединён с кодом XAML разметки при компиляции:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using AcDb = Autodesk.AutoCAD.DatabaseServices;

namespace Lisp_WPF {
    /// <summary>
    /// Interaction logic for HalturaWindow.xaml
    /// </summary>
    public partial class HalturkaWindow : Window {

        public HalturkaWindow() {
            InitializeComponent();
        }

        private void btnAccept_Click(object sender, RoutedEventArgs e) {
            Double _from, _to;
            if (!Double.TryParse(from.Text, out _from) || !Double.TryParse(to.Text, out _to)) {
                MessageBox.Show("В качестве значений полей \"От\" и \"До\" следует указывать" +
                    " целые или десятичные числа.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            if (_from > _to) {
                MessageBox.Show("Значение \"От\" не должно быть больше значения \"До\".",
                    "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            AcDb.TypedValue tv_from = new AcDb.TypedValue(5001, _from);
            AcDb.TypedValue tv_to = new AcDb.TypedValue(5001, _to);

            AcDb.TypedValue tv_change = default(AcDb.TypedValue);
           
            //****
            if (modify.IsChecked == true)
                tv_change = new AcDb.TypedValue(5021, true);
            else
                tv_change = new AcDb.TypedValue(5019);

            //****
            AcDb.TypedValue tv_coordinates = default(AcDb.TypedValue);

            if (coordinates.IsChecked == true)
                tv_coordinates = new AcDb.TypedValue(5021, true);
            else
                tv_coordinates = new AcDb.TypedValue(5019);

            //****
            AcDb.TypedValue tv_plus = default(AcDb.TypedValue);

            if (plus.IsChecked == true)
                tv_plus = new AcDb.TypedValue(5021, true);
            else
                tv_plus = new AcDb.TypedValue(5019);

            //****
            AcDb.TypedValue tv_textWithNumberOnly = default(AcDb.TypedValue);

            if (textWithNumberOnly.IsChecked == true)
                tv_textWithNumberOnly = new AcDb.TypedValue(5021, true);
            else
                tv_textWithNumberOnly = new AcDb.TypedValue(5019);

            //****
            AcDb.TypedValue tv_amountMonitoring = default(AcDb.TypedValue);

            if (amountMonitoring.IsChecked == true)
                tv_amountMonitoring = new AcDb.TypedValue(5021, true);
            else
                tv_amountMonitoring = new AcDb.TypedValue(5019);

            //****
            AcDb.ResultBuffer result = new AcDb.ResultBuffer(
                tv_from, tv_to, tv_change, tv_coordinates, tv_plus,
                tv_textWithNumberOnly, tv_amountMonitoring
                );

            DataContext = result;
            Close();
        }
    }
}

Пишем XML файл настроек (HalturkaSettings.xml):

<?xml version="1.0" encoding="utf-8" ?>
<!--Настройки диалогового окна, сохраняемые между сессиями работы.-->
<Settings>
  <!--От-->
  <From>0</From>
  <!--До-->
  <To>0</To>
  <!--True - изменить; False - заменить-->
  <Change>True</Change>
  <!--True - координаты; False - значение-->
  <Coordinates>True</Coordinates>
  <!--Знак "+" у положительных (True/False)-->
  <Plus>False</Plus>
  <!--Только текст, содержащий число (True/False)-->
  <TextWithNumberOnly>False</TextWithNumberOnly>
  <!--Контроль суммы (True/False)-->
  <AmountMonitoring>False</AmountMonitoring>
</Settings>



Теперь пишем основной код функции:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
// AutoCAD namespaces
using acad = Autodesk.AutoCAD.ApplicationServices.Application;
using AcApp = Autodesk.AutoCAD.ApplicationServices;
using AcDb = Autodesk.AutoCAD.DatabaseServices;
using AcEd = Autodesk.AutoCAD.EditorInput;
using AcRtm = Autodesk.AutoCAD.Runtime;
using System.Xml.Linq;

[assembly:AcRtm.CommandClass(typeof(Lisp_WPF.Dialogs))]

namespace Lisp_WPF {
    public class Dialogs {
        const String ns = "mrX";
        /// <summary>
        /// Открыть диалоговое окно "Подгонометрии"
        /// Вызов LISP функции: (mrX:get-halturka-parameters)
        /// В качестве результата возвращается либо список из 7-ми параметров окна:
        ///
        /// 1-й - значение "От"
        /// 2-й - значение "До"
        /// 3-й - значение "Изменить" (T/nil)
        /// 4-й - значение "Координаты" (T/nil)
        /// 5-й - значение "Знак + у положительных" (T/nil)
        /// 6-й - значение "Только текст содержащий число" (T/nil)
        /// 7-й - значение "Контроль суммы размеров" (T/nil)
        ///
        /// либо nil, если в диалоговом окне пользователь нажмёт кнопку "Отмена"
        /// </summary>      
        [AcRtm.LispFunction(ns + ":get-halturka-parameters")]
        public AcDb.ResultBuffer OpenHalturkaWindow(AcDb.ResultBuffer parameter) {
            AcDb.ResultBuffer result = default(AcDb.ResultBuffer);

            // Читаем настройки из XML файла. Если что-то не так - прерываем работу
            // функции и возвращаем nil.
            String curDir = Path.GetDirectoryName(typeof(Lisp_WPF.Dialogs)
                .Assembly.Location);
            String xmlFileName = "HalturkaSettings.xml";
            String fullName = Path.Combine(curDir, xmlFileName);

            if (!File.Exists(fullName))
                return null;

            XElement xml;
            try {
                xml = XElement.Load(fullName);
            }
            catch {
                return null;
            }

            if (xml == null)
                return null;

            // Дабы сократить объём кода, я не буду проверять наличие необходимых XML элементов,
            // а исходя из предположения, что они существуют, буду сразу читать их значения. В
            // реальных задачах эту проверку конечно же следует выполнять.

            Double from;

            if (!Double.TryParse(xml.Element("From").Value.Trim(), out from))
                return null;

            Double to;
            if (!Double.TryParse(xml.Element("To").Value.Trim(), out to))
                return null;

            Boolean isChange;

            if (!Boolean.TryParse(xml.Element("Change").Value.Trim(), out isChange))
                return null;

            Boolean isCoordinates;

            if (!Boolean.TryParse(xml.Element("Coordinates").Value.Trim(),
                out isCoordinates))
                return null;

            Boolean isPlus;

            if (!Boolean.TryParse(xml.Element("Plus").Value.Trim(), out isPlus))
                return null;

            Boolean isTextWithNumberOnly;

            if (!Boolean.TryParse(xml.Element("TextWithNumberOnly").Value.Trim(),
                out isTextWithNumberOnly))
                return null;

            Boolean isAmountMonitoring;

            if (!Boolean.TryParse(xml.Element("AmountMonitoring").Value.Trim(),
                out isAmountMonitoring))
                return null;

            //Создаём объект окна и инициализируем его нужными значениями
            HalturkaWindow win = new HalturkaWindow();

            win.from.Text = from.ToString();
            win.to.Text = to.ToString();

            if (isChange)
                win.modify.IsChecked = true;
            else
                win.replace.IsChecked = true;

            if (isCoordinates)
                win.coordinates.IsChecked = true;
            else
                win.value.IsChecked = true;

            win.plus.IsChecked = isPlus;
            win.textWithNumberOnly.IsChecked = isTextWithNumberOnly;
            win.amountMonitoring.IsChecked = isAmountMonitoring;

            // Открываем окно
            acad.ShowModalWindow(win);

            // Считываем значения настроек в виде списка
            result = win.DataContext as AcDb.ResultBuffer;

            // Если пользователь изменил значения - сохраняем их в XML файл
            if (win.DataContext != null) {
                xml.Element("From").Value = win.from.Text;
                xml.Element("To").Value = win.to.Text;
                xml.Element("Change").Value = (win.modify.IsChecked == true).ToString();
                xml.Element("Coordinates").Value =
                    (win.coordinates.IsChecked == true).ToString();
                xml.Element("Plus").Value = (win.plus.IsChecked == true).ToString();
                xml.Element("TextWithNumberOnly").Value =
                    (win.textWithNumberOnly.IsChecked == true).ToString();
                xml.Element("AmountMonitoring").Value =
                    (win.amountMonitoring.IsChecked == true).ToString();
                xml.Save(fullName);
            }
            // Возвращаем список с настройками
            return result;
        }
    }
}


Всё... Теперь запустим написанную нами выше функцию пару раз и посмотрим результаты...

Запускаем и вносим изменения в настройки:


Результат:

Снова запускаем и вносим изменения в настройки (обратите внимание на то, что выбор опции "Координаты" делает недоступными три флажка, расположенных внизу окна):



Результат:

Значения всех настроек сохраняются между сессиями работы в файле HalturkaSettings.xml.

Заключение

Если уж AutoLISP\Visual LISP программист и решит взяться за изучение .NET, то на мой взгляд, было бы в корне неправильным рассматривать эту платформу лишь как средство построения GUI для AutoLISP\Visual LISP приложений, поскольку это лишь малая капля того, что можно получить от .NET.

Используя эту платформу, можно написать огромное количество мощных AutoLISP\Visual LISP функций, расширяющих текущую реализацию этого языка. Поскольку компания Autodesk более не финансирует развитие AutoLISP\Visual LISP, то развивать эти языки можно самостоятельно за счёт платформы .NET, особенно учитывая то, что делается это очень просто. Autodesk для расширения функционала AutoLISP использовала технологию ActiveX, а вы в свою очередь можете расширять функционал AutoLISP\Visual LISP за счёт платформы .NET. Это может быть что угодно - на усмотрение программистов, например:
  • LISP функции по работе с подшивками
  • LISP функции по работе с модулями нормоконтроля чертежей
  • LISP функции по работе с другими чертежам, а не только с тем, в контекст которого эта функция была загружена
  • LISP функции по криптографии
  • LISP функции по проверке XML контента на предмет его соответствия указанным XSD схемам
  • LISP функции по работе с внешним оборудованием
  • LISP функции, распараллеливающие на CPU долгие вычисления по разным потокам, сокращая тем самым общее время выполнения задачи
  • LISP функции, запускающие выполнение больших вычислений на GPU, вместо CPU
  • и т.д. и т.п.
Надеюсь, что изложенный выше материал был понятен, интересен и полезен. Исходный код показанных выше примеров, в формате MS Visual Studio 2012 находится здесь.

2 комментария:

Boxa комментирует...

>>AutoCAD имеет встроенную IDE для написания и тестирования AutoLISP\Visial LISP кода. IDE для .NET и ObjectARX в состав AutoCAD не входят. Необходимо устанавливать IDE отдельно - как правило, это MS Visual Studio, причём не бесплатная, т.е. не Express версия.

Не совсем верно.
Для написания ObjectARX действительно нужна полная VS
Для NET вполне подходит Express версия.
Проблема только в отладке, но и это решается парой строчек в .vbproj файле проекта

Andrey Bushman комментирует...

Сейчас точно уже не помню, но много лет назад мне по каким-то причинам пришлось перейти с Express на платный вариант. Чтобы вспомнить в чём дело - это нужно устанавливать Express и пытаться тыкаться в нём в различных направлениях... Ограничения точно были, но сейчас с ходу не вспомню что именно мне не хватило в той версии. Ок, в заметке убрал инфу насчёт "платной", т.к. возможно кому-то хватит и Express...