DevCamp Day Two

This morning I presented my second session, on some of the Revit 2013 UI API enhancements, in the Revit API beginner track. After that, I returned back to the Revit API expert one to attend the presentations on modeless interaction and IFC before presenting my final session on the Revit MEP API. Here then are my sessions of today, in chronological order:

Revit 2013 UI API Enhancements

Enhanced add-in integration is an important topic in the Revit 2013 API. This session discusses four of the add-in integration topics in more depth, building on a preceding presentation by Saikat Bhattacharya presenting a snapshot of the entire Revit UI API functionality:

All four of these are covered by existing SDK samples, the first by ProgressNotifier (Events), the others by the comprehensive UIAPI sample.

For someone like me, feeling a strong affinity to Winnie the Pooh, the "bear of very little brain", the existing SDK samples are way beyond simple comprehension and reuse, so I ended up creating four own extremely simple and minimal ones exercising these features in just a few lines of code each instead, to reduce the code and required audience comprehension power to an absolute minimum.

Revit progress bar notifications

To receive information about the current Revit progress bar status, you can subscribe to the ProgressChanged event.

The event handler will receive a ProgressChangedEventArgs instance providing the following properties:

The ProgressNotifier SDK sample displays a WPF-based dialogue box. It provides a button to open and load a Revit model. Loading the selected file will cause a number of progress bar actions to execute. These actions are monitored and presented in a stack structure, since occasionally more than one progress bar event may be active simultaneously:

ProgressNotifier SDK sample

My new ProgressWatcher sample in much simpler and tracks all progress bar events by just printing the event argument data to the Visual Studio debug output window. This can be achieved in a very few lines of code, basically only one each to subscribe to the event and to access and print the progress bar event data:

[Transaction( TransactionMode.ReadOnly )]
public class Command : IExternalCommand
{
  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    UIApplication uiapp = commandData.Application;
    Application app = uiapp.Application;
 
    app.ProgressChanged 
      += new EventHandler<ProgressChangedEventArgs>( 
        OnProgressChanged );
 
    return Result.Succeeded;
  }
 
  void OnProgressChanged( 
    object sender, 
    ProgressChangedEventArgs e )
  {
    double percent = 100.0 * e.Position / e.UpperRange;
 
    Debug.Print( 
      "'{0}' stage {1} position {2} [{3}, {4}] ({5}%)",
      e.Caption, e.Stage, e.Position, e.LowerRange, 
      e.UpperRange, percent.ToString( "0.##" ) );
  }
}

Saving the output to a text file will allow you to analyse in detail the progress bar event sequence and nesting.

Options dialogue WPF custom extensions

In Revit 2013, an add-in can add its own custom tabs to the standard Revit Options dialogue.

This is achieved by defining a WPF control to display and subscribing to the UIApplication DisplayingOptionsDialog event.

The event handler receives an DisplayingOptionsDialogEventArgs instance. It has a PagesCount property providing the number of Options dialogue tabs including default Revit ones. It also sports the AddTab method that can be used to add a tab, providing a name and a handler for it. The handler information is encapsulated in the TabbedDialogExtension class.

The TabbedDialogExtension class provides a constructor taking two arguments, a WPF user control instance and an OK handler. These cannot be changed later. It provides methods to get and set contextual help, and properties to read the WPF control and OK handler. It also provides properties to both get and set the cancel and 'restore defaults' handlers:

The UIAPI SDK sample adds several custom tabs demonstrating most available features.

I created a minimal sample named AddOptionsTab which defines the simplest possible WPF control with one single button to click:

AddOptionsTab custom Options tab

If you click the button, the corresponding event handler on the form is called, which simply displays a task dialog:

AddOptionsTab button clicked

If this tab has ever been activated after the Options dialogue was opened, its OK, cancel and 'restore defaults' handlers are called, depending on the user actions.

Here is the entire user control source code defining its handlers:

public partial class UserControl1 : UserControl
{
  string _name;
 
  public UserControl1( string name )
  {
    _name = name;
 
    InitializeComponent();
  }
 
  private void button1_Click( 
    object sender, 
    RoutedEventArgs e )
  {
    TaskDialog.Show( _name, "I was clicked..." );
  }
 
  public void OnOK()
  {
    TaskDialog.Show( _name, "OK" );
  }
 
  public void OnCancel()
  {
    TaskDialog.Show( _name, "OnCancel" );
  }
 
  public void OnRestoreDefaults()
  {
    TaskDialog.Show( _name, "OnRestoreDefaults" );
  }
}

The event handler code reacting to the Options dialogue displaying simply instantiates the user control, calls the TabbedDialogExtension constructor with it, and feeds that to the event handler arguments AddTab method:

void OnDisplayingOptionsDialog( 
  object sender, 
  DisplayingOptionsDialogEventArgs e )
{
  UserControl1 c = new UserControl1( 
    "DevCamp User Control" );
 
  e.AddTab( "DevCamp Custom Tab", 
    new TabbedDialogExtension( 
      c, c.OnOK ) );
}

The event handler is called each time the Options dialogue is invoked once the event has been subscribed to, which is achieved in a single line of code like this in the AddOptionsTab external command:

public Result Execute(
  ExternalCommandData commandData,
  ref string message,
  ElementSet elements )
{
  UIApplication uiapp = commandData.Application;
 
  uiapp.DisplayingOptionsDialog 
    += new EventHandler<DisplayingOptionsDialogEventArgs>( 
      OnDisplayingOptionsDialog );
 
  return Result.Succeeded;
}

Embed and control a Revit view

Another of the add-in integration enhancements enables you to embed and control a Revit view in your own form, dialogue or window.

The new PreviewControl class presents a preview control to browse the Revit model. It takes two input arguments, a document and a view id. The hosting form or window must be modal. The View can be any graphical view, i.e. must be printable. Perspective views are also supported, and all 3D Views can be manipulated by view cube. The visibility and graphical settings of the view are effective and respected by the control.

The UIAPI SDK sample proves a demonstration of this as well, which allows you to interactively select any of the open documents to view, or even open a new one, and secondly presents a list of all the documents views for you to switch between. Each time you change any of these two selections, the current preview control is discarded and a new one is instantiated with the updated input arguments. These input arguments are the only way in which the add-in affects the view, all further interaction happens between the control and the user.

To use a Revit preview control, you simply create a standard .NET form and insert a WPF host in it. A suitable host is the System.Windows.Forms.Integration.ElementHost. You populate this with a new Revit preview control instance, e.g. like this

  elementHost.Child = new PreviewControl( 
    doc, view.Id );

The PreviewControl instance needs to be disposed of after use:

  PreviewControl vc = elementHost.Child
    as PreviewControl;

  if( vc != null ) { vc.Dispose(); }

Here is a code snippet showing how all the printable views can be retrieved using a filtered element collector:

  IEnumerable<View> views
    = new FilteredElementCollector( doc )
      .OfClass( typeof( View ) )
      .Cast<View>()
      .Where<View>( v => v.CanBePrinted );

I created a minimal sample application PreviewControlSimple for this DevCamp class with much less code (and functionality than the UIAPI one, since it just displays the current view of a document with no further ado.

It does add one interesting twist, though: to show exactly how the hosting form is set up, I implemented that twice over, first using the Visual Studio designer as usual, and then converting the form to be completely programmatically generated. Here is the DisplayRevitView method taking the document and view input arguments for the preview control. It also takes a third argument for the main Revit window, so that the generated control can be hooked up to that as a parent window. This defines an appropriate relationship between the two as far as the Windows OS is concerned, ensuring correct minimisation and restoration behaviour:

const string _caption_prefix
  = "Simple Revit Preview - ";
 
void DisplayRevitView( 
  Document doc,
  View view,
  IWin32Window owner )
{
 
  using( PreviewControl pc
    = new PreviewControl( doc, view.Id ) )
  {
 
#if CREATE_FORM_BY_CODE
 
    using( System.Windows.Forms.Form form
      = new System.Windows.Forms.Form() )
    {
      ElementHost elementHost = new ElementHost();
 
      elementHost.Location
        = new System.Drawing.Point( 0, 0 );
 
      elementHost.Dock = DockStyle.Fill;
      elementHost.TabIndex = 0;
      elementHost.Parent = form;
      elementHost.Child = pc;
 
      form.Text = _caption_prefix + view.Name;
      form.Controls.Add( elementHost );
      form.Size = new Size( 400, 400 );
      form.ShowDialog( owner );
    }
 
#else // if not CREATE_FORM_BY_CODE
 
    Form1 form = new Form1( pc );
    form.ShowDialog( owner );
 
#endif // CREATE_FORM_BY_CODE
 
  }
}

Please note that correct disposal of the preview control is ensured by the 'using' statement. This is similar to Arnošt's strong recommendation to always encapsulate Revit Transactions in a 'using' statement.

Calling this method from the external command mainline with the current document view is trivial. I add one line of code to access the Revit main window:

public Result Execute(
  ExternalCommandData commandData,
  ref string message,
  ElementSet elements )
{
  IWin32Window revit_window
    = new JtWindowHandle(
      ComponentManager.ApplicationWindow );
 
  UIApplication uiapp = commandData.Application;
  UIDocument uidoc = uiapp.ActiveUIDocument;
  Document doc = uidoc.Document;
 
  View view = doc.ActiveView;
 
  DisplayRevitView( doc, view, revit_window );
 
  return Result.Succeeded;
}

Drag and Drop API

I recently discussed the new drag and drop functionality and the UIAPI sample command demonstrating it.

The UIApplication provides the new static DoDragDrop method, with two overloads:

This method is designed for use in a modeless form.

Please note that this is currently the one and only Revit API method not requiring a valid Revit API context.

This method can be called from your modeless form at any time, even without Revit giving you explicit permission to interact with the API via some form of callback or notification, as required for all other Revit API calls.

The behaviour of drag and drop given a list of filenames depends on the type of files, as I already recently pointed out:

Here again, I implemented a new DevCamp sample add-in DragDropApi to be able to show the necessary steps in a simpler context than the corresponding UIAPI example.

Here are my four new Revit 2013 UI API samples AddOptionsTab, DragDropApi, PreviewControlSimple, ProgressWatcher and the slides I used.

Asynchronous Interactions

Arnošt Löbel already presented on this topic at Autodesk University 2011 in his class CP5381 'Asynchronous Interactions and Managing Modeless UI with the Autodesk Revit API', as mentioned in the overview of the AU 2011 Revit and AEC API sessions.

In this class, Arnošt also covered the new external events and demonstrated his ModelessForm_ExternalEvent, ModelessForm_IdlingEvent (ModelessDialog) and WorkThread (MultiThreading) SDK samples.

The conclusion of the presentation is the following pledge that we are all strongly recommended to take:

Here is a snapshot of the entire slide deck. This is not the final version, which will be posted soon after the end of DevCamp.

IFC Export Open Source

Angel Velez is senior principal engineer in the Revit development team and presented on the IFC exporter open source project, covering the following main topics:

Here is the snapshot of his slides.

Revit MEP API

The Revit MEP API is a recurring topic of mine at Autodesk University, and I presented on this once again last year in my session CP4453 'Everything in Place with Autodesk Revit MEP Programming', as mentioned in the overview of the AU 2011 Revit and AEC API sessions. The AU materials and the sample code that I use there was already published and discussed.

In preparation for the sessions here in Waltham, I also discussed the Revit 2013 MEP API and completed the migration of the ADN MEP sample code to Revit 2013.

Here is the slide deck I used today, based on the AU one and updated for Revit MEP 2013.

Please note again that all the materials provided above are just snapshots. Soon after the conference completes, the complete and final materials will be published.

Winding Down and Winding Up Again

In the evening we went out for a DevTech dinner, Japanese and Chinese cuisine. It was great to hang out with the guys for a while and wind down a bit.

Next stop is the DevLab tomorrow with a record number of almost fifty registered participants, so we will be way busy! Time to wind up again. No rest for the wicked! These events are always great, and I'm looking forward to it a lot.