Введение: Почему обычный код уже не годится?
Если вы уже писали плагины для Revit, то наверняка сталкивались с кодом, где обработка нажатия кнопки, логика работы с элементами Revit и вывод результатов в интерфейс находятся в одном месте. Такой подход работает для простых скриптов, но становится кошмаром при разработке сложных панелей инструментов.
Представьте, что вам нужно:
- Сделать интерфейс динамическим (Например: ProgressBar (индикатор выполнения), автоматическое обновление данных в интерфейсе и т.д. ).
- Создать сложный интерфейс с множеством команд.
Стандартный подход здесь бессилен. На помощь приходит архитектурный паттерн MVVM.
Что такое MVVM?
MVVM — это шаблон проектирования, который разделяет код на три независимые части:Model (Модель) - View (Представление) - ViewModel (Модель Представления)
Ключевая идея: View и Model не знают о существовании друг друга. Весь обмен данными идет через ViewModel.
Анатомия MVVM в контексте Revit
1. Model (Модель)
- Что это? Классы, которые напрямую работают с API Revit. Они содержат методы для создания, модификации, удаления и выборки элементов.
- Важно: Model ничего не знает о существовании интерфейса (View). Её методы принимают простые параметры (длину, уровень, тип семейства) и возвращают результаты или статусы операции.
2. View (Представление)
- Что это? Окно WPF (XAML-разметка), которое видит пользователь. Кнопки, текстовые блоки, выпадающие списки.
- Пример: Окно WindowView.xaml
- Важно: В идеале, в "коде позади" окна (WindowView.xaml.cs) не должно быть никакой логики, кроме инициализации и привязки DataContext (о нем ниже).
3. ViewModel (Модель Представления)
- Что это? . Это посредник между View и Model. Он включает:
- Свойства: Данные для отображения во View (например, WallHeight, SelectedElementType, ProgressValue).
- Команды: Методы, которые вызываются при действиях пользователя (нажатие кнопки "Создать", "Выбрать").
- Уведомления (OnPropertyChanged): Механизм, с помощью которого ViewModel сообщает View: "Эй, данные изменились, обнови интерфейс!".
Связывание всего вместе: Привязка данных (Data Binding) и команды
Магия MVVM происходит благодаря Data Binding (привязке данных) в WPF.
Например:
- Текст TextBox в View привязывается к свойству WallHeight во ViewModel.
- Свойство IsEnabled кнопки привязывается к свойству CanCreateWall во ViewModel.
- Событие Click кнопки привязывается к команде CreateWallCommand во ViewModel.
Когда пользователь меняет текст в TextBox, новое значение автоматически отправляется в свойство WallHeight во ViewModel. Когда ViewModel изменяет значение CanCreateWall с false на true, кнопка автоматически становится активной. Никакого ручного обновления интерфейса!
Шаблон MVVM
Для начала предлагаю доработать базовый шаблон для Revit 2025 (.Net 8.0) для понимания принципа работы.
(Статья по созданию базового: Создание шаблона Revit API 2025(.Net 8.0))
1. Для начала переименуем MyCommand в Main для удобства. Плагин будем запускать через него. Также в App.cs заменим "MyCommand" на "Main"
2. Добавим три папки Model, View, ModelView. В каждой из них будем хранить наши классы, поскольку их может быть много лучше сразу сортировать для удобства.
3. Теперь создадим в папке View - Окно (WPF), дав имя "WindowView"
4. В папке ViewModel создадим два класса MainViewModel и BaseViewModel. ViewModel будет наследовать функционал BaseViewModel. В папке Model создаем класс RevitCommand.
5. Теперь в логическом порядке будем заполнять наши классы. Начнем с класса "Main" именно с него будет запускаться наш плагин. В нем мы создаем окно интерфейса и открываем его. Не обращаем внимание на ошибки по ходу соберём все классы.
6. Далее переходим в класс WindowView.xaml.cs и пишем следующий код. Тут мы инициализируем компоненты, создаем ViewModel, создаем событие на закрытие окна интерфейса, привязываем данные для WindowView.
7. В классе BaseViewModel прописываем следующий код. Событие PropertyChanged будет обновлять интерфейс при изменении значения. CloseRequest, HideRequest, ShowRequest - открывать, скрывать и открывать окно интерфейса.
8. Добавим какую-нибудь метод "GetWallType" в класс RevitCommand.
10. Переходим к WindowView.xaml. Напишем простенький интерфейс для демонстрации в котором будет две кнопки "Найти типы стен" и "Закрыть". Обратите внимание на привязку ListBox и Button к данным, если в первый раз с таки сталкиваетесь то вскоре вы все поймёте после заполнения MainViewModel
9. И переходим к самому главному классу MainViewModel. Для начала устанавливаем библиотеку Prism.Core.
10. В классе MainViewModel записываем следующий код.
Все, шаблон готов!
Теперь давайте разбираться как все работает.
Плагин мы запускаем через класс Main(1). В нем создаем наш View (WindowView)(2) и открываем его. При открывании View (WindowView)(2) создаем нашего посредника ViewModel(MainViewModel)(3). Yаш посредник уже использует методы из Model (RevitCommand)(4).
Наш ViewModel(MainViewModel)(2) наследует методы BaseViewModel. Это сделано так, потому ViewModel и View может быть много. Чтобы всем не прописывать одинаковые методы, создаем базовый класс, в котором все основные функции прописаны, а уже другие ViewModel их наследуют, т.е. ими можно пользоваться .
В ViewModel(MainViewModel)(2) мы создаем делегаты командFindWallTypeCommand и CloseCommand, а также list<WallType> WallsType. Именно эти параметры мы и привязали в View(WindowView)(3)
OnPropertyChanged - будет обновлять интерфейс когда будет меняться значения листа WallsType.
В конструкторе MainViewModel мы передаем commandData и создаем экземпляры делегатов с командами OnFindWallTypeCommand и OnCloseCommand.
Соответственно создали сами команды, которые будут срабатывать при нажатии кнопок. Исполняемые методы, классы мы храним в папке Model (4).
Теперь давайте с помощью Addin manager (https://github.com/chuongmep/RevitAddInManager) протестируем на базовом архитектурном проекте.
При запуске видим такое окно:
После нажатия "Найти типы стен" видим, что наш ListBox, c привязанным к нему WallsType, обновился, поскольку мы добавили туда данные.
Тут уже можно доработать шаблон как вам хочется: убрать лишнее или наоборот добавить методы, события. Например, я добавил событие на скрытие и показ View, которыe не продемонстрировал. Как правило при создании нового проекта лишнее удаляется из шаблона, если оно не понадобится.
Создаем шаблон через панель "Проект"
Таким образом у нас получился структурированный шаблон в котором все лежит по своим местам. С таким шаблоном можно уже создавать сложные плагины не боясь запутаться.
Шаблон можно взять из моего GitHub (https://github.com/BelikAE/TemplateRevitMVVM2025)
Надеюсь у меня получилось объяснить, что такое паттерн MVVM и как его можно применять для написания плагинов под Revit. Пишите в комментарии свои вопросы и какие темы еще разобрать.