by Guy Robinson
19. April 2010 13:36
To be honest I’m loathed to single out a single standout feature from the new API’s. There is no doubt Updaters are going to be widely use. They plug a major gap in the API’s capabilities, the Updater API is well designed and are fast. However, there is a single API that does standout and in my opinion allows developers to begin the process of seamlessly integrating Revit into users BIM workflows unlike anything that has been possible before. And that API is the Application Idling event.
What happens is every time a user does nothing ;-) Revit will fire an application idling event. API developers can subscribe to this and do some work. The developer needs to make sure they don’t spend too much time in the handler and provide visual feedback on what they’re doing or the user experience will be significantly degraded and potential confusion.
There are a number of patterns possible with the Idling event and I’ll cover a simple one initially. Essentially this pattern replicates what updaters can do but in a more asynchronous manner. Although in the example command I’m not doing anything with the data. It uses an Idling event in conjunction with a DocumentChanged event to queue elements to be processed as required. The processing will only happen when Revit is idle.Here’s a screen shot:
The command opens a window that stays on top, that can be moved but not closed. The count shows the number of items in the queue to be processed and the status shows whether Revit is working or idling (orange working, green idle). I’ve intentionally slowed things down to try and show you how and when the idle event fires and the instances in the queue are processed. Create a number of elements in succession (like walls without chaining) and then double escape and watch the queue count and status update. Pretty damn cool!!
The essential class is the DataService singleton class . It’s this class I use to wire up the Idling and DocumentChanged events and generate the queue of ChangedIdTick instances to process. Here’s the event handlers for the Revit Idling and DocumentChanged events:
/// <summary>
/// Documents changed event handler.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="Autodesk.Revit.DB.Events.DocumentChangedEventArgs"/> instance containing the event data.</param>
public void DocumentChanged(object sender, DocumentChangedEventArgs e)
{ var activeDocument = e.GetDocument();
// this allows us to reference the document by name rather than storing documents in the queue
if (!_docTable.ContainsKey(activeDocument.Title)) _docTable.Add(activeDocument.Title, activeDocument);
// now add a tick to the queue
_queue.Enqueue(new ChangedIdTick(activeDocument.Title, e.GetAddedElementIds(), e.GetModifiedElementIds(), e.GetDeletedElementIds()));
}
/// <summary>
/// Applications idling event handler.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="Autodesk.Revit.UI.Events.IdlingEventArgs"/> instance containing the event data.</param>
public void ApplicationIdling(object sender, IdlingEventArgs e)
{ if (_queue.Count != 0)
{ // if the queue has some ticks get one and sleep for 1/2 second
var tick = _queue.Dequeue();
Thread.Sleep(500);
}
//set the count rather than expose the queue to the viewmodel
Count = _queue.Count;
// now raise the event on the viewmodel
OnRevitWasIdle(EventArgs.Empty);
}
And then the DataViewModel class provides the bridge between the revit dataservice and the WPF window. It subscribes to the DataServices’s RevitWasIdle event which the dataservice fires when it’s completed handling the Revit Idle event. The DataViewModel then updates the UI accordingly via databinding. If there are other aspect you’d like me to describe feel free to comment. I’ve tried to comment the code appropriately. Here’s the DataViewModel’s handler for the RevitWasIdle event.
/// <summary>
/// idle handler for dataservice.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void RevitWasIdleHandler(object sender, EventArgs args)
{ IsIdle = true;
Count = _revitDataService.Count.ToString();
// using a dispatch timer to leave the idling radio button
// on for a 1 second
// leave IsIdle on for 1 second if it's not already running
if (!_dispatcherTimer.IsEnabled) _dispatcherTimer.Start();
}
Maybe you don’t find this Idle exciting. But you might (should) when I post the next pattern…
Exciting times!!!
Code in the usual place. Note it’s a VS2010 project so if you’re on 2008 delete the .sln and open the .csproj directly, and you’ll need to update the path in the addin or use the AddInManager from the SDK to use the binary.