Element Selection Changed Event

Many add-in developers are interested in being notified when the current selection changes in the Revit user interface.

Among other things, it led to the implementation of the


selection watcher using the Idling event
and
was one aspect of the discussion of
element level events.

Checking for Selection Changes in Idling Event Handler

Ken Goulding submitted a

comment
on
that, saying:

When you mentioned “Element Level”, I was hoping that included element selection events, but that does not seem to be the case.

As a work-around, I created this class that subscribes to the application “Idling” event and checks for selection changes.

Given that it is an “Idling” event, I have tried to be as efficient as possible at figuring out whether a change has happened, but there may be a simpler way?

Usage:


public Result OnStartup( UIControlledApplication a )
{
selectionChangedWatcher = new SelectionChangedWatcher( a );
selectionChangedWatcher.SelectionChanged
+= new EventHandler(
selectionChangedWatcher_SelectionChanged);
// . . .

Here are the PasteBin URLs to the sample implementation code:

Using a UI Automation Event to Notify Selection Changes

Vilo submitted a new

comment
with
an updated idea based on using UI Automation instead.

In Vilo’s own words:

As many others Revit API users, I miss the “element selection changed” API access.

As mentioned in this post, there are several solutions, each having some caveats:

  1. Use the OnIdling event to check current selection.
      – This may not be reliable, because there is no guarantee it will be fired by Revit.
    Secondly, this is an asynchronous method.
  2. Use a Timer to raise an event at a specified interval.
      – More reliable, but still asynchronous and the more accurate it is (i.e. shorter timer delay), the more unneeded events are generated.
  3. There is a third method that solves all the above “issues”:

In Revit, when the user select/unselect an object, there is a special ribbon tab that dynamically changes, its name is “Modify”.

Knowing that, and using the standard Autodesk assembly “AdWindows.dll”, we can register an event that will be fired when this Tab’s title changes.

Here it is the code to subscribe to the event:


  foreach( Autodesk.Windows.RibbonTab tab in
    Autodesk.Windows.ComponentManager.Ribbon.Tabs )
  {
    if( tab.Id == "Modify" )
    {
      tab.PropertyChanged += PanelEvent;
      break;
    }
  }

The event handler can look like this:


  void PanelEvent(
    object sender,
    System.ComponentModel.PropertyChangedEventArgs e )
  {
    if( sender is Autodesk.Windows.RibbonTab )
    {
      if( e.PropertyName == "Title" )
      {
        //selection changed !
      }
    }
  }

Not the cleanest way I admit, but until Autodesk adds the relevant API methods, this is (for me) the best.

Update:

  • The test “if (sender is Autodesk.Windows.RibbonTab)” is not needed.
  • The Tab’s name is not dependent to current language.

My Revit is in French, and the tab’s name is still “Modify” at start (then it changes according to current selection of course).

Limitation:

  • There is actually a limitation I’m working on: the event is not fired after selecting the 3rd element (and 4th, 5th, …) in a multiselection.

CmdSelectionChanged

Jeremy adds: I implemented a new external command

CmdSelectionChanged
in

The Building Coder samples
exercising this:


#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;
    }
  }
}

Notice that this external command toggles the event subscription on and off on each call.

Obviously this technique would normally not be used in a command, but on startup in an external application.

Also, accessing the ActiveUIDocument.Selection.GetElementIds method in the PanelEvent event handler is absolutely forbidden, since we are not in a valid API context at that point.

A real application would handle this more elegantly and above all legally, e.g. by raising an external event in the PanelEvent event handler, and then accessing the selection set inside the external event handler instead.

Still, for a quick test this works fine.

Here is the result of launching the command, alternately selecting individual Revit elements in the model, clearing the selection set again by clicking into void space, and relaunching the command to terminate the event subscription again:


$ 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

The new external command method CmdSelectionChanged lives in the module

CmdSelectionChanged.cs
in

release 2015.0.120.0
and later of
The Building Coder samples.


Comments

4 responses to “Element Selection Changed Event”

  1. Hi Jeremy,
    Thanks a lot for this blog post, and for having completed my comment with very useful extra elements :o)
    The use of “ExternalEvent” mechanism is, indeed, the way to go to re-enter a valid Revit context (that’s what I’m using).
    By the way, as I stated in my comments, this method gives great overall results, but fails in one situation : multiple homogeneous selections where selections 3+ may not trigger the event.
    To solve this issue, I think there may be something to do with the StatusBar.
    As you may have noticed, when the user select/deselect any object in the model, the small “filter icon” at the end of the statusbar changes to reflect the number of selected items (i.e. text changes from “:0” to “:1” and so on).
    If we were able to get access to this statusbar button, and be notified when its text changes, I think this could be the ultimate solution.
    The bad news is that UI Automation (AdWindows.dll) does not expose the statusbar control …
    The only trick I see would be to create a HOOK to monitor messages for this very button, but I don’t want to go to the dark side of the force.

  2. Dear Vilo,
    Thank you again for initiating all this, and for your important observations regarding the multiple selection issue.
    Actually, I think the status bar is very well accessible using UI Automation, and, unless I misunderstood, Revitalizer alias Rudolf Honke just demonstrated how to do so to list and switch design options:
    http://thebuildingcoder.typepad.com/blog/2015/03/list-and-switch-design-options-using-ui-automation.html
    Cheers, Jeremy.

  3. Yes indeed Jeremy,
    The example shows how to get access to StatusBar, but through standard windows API and Handles, not through AdWindows.
    Sadly, accessing the StatusBar via AdWindows mechanism is (as far as I can say) mandatory to be able to add an event listener through “PropertyChanged”.
    Accessing it via Handles, as I stated above, can grant us the possibility to place a system wide Hook to monitor text change.
    But this is a far uglier solution that may lead to other problems (harder to code, slower, not reliable, etc …).

  4. Dear Vilo,
    Right you are. Well, well…
    Thank you for the clarification!
    Cheers, Jeremy.

Leave a Reply to ViloCancel reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading