ADN Open CIS
Сообщество программистов Autodesk в СНГ

31/07/2013

DocumentChanged VS Dynamic Model Updater

DocumentChanged VS Dynamic Model Updater

Небольшой обзор и сравнение двух способов отслеживания изменений в модели Revit. Revit API предоставляет два различных способа: подписаться на событие DocumentChangedEvent или зарегистрировать DMU dynamic model updater (динамическое обновление модели). После того как вы подписались на событие или зарегистрировали DMU, важно не забыть отписаться от этого события или отменить регистрацию DMU, когда больше нет необходимости отслеживать изменения.

Рассмотрим два способа на примере отображения сообщения, когда добавляется новый вид Фасад здания.

Подписка на событие DocumentChanged для реагирования на добавление нового Фасада

Очень легко определить когда какой-то определенный элемент был добавлен, например новый вид, подписавшись на событие DocumentChanged. Это можно сделать в методе OnStartup при старте Revit,  или при запуске вашей команды. В любом случае, следует иметь ввиду, что отписаться от события нужно в любом случае, после того как оно вам больше не нужно. Это можно сделать в методе OnShutdown, в конце выполнения команды или каким-либо иным способом.

Ниже представлен полный код реализации внешней команды:

Код - C#: [Выделить]
  1.  
  2.     /// <summary>
  3.     /// Реакция на добавление нового вида Фасад
  4.     /// </summary>
  5.     [Transaction(TransactionMode.ReadOnly)]
  6.     public class CmdElevationWatcher : IExternalCommand
  7.     {
  8.         /// <summary>
  9.         /// Возвращает первый вид типа Фасад в переданной коллекции 
  10.         /// Element Id или null если вид не найден
  11.         /// </summary>
  12.         static View FindElevationView(
  13.           Document doc,
  14.           ICollection<ElementId> ids)
  15.         {
  16.             View view = null;
  17.  
  18.             foreach (ElementId id in ids)
  19.             {
  20.                 view = doc.GetElement(id) as View;
  21.  
  22.                 if (null != view
  23.                   && ViewType.Elevation == view.ViewType)
  24.                 {
  25.                     break;
  26.                 }
  27.  
  28.                 view = null;
  29.             }
  30.             return view;
  31.         }
  32.  
  33.         
  34.         public Result Execute(
  35.           ExternalCommandData commandData,
  36.           ref string message,
  37.           ElementSet elements)
  38.         {
  39.             UIApplication uiapp = commandData.Application;
  40.             Application app = uiapp.Application;
  41.  
  42.             // Подписка на событие DocumentChanged
  43.  
  44.             app.DocumentChanged
  45.               += OnDocumentChanged;
  46.  
  47.             return Result.Succeeded;
  48.         }
  49.  
  50.         /// <summary>
  51.         /// Обработка события DocumentChanged
  52.         /// </summary>
  53.         private void OnDocumentChanged(object sender, DocumentChangedEventArgs e)
  54.         {
  55.             Document doc = e.GetDocument();
  56.  
  57.             View view = FindElevationView(
  58.               doc, e.GetAddedElementIds());
  59.  
  60.             if (null != view)
  61.             {
  62.                 string msg = string.Format(
  63.                   "Вы только что создали новый вид Фасад "
  64.                   + " '{0}'.",
  65.                   view.Name);
  66.  
  67.                 TaskDialog.Show("ElevationChecker", msg);
  68.             }
  69.         }
  70.     }
  71.  

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

 

Но такой подход имеет два значительных недостатка:

1)      Он реагирует также на добавление семейства

2)      Производительность метода не очень оптимальна

 

1. Когда семейство загружается в проект, также загружаются и все виды, присутствующие в этом семействе. Соответственно происходит обработка события DCE. Например, если я вставлю стандартное семейство Колонна прямоугольного сечения.rfa, то также увижу сообщение о вставке Фасада:

 

Такое поведение может сбить с толку

2. Производительность.  Событие DocumentChanged реагирует абсолютно на любое изменение модели. Более того, когда мы добавляем любой элемент или элементы, нужно проверить не является ли этот элемент Фасадом.

Оба этих недостатка можно избежать, используя механизм Dynamic Model Updater

Регистрация Dynamic Model Updater для реагирования на добавление нового Фасада

Как я уже сказал, с помощью механизма  DMU можно легко определить какие конкретно элементы были добавлены в модель, и избежать недостатков DCE о которых я упомянул чуть ранее, так как DMU реагирует строго на определенные изменения модели (добавление, изменение, удаление) и на строго определенных набор элементов, который задается при регистрации DMU.

Первым делом нам нужно создать класс, реализующий интерфейс IUpdater, в котором мы будет отображать сообщение при добавлении Фасада.

Код - C#: [Выделить]
  1.  
  2.     /// <summary>
  3.     /// Updater оповещающий пользователя при добавлении фасада.
  4.     /// </summary>
  5.     public class ElevationWatcherUpdater : IUpdater
  6.     {
  7.         static AddInId _appId;
  8.         static UpdaterId _updaterId;
  9.  
  10.         public ElevationWatcherUpdater(AddInId id)
  11.         {
  12.             _appId = id;
  13.  
  14.             _updaterId = new UpdaterId(_appId, new Guid(
  15.                                                    "fafbf6b2-4c06-42d4-97c1-d1b4eb593eff"));
  16.         }
  17.  
  18.         public void Execute(UpdaterData data)
  19.         {
  20.             Document doc = data.GetDocument();
  21.             Application app = doc.Application;
  22.             foreach (ElementId id in
  23.                 data.GetAddedElementIds())
  24.             {
  25.                 View view = doc.GetElement(id) as View;
  26.  
  27.                 if (null != view
  28.                     && ViewType.Elevation == view.ViewType)
  29.                 {
  30.                     TaskDialog.Show("ElevationWatcher Updater",
  31.                                     string.Format("Новый фасад'{0}'",
  32.                                                   view.Name));
  33.                 }
  34.             }
  35.         }
  36.  
  37.         public string GetAdditionalInformation()
  38.         {
  39.             return "The Building Coder, "
  40.                    + "http://thebuildingcoder.typepad.com";
  41.         }
  42.  
  43.         public ChangePriority GetChangePriority()
  44.         {
  45.             return ChangePriority.FloorsRoofsStructuralWalls;
  46.         }
  47.  
  48.         public UpdaterId GetUpdaterId()
  49.         {
  50.             return _updaterId;
  51.         }
  52.  
  53.         public string GetUpdaterName()
  54.         {
  55.             return "ElevationWatcherUpdater";
  56.         }
  57.     }
  58.  

Создадим еще одну внешнюю команду для регистрации нашего Updater’а и зададим триггер для него. Назовем ее CmdElevationWatcherUpdater. Триггер будет реагировать только на добавление нового фасада и больше ни на что.

Код - C#: [Выделить]
  1.  
  2.     [Transaction(TransactionMode.ReadOnly)]
  3.     public class CmdElevationWatcherUpdater : IExternalCommand
  4.     {
  5.         public Result Execute(
  6.             ExternalCommandData commandData,
  7.             ref string message,
  8.             ElementSet elements)
  9.         {
  10.             UIApplication uiapp = commandData.Application;
  11.             Application app = uiapp.Application;
  12.  
  13.             // Register updater to react to view creation
  14.  
  15.             ElevationWatcherUpdater updater
  16.                 = new ElevationWatcherUpdater(
  17.                     app.ActiveAddInId);
  18.  
  19.             UpdaterRegistry.RegisterUpdater(updater);
  20.  
  21.             ElementCategoryFilter f
  22.                 = new ElementCategoryFilter(
  23.                     BuiltInCategory.OST_Views);
  24.  
  25.             UpdaterRegistry.AddTrigger(
  26.                 updater.GetUpdaterId(), f,
  27.                 Element.GetChangeTypeElementAddition());
  28.  
  29.             return Result.Succeeded;
  30.         }
  31.     }

После запуска команды наш Updater зарегистрирован и задан триггер. Как и ранее, после выполнения этой команды, при добавлении Фасада появится сообщение.

 

 

 

Отписка от события DocumentChanged и отмена регистрации Dynamic Model Updater

Для отписки и отмены регистрации я немного подправлю код команд так, что каждый последующий вызов команды подписывает или отменяет подписку на событие или регистрирует или отменяет регистрацию Updater’а

В обоих случаях это очень легко сделать. Я просто добавлю статическое свойство содержащее делегат обработки события или Updater'а и установлю и значение равным null.

Вот так это выглядит для события:

Код - C#: [Выделить]
  1.  
  2.  EventHandler<DocumentChangedEventArgs>
  3.              _handler = null;
  4.  
 

И еще проще для Updater’а

Код - C#: [Выделить]
  1.  
  2. private ElevationWatcherUpdater _updater = null;
  3.  

Каждый раз когда команда выполняется происходит подписка или регистрация и переменным присваивается значение если до этого они были равны null, или происходит отписка от события или отмена регистрации и присваивание переменным null в противном случае.

Ниже обновленный фрагмент кода для события DocumentChanged:

Код - C#: [Выделить]
  1.  
  2.             // Подписка на событие DocumentChanged
  3.             if (_handler == null)
  4.             {
  5.                 _handler =
  6.                     OnDocumentChanged;
  7.  
  8.                 app.DocumentChanged
  9.                     += _handler;
  10.             }
  11.             else
  12.             {
  13.                 //Отписка
  14.                 app.DocumentChanged -=
  15.                     _handler;
  16.                 _handler = null;
  17.  
  18.             }

И соответствующий код для DMU:

Код - C#: [Выделить]
  1.  
  2.             if (_updater == null)
  3.             {
  4.                 _updater
  5.                     = new ElevationWatcherUpdater(
  6.                         app.ActiveAddInId);
  7.  
  8.                 UpdaterRegistry.RegisterUpdater(_updater);
  9.  
  10.                 ElementCategoryFilter f
  11.                     = new ElementCategoryFilter(
  12.                         BuiltInCategory.OST_Views);
  13.  
  14.                 UpdaterRegistry.AddTrigger(
  15.                     _updater.GetUpdaterId(), f,
  16.                     Element.GetChangeTypeElementAddition());
  17.             }
  18.             else
  19.             {
  20.                 UpdaterRegistry.UnregisterUpdater(_updater.GetUpdaterId());
  21.  
  22.                 _updater = null;
  23.             }
  24.  
 

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

 

Дополнение: Как справедливо заметил Виктор, я упустил еще одно важное отличие между DCE и DMU, которое мы уже несколько раз обсуждали ранее.

Обработка события DocumentChanged не возникнет до тех пор, пока транзакция не завершена. Таким образом, нельзя отменить изменения в обработке события. Если элементы были удалены, то в обработке события вы сможете получить только их Id и даже не узнаете что это были за элементы. Updater   же вызывается в момент совершения транзакции и поэтому более контролируем.

 

Демонстрационный проект можно скачать архивом здесь или на GitHub.


Источник: http://thebuildingcoder.typepad.com/blog/2012/06/documentchanged-versus-dynamic-model-updater.html

Обсуждение:  http://adn-cis.org/forum/index.php?topic=122.0

 

 

Опубликовано 31.07.2013
Отредактировано 01.08.2013 в 09:04:48