From my point of view, one of the most important enhancements to the Revit API is the availability of element level events. This allows an application to react to modifications to the database, including tracking changes to individual elements. The Revit 2011 API provides not just one but two separate mechanisms to subscribe to element level notification events:
Elements Changed Event
The new Application.DocumentChanged event is raised after every transaction is committed, undone, or redone. This is a read-only event, designed to allow an application to keep external data synchronised with the Revit database state. To update the Revit database in response to changes in elements, use the Dynamic Model Update framework described below.
The event handler receives a DocumentChangedEventArgs instance as an input argument which provides information on the operation performed and the document and elements involved. Some of its most important properties and methods are therefore the following:
- Operation: the operation associated with this event.
- GetDocument: the document associated with this event.
- GetAddedElementIds: the set of elements newly added to the document.
- GetDeletedElementIds: the set of elements that were deleted.
- GetModifiedElementIds: the set of elements that were modified.
- GetTransactionNames: the names of the transactions associated with this event.
ChangesMonitor SDK Sample
The use of the elements changed event is demonstrated by the ChangesMonitor Revit SDK sample. It implements a data table to log all the changes made to the database, and a modeless dialogue ChangesInformationForm to display its contents. The main modules of the sample project are ChangesMoniotor.cs and ChangesInfoForm.cs:
- ChangesMoniotor.cs: This file contains a class ExternalApplication which implements the IExternalApplication interface and the sample entry point. It creates a modeless dialog to track the changed elements' information. This file also defines an external command class Command which inherits from the IExternalCommmand interface. Its function is to redisplay the information dialogue if it has been closed.
- ChangesInfoForm.cs: This file defines a ChangesInformationForm form class which displays the changed element information in a DataGridView in a modeless dialogue. The information includes the change type (Added, Deleted, and Modified), and the modified element id, name, category name, and document owning it.
The external application initialises these components and registers to the Application.DocumentChanged event in its OnStartup method like this:
public Result OnStartup( UIControlledApplication application ) { // initialize member variables. m_CtrlApp = application.ControlledApplication; m_ChangesInfoTable = CreateChangeInfoTable(); m_InfoForm = new ChangesInformationForm( ChangesInfoTable ); // register the DocumentChanged event m_CtrlApp.DocumentChanged += new EventHandler< Autodesk.Revit.DB.Events.DocumentChangedEventArgs>( CtrlApp_DocumentChanged ); // show dialog m_InfoForm.Show(); return Result.Succeeded; }
Here is an example of the information that it collects and presents:
ChangesMonitor also implements an external command to redisplay the modeless dialogue, in case you closed it and want to reopen it again. By default, it is opened automatically on start-up.
Dynamic Model Update
As mentioned above, the Application.DocumentChanged event is a read-only event, so it does not support modifications to the Revit database as a reaction to the changes that triggered it.
For that, you can make use of the much more powerful dynamic model update mechanism.
This is what the What's New section in the Revit API help file has to say about the dynamic model update:
Dynamic model update offers the ability for a Revit API application to modify the Revit model as a reaction to changes happening in the model.
This facility is offered through implementation of updaters.
The updater interface offers the ability to implement a method that is informed of the scope of changes that triggered the update.
In order to 'subscribe' an updater to the right set of changes, the updater should be assigned one or more update triggers.
Update triggers are combinations of 'Change Scope' and 'Change Type'. Change Scope may be either an explicit list of element ids in a document, or an implicit list of elements communicated via an ElementFilter. Change Type represents one of an available list of possible changes, including element addition, deletion, and modification of geometry, parameters, or any property of the element.
More detailed information on the dynamic model update capabilities are provided in the Developer Guide chapter 25.
One important thing to note is that this facility allows the application to perform its changes to react to the notification within the same transaction as the one that triggered the changes.
Since many different updaters may try to modify things which will trigger new updaters, the execution sequence is important. A facility for defining your execution priority is included in the system.
The use of the dynamic model update is demonstrated by two different SDK samples, DynamicModelUpdate and DistanceToSurfaces. The latter also demonstrates the new analysis visualization framework and is therefore located in the AnalysisVisualizationFramework subfolder. Both of these are implemented as external applications which set up their updater and trigger during the OnStartup or DocumentOpened events.
DynamicModelUpdate SDK Sample
The use of the dynamic model update is demonstrated by the DynamicModelUpdate Revit SDK sample. It is also implemented as an external application and designed to work in a specific included sample model AssociativeSection.rvt. It shows how you can use this mechanism to maintain the relative position between elements, in this case a window located in a wall and a section view positioned to display the cut through the window and the wall. This sample uses the hard-coded element id of the section view marker to update its position:
Here is the result of the section view displaying the window in the wall:
These are the detailed steps of the sample implementation:
- During Revit start-up:
- Register the SectionUpdater class.
- Add a trigger so that the SectionUpdater class will be invoked for any geometry change to a window.
- When the geometry of a window changes:
- Move the section marker (using the hard-coded element id for the sample model) to a new location based on the distance that the window moved in the X, Y, and Z directions, which are stored as parameters in the window family.
- Update the values of the window's X, Y, and Z parameters based on the location point of the window.
If the window is moved, the section view is updated to move with it, so that it continues to display the correct cut. Obviously, if the wall moves, the window will move with it, too, and that will again update the section location.
DistanceToSurfaces SDK Sample
The use of the dynamic model update is also demonstrated by the DistanceToSurfaces SDK sample, although it is primarily intended to highlight the new analysis visualization framework, which allows an application to display analysis results graphically by painting the surfaces of Revit elements. Once again, it is written to work in a specific sample model. The model includes a sphere object represented by a family instance, i.e. an insertion of the sphere.rfa family. It calculates and displays the distance from the sphere to all surfaces of all walls and mass elements in the model.
On start-up, the external application registers a DocumentOpened event handler. This in turn creates a trigger that will execute when walls, masses, and family instances in the project change. When this occurs, the application calculates the distance from the sphere to several points on each face. These distances are used as values for the analysis visualization display. Here are the individual steps in more detail:
- On start-up, register a DocumentOpened event. When a document is opened:
- Check for the presence of the sphere family.
- Check for the presence of a 3D view named 'AVF'.
- Create a GetChangeTypeGeometry trigger that will activate when a wall, mass, or family instance changes.
- When this trigger occurs:
- Find the XYZ location of the sphere.
- Get the existing spatial field manager object, or create one if one does not already exist.
- Create a face array containing every face of a wall or mass in the project, including in-place geometry.
- Calculate the distance between the sphere's origin and several points on each face.
- Display these distances as analysis results on the faces by updating the spatial field primitive.
This is what the initial DistanceToSurfaces.rvt sample model looks like:
If any one of the relevant elements is moved, the trigger reacts and the updater is called, which uses a filter to retrieve all walls and mass elements and the SpatialFieldManager to paint their faces depending on the distance to the sphere element:
Using Manage > Additional Settings > Analysis Display Styles, the analysis result colour mapping can either be set up to use ranges, as shown above, or a smooth gradient like this:
Developers have been clamouring for element level events ever since the first events were introduced in the Revit API, and even more so when the events were enhanced for the Revit 2010 API. In previous releases, only application and document level events were available. It will be very exciting to see what powerful new applications can be created to make use of this!
Object Level events are not available in the latest SDK. Can you post the latest changemonitor solution file?
Thanks
Posted by: Srinivasan | April 28, 2010 at 05:49
Dear Srinivasan,
I assume you mean "are now available".
The latest version of the SDK including the ChangesMonitor sample is publicly available at
http://www.autodesk.com/developrevit
The file details from that location are:
2010-04-28 13:42 110,098,139 revit2011sdk.exe
The versions of the SDK included with the Revit Architecture, MEP and Structure products are:
2010-03-26 21:23 110,416,595 RevitSDK.exe
It does not really make any difference which SDK you use. The SDK really just provides information and samples. The actual API is completely defined by and contained in the Revit API assemblies which are provided with the Revit product, in the Program subdirectory of the main installation folder where Revit.exe also resides. On my system they are
2010-03-26 20:27 15,494,656 RevitAPI.dll
2010-03-26 20:28 711,168 RevitAPIUI.dll
I have not compared the two versions of the SDK, but I think the differences are negligible.
On my system I installed the one coming with the product, i.e. the 110,416,595 byte version, and I have not noticed any problems with that.
Cheers, Jeremy.
Posted by: Jeremy Tammik | April 28, 2010 at 08:14
Hi Jeremy
Iam mean that the eventsmonitor sample in the SDK doesn't show any function related to the document changed event.
I wanted to test with the object level event & was looking for some sample.
Srini
Posted by: Srinivasan | April 29, 2010 at 03:33
Dear Srini,
I do not know when you say "the eventsmonitor sample". Do you mean the ChangesMonitor sample, which demonstrates the use of the Application.DocumentChanged event? If yes, then please see above for all the details. If not, please clarify.
Cheers, Jeremy.
Posted by: Jeremy Tammik | April 29, 2010 at 12:36
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 "Idle" event and checks for selection changes. Given that its an "Idle" 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 Autodesk.Revit.UI.Result OnStartup(UIControlledApplication application) {
selectionChangedWatcher = new SelectionChangedWatcher(application);
selectionChangedWatcher.SelectionChanged += new EventHandler(selectionChangedWatcher_SelectionChanged);
...
Code:
public class SelectionChangedWatcher
{
public event EventHandler SelectionChanged;
protected void Call_SelectionChanged() {
if (SelectionChanged != null) {
SelectionChanged(null, new EventArgs());
}
}
private UIApplication uiApplication;
private List m_Selection;
public List Selection {
get {
return m_Selection;
}
set {
m_Selection = value;
}
}
public SelectionChangedWatcher(UIControlledApplication application) {
application.ControlledApplication.DocumentOpened += new EventHandler(ControlledApplication_DocumentOpened);
application.Idling += new EventHandler(application_Idling);
}
void ControlledApplication_DocumentOpened(object sender, Autodesk.Revit.DB.Events.DocumentOpenedEventArgs e) {
if (uiApplication == null) {//--doesn't really need to listen to documentOpened event, but this seems the easiest way to get a reference to the UIApplication
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();
selectionUID = "";
foreach (Element elem in selected) {
m_Selection.Add(elem);
selectionUID += "_" + elem.Id;
}
Call_SelectionChanged();
}
}
Posted by: Ken Goulding | August 20, 2010 at 11:41
Sorry for the unreadable code. Here are some PasteBin URLs:
Class: http://visualstudio.pastebin.com/X5rBXSA6
Usage: http://visualstudio.pastebin.com/aNwEFRkZ
Posted by: Ken Goulding | August 23, 2010 at 10:57
Dear Ken,
Thank you for your query with the interesting idea and the source code.
No worry about its readability, one can easily copy and paste it into a Visual Studio project to automatically reformat it.
Thank you also for the interesting PasteBin URLs. I was not previously aware of this possibility.
Regarding the element selection events, you are correct in your assessment that these are currently not provided by the Revit API.
Your use of the Idling event to implement a work-around sounds like a brilliant idea to me.
Would you like to provide a complete little sample solution with the code and a minimal testing script, so I can run through it with a minimum of fuss?
I would love to promote this to a full-fledged blog post to increase awareness of it and provide an additional example of using the Idling event in creative ways including stopping gaps in the existing API. My contact details are listed in the page 'About the Author'.
I tried to contact you directly using the link attached to your name above, but that does not work for me, unfortunately.
Thank you!
Cheers, Jeremy.
Posted by: Jeremy Tammik | August 25, 2010 at 05:15
Hi all,
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 guaranty 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 event are generated.
3. There is a third method that solve 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 :
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")
{
//selection changed !
}
}
}
Not the cleanest way I admit, but until Autodesk adds the relevant API methods, this is (for me) the best.
Posted by: Vilo | March 16, 2015 at 06:20
Dear Vilo,
That is an absolutely superb idea and brilliant little workaround.
I'll promote it to a main blog post asap.
Thank you!
Cheers, Jeremy.
Posted by: Jeremy Tammik | March 16, 2015 at 07:24
Hi Jeremy,
Feel free to make what you want with this workaround ;o)
Since I posted, I can added the following elements :
- 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).
- 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.
Posted by: Vilo | March 16, 2015 at 09:25
Dear Vilo,
Thank you for your update, permission to share and pointer to the current limitations.
I implemented a new external command for The Building Coder samples exercising this:
https://github.com/jeremytammik/the_building_coder_samples/blob/master/BuildingCoder/BuildingCoder/CmdSelectionChanged.cs
Obviously it would normally not be used in a command, but on startup in an external application.
Cheers, Jeremy.
Posted by: Jeremy Tammik | March 16, 2015 at 09:55
Dear Vilo,
Thank you again for your wonderful idea!
I published it as a stand-alone post now:
http://thebuildingcoder.typepad.com/blog/2015/03/element-selection-changed-event.html
Cheers, Jeremy.
Posted by: Jeremy Tammik | March 16, 2015 at 14:58