Событие по изменению выделенного элемента
Много разработчиков интересуются возможностью получить оповещения, когда меняется выделение текущего объекта в пользовательском интерфейсе.
Я уже обсуждал реализацию данной возможности в статье Использования события Idling для отслеживания выделения объектов (на англ.) и одним из ключевых аспектов обсуждения являлись так называемые уровни возникновения события, т.е. различные возможности приложения реагировать на изменения объектов в модели.
Проверка на изменение выделенного элемента с помощью обработки события Idling
Ken Goulding прокомментировал следующее:
Когда вы упомянули об возможностях реагировать на изменения в модели, я надеялся, что наконец то появилось событие, возникающее при изменении выделенного элемента, но похоже это не так.
Как обходное решение, я создал класс, в котором идет подписка на событие Idling и идет проверка на изменение выделенного элемента.
Учитывая, что это событие Idling, я постарался максимально грамотно и эффективно реализовать алгоритм отслеживания изменений, но все же может есть более простой способ?
Вот код класса:
- public class SelectionChangedWatcher
- {
- public event EventHandler SelectionChanged;
- protected void Call_SelectionChanged() {
- if (SelectionChanged != null) {
- SelectionChanged(null, new EventArgs());
- }
- }
- private UIApplication uiApplication;
- private List<Element> m_Selection;
- public List<Element> Selection {
- get {
- return m_Selection;
- }
- set {
- m_Selection = value;
- }
- }
- public SelectionChangedWatcher(UIControlledApplication application) {
- application.ControlledApplication.DocumentOpened += new EventHandler<Autodesk.Revit.DB.Events.DocumentOpenedEventArgs>(ControlledApplication_DocumentOpened);
- application.Idling += new EventHandler<Autodesk.Revit.UI.Events.IdlingEventArgs>(application_Idling);
- }
- void ControlledApplication_DocumentOpened(object sender, Autodesk.Revit.DB.Events.DocumentOpenedEventArgs e) {
- if (uiApplication == null) { uiApplication = new UIApplication(e.Document.Application);
- }
- }
- private string selectionUID;
- void application_Idling(object sender, Autodesk.Revit.UI.Events.IdlingEventArgs e) {
- if (uiApplication == null) return;
- if (uiApplication.ActiveUIDocument == null) return;
- SelElementSet selected = uiApplication.ActiveUIDocument.Selection.Elements;
- if (selected.Size == 0) {
- if (m_Selection != null && m_Selection.Count > 0) {
- HandleSelectionChange(selected);
- }
- } else {
- if (m_Selection == null) {
- HandleSelectionChange(selected);
- } else {
- if (m_Selection.Count != selected.Size) {
- HandleSelectionChange(selected);
- } else {//--count is the same... compare UID to see if selection has changed
- string uid = "";
- foreach (Element elem in selected) {
- uid += "_" + elem.Id;
- }
- if (uid != selectionUID) {
- HandleSelectionChange(selected);
- }
- }
- }
- }
- }
- private void HandleSelectionChange(SelElementSet selected) {
- m_Selection = new List<Element>();
- selectionUID = "";
- foreach (Element elem in selected) {
- m_Selection.Add(elem);
- selectionUID += "_" + elem.Id;
- }
- Call_SelectionChanged();
- }
- }
И пример использования:
- private SelectionChangedWatcher selectionChangedWatcher;
- public Autodesk.Revit.UI.Result OnStartup(UIControlledApplication application) {
- selectionChangedWatcher = new SelectionChangedWatcher(application);
- selectionChangedWatcher.SelectionChanged += new EventHandler(selectionChangedWatcher_SelectionChanged);
- }
- void selectionChangedWatcher_SelectionChanged(object sender, EventArgs e) {
- if (selectionChangedWatcher.Selection == null) return;
- // перебираем все помещения в текущем выделении
- List<Room> rooms = new List<Room>(selectionChangedWatcher.Selection.OfType<Room>());
- ...
- }
Использование события UI Automation
Пользователь Vilo предложил другой способ, основанный на использовании UI Automation:
Как и многие другие пользователи Revit API, я скучаю по отсутствию события, реагирующего на изменение выделенного элемента.
Как было отмечено в статье, есть несколько способов для реализации:
- Использовать событие Idling. Способ не надежный, так как нет гарантии, что событие возникнет. К тому же метод асинхронный.
- Использование таймера и проверять выделенные элементы через определённый интервал. Более надежный, но также является асинхронным и нужно с ним быть очень аккуратным, так как он может затормозить работу Revit.
- Но, есть и третий способ, решающий все недостатки предыдущих.
В Revit, когда пользователь выделяет или снимает выделение с объекта, появляется или исчезает специальная вкладка на ленте – «Изменить»
Используя данную возможность и стандартную сборку AdWindows.dll можно подписаться на событие, которое возникает при изменении наименования этой вкладки:
- foreach( Autodesk.Windows.RibbonTab tab in
- Autodesk.Windows.ComponentManager.Ribbon.Tabs )
- {
- if( tab.Id == " Modify" )
- {
- tab.PropertyChanged += PanelEvent;
- break;
- }
- }
Обработка события выглядит вот так:
- void PanelEvent(
- object sender,
- System.ComponentModel.PropertyChangedEventArgs e )
- {
- if( sender is Autodesk.Windows.RibbonTab )
- {
- if( e.PropertyName == "Title" )
- {
- //Произошло изменение выделенного объекта !
- }
- }
- }
Я самый очевидный способ, но я признаю это и, пока Autodesk не добавит соответствующее событие в Revit API, для меня это лучшее решение.
После некоторое тестирования я могу добавить:
- Дополнительной проверки if( sender is Autodesk.Windows.RibbonTab ) не требуется
- Заголовок не зависит от локализации Revit
Ограничения:
- Событие не возникает, когда выделяется третий(четвертый, пятый и т.д.) элемент. Я над этим работаю
CmdSelectionChanged
Jeremy реализовал новую команду CmdSelectionChanged в примерах The Building Coder:
- #region Namespaces
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Linq;
- using Autodesk.Revit.Attributes;
- using Autodesk.Revit.DB;
- using Autodesk.Revit.UI;
- #endregion // Namespaces
- namespace BuildingCoder
- {
- [Transaction( TransactionMode.ReadOnly )]
- class CmdSelectionChanged : IExternalCommand
- {
- static UIApplication _uiapp;
- static bool _subscribed = false;
- void PanelEvent(
- object sender,
- System.ComponentModel.PropertyChangedEventArgs e )
- {
- Debug.Assert( sender is Autodesk.Windows.RibbonTab,
- "expected sender to be a ribbon tab" );
- if( e.PropertyName == "Title" )
- {
- ICollection<ElementId> ids = _uiapp
- .ActiveUIDocument.Selection.GetElementIds();
- int n = ids.Count;
- string s = ( 0 == n )
- ? "<nil>"
- : string.Join( ", ",
- ids.Select<ElementId, string>(
- id => id.IntegerValue.ToString() ) );
- Debug.Print(
- "CmdSelectionChanged: selection changed: "
- + s );
- }
- }
- public Result Execute(
- ExternalCommandData commandData,
- ref string message,
- ElementSet elements )
- {
- _uiapp = commandData.Application;
- foreach( Autodesk.Windows.RibbonTab tab in
- Autodesk.Windows.ComponentManager.Ribbon.Tabs )
- {
- if( tab.Id == "Modify" )
- {
- if( _subscribed )
- {
- tab.PropertyChanged -= PanelEvent;
- _subscribed = false;
- }
- else
- {
- tab.PropertyChanged += PanelEvent;
- _subscribed = true;
- }
- break;
- }
- }
- Debug.Print( "CmdSelectionChanged: _subscribed = {0}", _subscribed );
- return Result.Succeeded;
- }
- }
- }
Обратите внимание, что команда включает или выключает подписку на событие.
Очевидно, что в реальном приложении логичнее это включать не командой, а при старте приложения.
Ну и стоит отметить, что доступ к методу ActiveUIDocument.Selection.GetElementIds в обработке события PanelEvent недопустимо, так как обработка этого события, не является безопасным контекстом для вызова методов Revit API.
Тем не менее, при беглом тестировании я не заметил ничего страшного и способ действительно работает.
Вот результат выполнения команды, где я выделял элементы и снимал выделения с объектов:
- $ grep CmdSel /tmp/sel.txt
- CmdSelectionChanged: _subscribed = True
- CmdSelectionChanged: selection changed: 121703
- CmdSelectionChanged: selection changed: <nil>
- CmdSelectionChanged: selection changed: 121736
- CmdSelectionChanged: selection changed: <nil>
- CmdSelectionChanged: selection changed: 121785
- CmdSelectionChanged: selection changed: <nil>
- CmdSelectionChanged: selection changed: 121812
- CmdSelectionChanged: selection changed: <nil>
- CmdSelectionChanged: selection changed: 121868
- CmdSelectionChanged: selection changed: <nil>
- 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