A Simpler Dockable Panel Sample

Here is a very nice little sample on using the new Revit 2014 dockable panels by Håkan Wikemar of
AEC, Sweden.

In Håkan’s words, it is close to the DockableDialogs SDK sample but easier to follow.

The DockableDialogs SDK sample demonstrates modeless dialog design, external events, and the new dockable dialogs UI API framework.

Håkan’s DockableDialog sample described here focuses purely on the dockable panel functionality and nothing else.

One thing to note is that you need to register the panel before it can be displayed, and this needs to happen in a zero document state, i.e. with no project open.

In order for the external application ribbon panel to be accessible at all in zero document state, it needs to implement and register an availability class, and the associated command has to use manual transaction mode, as explained in
enabling ribbon items in zero document state.

Once this is done, the panel appears like this:

Register dockable panel in zero document state

If you try to register it twice, an exception is thrown, naturally, saying “Cannot register the same dockable pane ID more than once. Parameter name: id”.

Once the panel has been registered and a project activated, the commands to show and hide the dockable panel are presented:

Show dockable panel command in project document

Those two commands can happily use read-only transaction mode, since they do not modify the database in any way.

The dockable panel is initially displayed sharing the same floating window as the project browser:

Dockable panel in project browser floating window

It can be dragged off that pane to its own individual floating window:

Dockable panel in separate floating window

Clicking ‘Hide’ removes it again.

Here is the full source code, which unfortunately sports rather long lines.
You can copy to a text editor or view the HTML source to see them in full, or simply download the zip file below:


public class Ribbon : IExternalApplication
{
  public Result OnStartup(
    UIControlledApplication a )
  {
    a.CreateRibbonTab( "AEC LABS" );
 
    RibbonPanel AECPanelDebug
      = a.CreateRibbonPanel( "AEC LABS", "AEC LABS" );
 
    string path = Assembly.GetExecutingAssembly().Location;
 
    #region DockableWindow
    PushButtonData pushButtonRegisterDockableWindow = new PushButtonData( "RegisterDockableWindow", "RegisterDockableWindow", path, "DockableDialog.RegisterDockableWindow" );
    pushButtonRegisterDockableWindow.LargeImage = GetImage( Resources.green.GetHbitmap() );
    pushButtonRegisterDockableWindow.AvailabilityClassName = "DockableDialog.AvailabilityNoOpenDocument";
    PushButtonData pushButtonShowDockableWindow = new PushButtonData( "Show DockableWindow", "Show DockableWindow", path, "DockableDialog.ShowDockableWindow" );
    pushButtonShowDockableWindow.LargeImage = GetImage( Resources.red.GetHbitmap() );
    PushButtonData pushButtonHideDockableWindow = new PushButtonData( "Hide DockableWindow", "Hide DockableWindow", path, "DockableDialog.HideDockableWindow" );
    pushButtonHideDockableWindow.LargeImage = GetImage( Resources.orange.GetHbitmap() );
    //IList<RibbonItem> ribbonpushButtonDockableWindow = AECPanelDebug.AddStackedItems(pushButtonRegisterDockableWindow, pushButtonShowDockableWindow, pushButtonHideDockableWindow);
 
    RibbonItem ri1 = AECPanelDebug.AddItem( pushButtonRegisterDockableWindow );
    RibbonItem ri2 = AECPanelDebug.AddItem( pushButtonShowDockableWindow );
    RibbonItem ri3 = AECPanelDebug.AddItem( pushButtonHideDockableWindow );
    #endregion
 
    return Result.Succeeded;
  }
 
  public Result OnShutdown(
    UIControlledApplication a )
  {
    return Result.Succeeded;
  }
 
  private System.Windows.Media.Imaging.BitmapSource GetImage(
    IntPtr bm )
  {
    System.Windows.Media.Imaging.BitmapSource bmSource
      = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
        bm,
        IntPtr.Zero,
        System.Windows.Int32Rect.Empty,
        System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions() );
 
    return bmSource;
  }
}
 
/// <summary>
/// You can only register a dockable dialog in "Zero doc state"
/// </summary>
public class AvailabilityNoOpenDocument
  : IExternalCommandAvailability
{
  public bool IsCommandAvailable(
    UIApplication a,
    CategorySet b )
  {
    if( a.ActiveUIDocument == null )
    {
      return true;
    }
    return false;
  }
}
 
/// <summary>
/// Register your dockable dialog
/// </summary>
[Transaction( TransactionMode.Manual )]
public class RegisterDockableWindow
  : IExternalCommand
{
  MainPage m_MyDockableWindow = null;
 
  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    DockablePaneProviderData data
      = new DockablePaneProviderData();
 
    MainPage MainDockableWindow = new MainPage();
 
    m_MyDockableWindow = MainDockableWindow;
 
    //MainDockableWindow.SetupDockablePane(me);
 
    data.FrameworkElement = MainDockableWindow
      as System.Windows.FrameworkElement;
 
    data.InitialState = new DockablePaneState();
 
    data.InitialState.DockPosition
      = DockPosition.Tabbed;
 
    //DockablePaneId targetPane;
    //if (m_targetGuid == Guid.Empty)
    //    targetPane = null;
    //else targetPane = new DockablePaneId(m_targetGuid);
    //if (m_position == DockPosition.Tabbed)
 
    data.InitialState.TabBehind = DockablePanes
      .BuiltInDockablePanes.ProjectBrowser;
 
    DockablePaneId dpid = new DockablePaneId(
      new Guid( "{D7C963CE-B7CA-426A-8D51-6E8254D21157}" ) );
 
    commandData.Application.RegisterDockablePane(
      dpid, "AEC Dockable Window", MainDockableWindow
      as IDockablePaneProvider );
 
    commandData.Application.ViewActivated
      += new EventHandler<ViewActivatedEventArgs>(
        Application_ViewActivated );
 
    return Result.Succeeded;
  }
 
  void Application_ViewActivated(
    object sender,
    ViewActivatedEventArgs e )
  {
    m_MyDockableWindow.lblProjectName.Content
      = e.Document.ProjectInformation.Name;
  }
}
 
/// <summary>
/// Show dockable dialog
/// </summary>
[Transaction( TransactionMode.ReadOnly )]
public class ShowDockableWindow : IExternalCommand
{
  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    DockablePaneId dpid = new DockablePaneId(
      new Guid( "{D7C963CE-B7CA-426A-8D51-6E8254D21157}" ) );
 
    DockablePane dp = commandData.Application
      .GetDockablePane( dpid );
 
    dp.Show();
 
    return Result.Succeeded;
  }
}
 
/// <summary>
/// Hide dockable dialog
/// </summary>
[Transaction( TransactionMode.ReadOnly )]
public class HideDockableWindow : IExternalCommand
{
  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    DockablePaneId dpid = new DockablePaneId(
      new Guid( "{D7C963CE-B7CA-426A-8D51-6E8254D21157}" ) );
 
    DockablePane dp = commandData.Application
      .GetDockablePane( dpid );
 
    dp.Hide();
    return Result.Succeeded;
  }
}

In case you overlooked any of the following aspects, here is a list of the functionality demonstrated in the relatively few lines of code above:

  • Implementation of an external application and several external commands in the same assembly
  • Creation and population of a custom ribbon tab
  • Creation and population of a custom ribbon panel with command buttons
  • Loading of button images from .NET assembly resources
  • Implementation of a command availability class
  • Static enabling a command in zero document state
  • Dynamic toggling of command availability based on document state

Here is
DockableDialog.zip containing
the complete source code, Visual Studio solution and add-in manifest of this external application.

Many thanks to Håkan for this nice sample!

Addendum: Guy Robinson of redbolts.com adds to this and says:

I noticed the post on DockablePanes and Håkan’s comment about finding the SDK sample confusing.
I did too.
I’ve been testing some UI ideas for a new app today and posted a basic version of something similar if interested.

I didn’t want a register command on the ribbon and wanted more abstraction.
For instance, I didn’t want the Page inheriting IDockablePaneProvider.

Therefore, registration is in the external application and uses some extension methods.

It is not perfect; it would require use of the Win32 API to capture the dock hiding/showing state if hidden from the dock itself.

Anyway, anyone interested can have a look at my
DockableUITest project on GitHub.

Addendum 2: Make sure you
do not reuse an existing GUID if
you decide to copy and paste the code above.

Several people simply copied the code above into their own project without replacing the existing GUID.

Now they suffer the consequences.

Avoid similar pain yourself.


Comments

28 responses to “A Simpler Dockable Panel Sample”

  1. Good Post Jeremy. I’m showcasing this feature next week at RTCAUS in Auckland in a Lab.
    2 Points to be aware of:
    1. The docked form must be a WPF form.
    2. There is a bug in the Revit API in the RTM version that causes issue when you interact with the Active View. For example if you want to return the activeView.name, or placing families or posting a command like “PostCommand(RevitCommandId.LookupPostableCommandId(PostableCommand.Wall))”, it will fail.
    I have been advised that this has now been fixed in the internal build of Revit and hope to see that in Web Update 1.
    Cheers
    Phillip

  2. Dear Phillip,
    Thank you for the appreciation! An honour, coming from you :-)
    Thank you also for the additional notes.
    If you would like to share your RTCAUS material to a wider audience, I will happily take a look and see whether it can be published here as well.
    Good luck with your presentations!
    Cheers, Jeremy.

  3. Jeremey – You know how to flatter people lol.
    I will certinally forward through my material once it is finished :)

  4. Graham Cook Avatar
    Graham Cook

    Jeremey – Thanks for this post on the excellent dockable panels addition to the 2014 API. I’ve based my implementation around the code in this post but have put the registration of the panel in the Application Startup event. What I’ve noticed though with both implementations is that if the first document loaded in the session is closed (with a second document already opened) then the panel closes with it. Any subsequent attempts to reload the panel by calling ShowDockableWindow errors on line DockablePane dp = commandData.Application.GetDockablePane( dpid ); saying that the requested dockable pane has not yet been created. So although the panel was created in a zero document state, it does seem to be linked to the first document that opens. This is easily proved by opening a documentA, ensure the panel is displayed, open documentB, close documentA, now try and reload the panel. Maybe this is linked with the bug that Phillip Miller is referring to above or maybe it’s by design and I’m missing a trick to keep the panel open / available?
    Regards
    Graham Cook

  5. Dear Graham,
    Thank you for reporting this!
    This behaviour has been logged as SPR #237061 [DockablePane does not allow to go back to zero document state] and is being looked at.
    I added notes of your observation to that case.
    Cheers, Jeremy.

  6. Hi Jeremy,
    Thanks for this sample of DockableDialog. I installed it. And wow, this looks nice.
    However, it seems that sometimes Revit crashes (An unrecoverable error has occured.) because of this.
    The only thing I do is run the plugin, slide the window in a certain place and then try to do some modelling. Then for some reason it crashes. However, not always!
    Did I do something wrong?
    Greetings Renzo

  7. Hi Jeremy,
    Finally I succeeded in getting this sample to work for me and changed it for my needs. Thanks to your explaination.
    Now, I created a button on the WPF form that must get the selected Revit objects.
    In some way I have to pass the Application to the WPF form. Somehow, I can not solve this step. Can you give me an idea for that…
    Greetins, Renzo

  8. Good morning,
    I created a dockable panel for a new add-in and it works, But when I close the last view of an open document, Revit doesn’t go to the “recent files” view automatically and the document appear in stand-by. I tried also your example and it works in the same way. Is there a way to fix this “bug”?
    Could you help me?
    Thank you very much in advance,
    Best regards

  9. Hi
    I experience the same annoying problem.
    Any help or hints will be much appreciated.

  10. Dear Renzo,
    Thank you for our conversations elsewhere and for posting this comment here.
    As said, you can look at the existing dockable panel samples.
    You need to understand that they live in a modeless context and do not have access to a valid Revit API context.
    One way to obtain one is to subscribe to the Idling event.
    I describe this more fully here:
    http://thebuildingcoder.typepad.com/blog/2013/09/no-command-launching-from-dockable-panel.html
    Cheers, Jeremy.

  11. Alexander Avatar
    Alexander

    Hello,
    Very usefull sample, but I have several questions.
    First, I use dockable panel and I need to change ActiveView. It works fine in first change but after that ActiewView equals null and I get ArgumentException (Value cannot be null. Parameter name: Element). Could you help me?
    Second, is there any way to set dockable panel minimum size?
    Best regards

  12. Dear Alexander,
    Thank you for your appreciation. Glad you like it.
    I wonder how you are trying to access the active view.
    Basically, in a dockable panel, you are in a modeless state, so you cannot access it at all.
    And no, I am not aware of any way to limit the dockable panel size. Maybe some .NET functionality can help you implement that.
    Cheers, Jeremy.

  13. Dear Luca and Henrik,
    Thank you for reporting this!
    I the first customer ship version of Revit 2014, one way to work around this is to close it through the close button accessed from the Big “R” in the top left corner. Sorry about that.
    The good news, though: it is fixed in Revit 2014 Update Release 2:
    http://thebuildingcoder.typepad.com/blog/2013/11/revit-2014-update-release-2.html
    Cheers, Jeremy

  14. Jeremy I’m a little confused how you are registering the dockable dialog in your example.
    In RegisterDockableWindow.Execute() you initialize and setup a DockablePaneProviderData object, but then never reference it again. It looks like the DockablePaneProviderData is just a simple class with a couple properties.
    I tried commenting out all the code related to the DockablePaneProviderDataand the add-in seemed to work the same.

  15. Dear Eric,
    Please simply debug the sample code and see for yourself what is called and what not.
    There may very well be some redundant code in there. If so, please ignore it. If it works well with it commented out, then congratulations! Trust your senses!
    Cheers, Jeremy.

  16. Dear Jeremy,
    I have questions same as Alexander,
    external events framework can’t access the active view?

  17. Is there a good reason why the SDK sample has a register panel button instead of automatically registering as one user in the comments suggests?

  18. Dear Randall,
    I don’t remember off-hand.
    I do remember that I found the SDK sample overly complex, though, and therefore prefer this one.
    Cheers, Jeremy.

  19. Hi Jeremy,
    How can I create two dockable panels in the same addin?

  20. Ok, don’t worry. I got it.

  21. Dear Louis,
    Cool. Congratulations.
    Cheers, Jeremy.

  22. I have tried to rewrite this in vb, and everything works apart from the line of the event handler for ViewActivated, in vb I have this:
    commandData.Application.ViewActivated += New EventHandler(Of ViewActivatedEventArgs)(AddressOf Application_ViewActivated)
    but I get the following error:
    Error 1 ‘Public Event ViewActivated(sender As Object, e As Autodesk.Revit.UI.Events.ViewActivatedEventArgs)’ is an event, and cannot be called directly. Use a ‘RaiseEvent’ statement to raise an event.
    So I have commented it out and it works apart from the xaml is blank in the dockable window.
    Does anyone have a bit of guidance on how to get started with a RaiseEvent? I promise I am looking into it myself, but if anyone has some guidance that would be great.
    Drew

  23. OK, this fixed it:
    AddHandler commandData.Application.ViewActivated, AddressOf Application_ViewActivated
    so that was easy :-)

  24. Is it possible to post links? I have an example of the vb project that is not working correctly (not showing the xaml) but each time I try to post a link to it the comment is rejected without an error.
    Drew

  25. Dear Drew,
    Yes, you can certainly add links in the comments here. I do so all the time.
    You can post the sample in any one of various places and add a link to it in a comment here.
    Two typical repositories that others have used in the past include gist and dropbox:
    https://gist.github.com
    https://www.dropbox.com
    On the other hand, since I am anything but a VB aficionado, I would suggest that you be better off to submit the issue as a new discussion thread in the Revit API discussion forum, so that others can chip in as well:
    http://forums.autodesk.com/t5/Revit-API/bd-p/160
    The discussion forum supports inline images (actually, so do the comments here) and attachments, so you can provide your complete sample code and VB solution right there on the spot.
    I hope this helps.
    Cheers, Jeremy.

  26. Is it possible to have the DockablePane modify the document? for example I have a dockablepane populated based on the content of a project and then a handler to fire when I click on one of the things in the dockablepane, but I get an error that starting a transaction from an external application running outside of API content is not allowed.
    In the past I have had my xaml set a value in the addin then upon closing the xaml the addin would run the appropriate command based on the value, but with the dockable pane I would like to keep the panel on the screen rather than ceed control back to the addin…

  27. Thanks Jeremy, it was perhaps my browser yesterday not being my friend. Thanks for the clarification.
    Drew

  28. Dear Drew,
    Of course it is possible to modify the current document based on the interaction with the dockable panel, and a very common use case it is indeed.
    You simply must be aware of the fact that the dockable panel lives in a modeless context and has no direct access to a valid API context.
    You can use the Idling event (less recommended) or an external event (much better) to gain access to a valid Revit API context.
    There are lots of examples demonstrating how to do this:
    http://thebuildingcoder.typepad.com/blog/about-the-author.html#5.28
    Most of them are not directly related to the dockable panels, but the principles are exactly the same. Modeless is modeless.
    Here are my topics specific to dockable panels:
    http://thebuildingcoder.typepad.com/blog/about-the-author.html#5.4
    If you implement something nice and simple and generic based on this, please feel free to write a guest blog post describing it. I am sure that would be helpful for others as well. Thank you!
    Cheers, Jeremy.

Leave a Reply

Discover more from Autodesk Developer Blog

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

Continue reading