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

21/03/2015

Событие по изменению выделенного элемента

Много разработчиков интересуются возможностью получить оповещения, когда меняется выделение текущего объекта в пользовательском интерфейсе.

Я уже обсуждал реализацию данной возможности в статье Использования события Idling для отслеживания выделения объектов (на англ.) и одним из ключевых аспектов обсуждения являлись так называемые уровни возникновения события, т.е. различные возможности приложения реагировать на изменения объектов в модели.

Проверка на изменение выделенного элемента с помощью обработки события Idling

Ken Goulding прокомментировал следующее:

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

Как обходное решение, я создал класс, в котором идет подписка на событие Idling и идет проверка на изменение выделенного элемента.

Учитывая, что это событие Idling, я постарался максимально грамотно и эффективно реализовать алгоритм отслеживания изменений, но все же может есть более простой способ?

Вот код класса:

Код - C#: [Выделить]
  1.     public class SelectionChangedWatcher
  2.     {
  3.         public event EventHandler SelectionChanged;
  4.         protected void Call_SelectionChanged() {
  5.             if (SelectionChanged != null) {
  6.                 SelectionChanged(null, new EventArgs());
  7.             }
  8.         }
  9.  
  10.         private UIApplication uiApplication;     
  11.         private List<Element> m_Selection;
  12.         public List<Element> Selection {
  13.             get {
  14.                 return m_Selection;
  15.             }
  16.             set {
  17.                 m_Selection = value;
  18.             }
  19.         }
  20.         public SelectionChangedWatcher(UIControlledApplication application) {
  21.             application.ControlledApplication.DocumentOpened += new EventHandler<Autodesk.Revit.DB.Events.DocumentOpenedEventArgs>(ControlledApplication_DocumentOpened);
  22.             application.Idling += new EventHandler<Autodesk.Revit.UI.Events.IdlingEventArgs>(application_Idling);
  23.         }
  24.  
  25.         void ControlledApplication_DocumentOpened(object sender, Autodesk.Revit.DB.Events.DocumentOpenedEventArgs e) {
  26.             if (uiApplication == null) {                uiApplication = new UIApplication(e.Document.Application);
  27.             }
  28.         }
  29.  
  30.         private string selectionUID;
  31.         void application_Idling(object sender, Autodesk.Revit.UI.Events.IdlingEventArgs e) {           
  32.             if (uiApplication == null) return;
  33.             if (uiApplication.ActiveUIDocument == null) return;
  34.             SelElementSet selected = uiApplication.ActiveUIDocument.Selection.Elements;
  35.             if (selected.Size == 0) {
  36.                 if (m_Selection != null && m_Selection.Count > 0) {
  37.                     HandleSelectionChange(selected);
  38.                 }
  39.             } else {
  40.                 if (m_Selection == null) {                   
  41.                     HandleSelectionChange(selected);
  42.                 } else {
  43.                     if (m_Selection.Count != selected.Size) {
  44.                         HandleSelectionChange(selected);
  45.                     } else {//--count is the same... compare UID to see if selection has changed
  46.                         string uid = "";                       
  47.                         foreach (Element elem in selected) {                           
  48.                             uid += "_" + elem.Id;
  49.                         }
  50.                         if (uid != selectionUID) {
  51.                             HandleSelectionChange(selected);
  52.                         }
  53.                     }
  54.                 }
  55.             }
  56.         }
  57.  
  58.         private void HandleSelectionChange(SelElementSet selected) {
  59.             m_Selection = new List<Element>();
  60.             selectionUID = "";
  61.             foreach (Element elem in selected) {
  62.                 m_Selection.Add(elem);
  63.                 selectionUID += "_" + elem.Id;
  64.             }
  65.             Call_SelectionChanged();
  66.         }
  67.     }

И пример использования:

Код - C#: [Выделить]
  1. private SelectionChangedWatcher selectionChangedWatcher;
  2.           
  3. public Autodesk.Revit.UI.Result OnStartup(UIControlledApplication application) {
  4.         selectionChangedWatcher = new SelectionChangedWatcher(application);
  5.         selectionChangedWatcher.SelectionChanged += new EventHandler(selectionChangedWatcher_SelectionChanged);
  6. }
  7.  
  8. void selectionChangedWatcher_SelectionChanged(object sender, EventArgs e) {
  9.         if (selectionChangedWatcher.Selection == null) return;
  10.        // перебираем все помещения в текущем выделении
  11.         List<Room> rooms = new List<Room>(selectionChangedWatcher.Selection.OfType<Room>());
  12.         ...
  13. }

Использование события UI Automation

Пользователь Vilo предложил другой способ, основанный на использовании UI Automation:

Как и многие другие пользователи Revit API, я скучаю по отсутствию события, реагирующего на изменение выделенного элемента.

Как было отмечено в статье, есть несколько способов для реализации:

  1. Использовать событие Idling. Способ не надежный, так как нет гарантии, что событие возникнет. К тому же метод асинхронный.
  2. Использование таймера и проверять выделенные элементы через определённый интервал. Более надежный, но также является асинхронным и нужно с ним быть очень аккуратным, так как он может затормозить работу Revit.
  3. Но, есть и третий способ, решающий все недостатки предыдущих.

В Revit, когда пользователь выделяет или снимает выделение с объекта, появляется или исчезает специальная вкладка на ленте – «Изменить»

Используя данную возможность и стандартную сборку AdWindows.dll можно подписаться на событие, которое возникает при изменении наименования этой вкладки:

Код - C#: [Выделить]
  1.   foreach( Autodesk.Windows.RibbonTab tab in
  2.     Autodesk.Windows.ComponentManager.Ribbon.Tabs )
  3.   {
  4.     if( tab.Id == " Modify" )
  5.     {
  6.       tab.PropertyChanged += PanelEvent;
  7.       break;
  8.     }
  9.   }

Обработка события выглядит вот так:

Код - C#: [Выделить]
  1.   void PanelEvent(
  2.     object sender,
  3.     System.ComponentModel.PropertyChangedEventArgs e )
  4.   {
  5.     if( sender is Autodesk.Windows.RibbonTab )
  6.     {
  7.       if( e.PropertyName == "Title" )
  8.       {
  9.         //Произошло изменение выделенного объекта !
  10.       }
  11.     }
  12.   }

Я самый очевидный способ, но я признаю это и, пока Autodesk не добавит соответствующее событие в Revit API, для меня это лучшее решение.

После некоторое тестирования я могу добавить:

  • Дополнительной проверки if( sender is Autodesk.Windows.RibbonTab ) не требуется
  • Заголовок не зависит от локализации Revit

Ограничения:

  • Событие не возникает, когда выделяется третий(четвертый, пятый и т.д.) элемент. Я над этим работаю

CmdSelectionChanged

Jeremy реализовал новую команду CmdSelectionChanged в примерах The Building Coder:

Код - C#: [Выделить]
  1. #region Namespaces
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using Autodesk.Revit.Attributes;
  7. using Autodesk.Revit.DB;
  8. using Autodesk.Revit.UI;
  9. #endregion // Namespaces
  10.  
  11. namespace BuildingCoder
  12. {
  13.   [Transaction( TransactionMode.ReadOnly )]
  14.   class CmdSelectionChanged : IExternalCommand
  15.   {
  16.     static UIApplication _uiapp;
  17.     static bool _subscribed = false;
  18.  
  19.     void PanelEvent(
  20.       object sender,
  21.       System.ComponentModel.PropertyChangedEventArgs e )
  22.     {
  23.       Debug.Assert( sender is Autodesk.Windows.RibbonTab,
  24.         "expected sender to be a ribbon tab" );
  25.  
  26.       if( e.PropertyName == "Title" )
  27.       {
  28.         ICollection<ElementId> ids = _uiapp
  29.           .ActiveUIDocument.Selection.GetElementIds();
  30.  
  31.         int n = ids.Count;
  32.  
  33.         string s = ( 0 == n )
  34.           ? "<nil>"
  35.           : string.Join( ", ",
  36.             ids.Select<ElementId, string>(
  37.               id => id.IntegerValue.ToString() ) );
  38.  
  39.         Debug.Print(
  40.           "CmdSelectionChanged: selection changed: "
  41.           + s );
  42.       }
  43.     }
  44.  
  45.     public Result Execute(
  46.       ExternalCommandData commandData,
  47.       ref string message,
  48.       ElementSet elements )
  49.     {
  50.       _uiapp = commandData.Application;
  51.  
  52.       foreach( Autodesk.Windows.RibbonTab tab in
  53.         Autodesk.Windows.ComponentManager.Ribbon.Tabs )
  54.       {
  55.         if( tab.Id == "Modify" )
  56.         {
  57.           if( _subscribed )
  58.           {
  59.             tab.PropertyChanged -= PanelEvent;
  60.             _subscribed = false;
  61.           }
  62.           else
  63.           {
  64.             tab.PropertyChanged += PanelEvent;
  65.             _subscribed = true;
  66.           }
  67.           break;
  68.         }
  69.       }
  70.  
  71.       Debug.Print( "CmdSelectionChanged: _subscribed = {0}", _subscribed );
  72.  
  73.       return Result.Succeeded;
  74.     }
  75.   }
  76. }

Обратите внимание, что команда включает или выключает подписку на событие.

Очевидно, что в реальном приложении логичнее это включать не командой, а при старте приложения.

Ну и стоит отметить, что доступ к методу ActiveUIDocument.Selection.GetElementIds в обработке события PanelEvent недопустимо, так как обработка этого события, не является безопасным контекстом для вызова методов Revit API.

Тем не менее, при беглом тестировании я не заметил ничего страшного и способ действительно работает.

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

Код - XML: [Выделить]
  1.   $ grep CmdSel /tmp/sel.txt
  2.   CmdSelectionChanged: _subscribed = True
  3.   CmdSelectionChanged: selection changed: 121703
  4.   CmdSelectionChanged: selection changed: <nil>
  5.   CmdSelectionChanged: selection changed: 121736
  6.   CmdSelectionChanged: selection changed: <nil>
  7.   CmdSelectionChanged: selection changed: 121785
  8.   CmdSelectionChanged: selection changed: <nil>
  9.   CmdSelectionChanged: selection changed: 121812
  10.   CmdSelectionChanged: selection changed: <nil>
  11.   CmdSelectionChanged: selection changed: 121868
  12.   CmdSelectionChanged: selection changed: <nil>
  13.   CmdSelectionChanged: _subscribed = False

Источник: http://thebuildingcoder.typepad.com/blog/2015/03/element-selection-changed-event.html

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

Опубликовано 21.03.2015