>Blog>Custom Revit Dockable Pane registration

Custom Revit Dockable Pane registration

September 16, 2025
cover image

This article provides a step-by-step guide to creating a custom Dockable Pane for Autodesk Revit (2024 and earlier). It covers setting up the project, registering the pane, implementing the IDockablePaneProvider interface, adding a ribbon button, and managing the pane’s visibility. All examples are based on .NET Framework 4.8 and demonstrate best practices for structuring a Revit add-in.

creating the library class

Start by creating a Class Library project using .NET Framework 4.8 (compatible with Revit 2024 and earlier).
This step lays the foundation for your add-in. If you’re familiar with Revit development, this is a standard setup process. Otherwise, you can refer to the example here.

Adding core classes

Next, create at least four classes to structure your add-in:

  • Application — inherits IExternalApplication. This class is responsible for registering the DockablePane and setting up the Ribbon Panel for the command.
  • Command — inherits IExternalCommand. This class manages the visibility of the DockablePane.
  • DockablePaneProvider — inherits IDockablePaneProvider. This class defines and configures the DockablePane.
  • Page — use a Page (not a Window). For demonstration purposes, a simple “Hello Revit!” page without a ViewModel is created, since this article focuses only on DockablePane registration.

Page sample:

<Page x:Class="DockablePaneExample.ExamplePage"     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     xmlns:local="clr-namespace:DockablePaneExample"     mc:Ignorable="d"
Background="White"
d:DesignHeight="850"
d:DesignWidth="400">
   <Grid>
       <TextBlock
           FontSize="36"
           VerticalAlignment="Top"
           HorizontalAlignment="Center"
           Text="Hello Revit!" />
   </Grid>
</Page>

Externalcommand to show/hide the pane:

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System;

namespace DockablePaneExample
{
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    public class ShowCommand : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            var dockablePaneId = new DockablePaneId(new Guid("ADC789B2-3BFB-4217-9728-B6481C9791E4"));
            var dockablePane = commandData.Application.GetDockablePane(dockablePaneId);

            if (dockablePane.IsShown())
            {
                dockablePane.Hide();
            }
            else
            {
                dockablePane.Show();
            }

            return Result.Succeeded;
        }
    }
}

This code does not handle the case where no DockablePane with the specified ID exists. In such situations an exception is thrown and Revit displays a message box.

To improve architecture, a separate static class can be used to store the Guid for the DockablePaneId as a constant value. This allows referencing it from one place, simplifying maintenance and reducing the chance of errors.

Implementing Idockablepaneprovider

Below is a minimal provider implementation:

namespace DockablePaneExample
{
    public class ExampleDockablePaneProvider : IDockablePaneProvider
    {
        private readonly DockablePaneProviderData _data;

        public ExampleDockablePaneProvider(DockablePaneProviderData data)
        {
            _data = data;
        }

        public void SetupDockablePane(DockablePaneProviderData data)
        {
            data.FrameworkElement = _data.FrameworkElement;
            data.VisibleByDefault = _data.VisibleByDefault;
            data.InitialState = _data.InitialState;
        }
    }
}

Why both data and _data? How to use them correctly?
The key is the signature of RegisterDockablePane(...): it requires an IDockablePaneProvider, while all configuration for a Dockable Pane lives in DockablePaneProviderData. The provider’s SetupDockablePane receives a DockablePaneProviderData instance to populate. Hence, there are several options:

  1. Configure inside SetupDockablePane.
    Initialize DockablePaneProviderData directly within SetupDockablePane.
    Drawback: a bespoke provider is needed for every different pane configuration.
  2. Constructor injection (shown above).
    Pass a prepared DockablePaneProviderData into the provider’s constructor and copy values in SetupDockablePane.
    Benefit: the same provider type can be reused with different data objects.
  3. Delegate/callback.
    Provide a delegate that is invoked inside SetupDockablePane to fill the data.
    Benefit: high reusability and clear separation; suitable for advanced scenarios.

Registering the Dockable Pane in Application:

private static void SetupDockablePane(UIControlledApplication application)
{
    var dockablePaneData = new DockablePaneProviderData()
    {
        FrameworkElement = new ExamplePage(),
        VisibleByDefault = true,
        InitialState = new DockablePaneState
        {
            DockPosition = DockPosition.Right,
            MinimumWidth = 400
        },
    };

    var dockablePaneId = new DockablePaneId(new Guid("ADC789B2-3BFB-4217-9728-B6481C9791E4"));
    var title = "Example dockable pane";

    application.RegisterDockablePane(dockablePaneId, title, new ExampleDockablePaneProvider(dockablePaneData));
}

In this approach a preconfigured dockablePaneData is passed to RegisterDockablePane via the provider instance.

Ribbon panel and command button

Add a Ribbon Panel and a PushButton to toggle the pane:

private void SetupPanel(UIControlledApplication application)
{
    string tabName = "DockablePaneExample";
    application.CreateRibbonTab(tabName);
    RibbonPanel ribbonPanel = application.CreateRibbonPanel(tabName, "DockablePaneExample");
    AddPushButton(ribbonPanel, "Show panel", assemblyPath, "DockablePaneExample.ShowCommand");
}

private PushButton AddPushButton(RibbonPanel ribbonPanel, string buttonName, string path, string linkToCommand)
{
    var buttonData = new PushButtonData(buttonName, buttonName, path, linkToCommand);
    var button = ribbonPanel.AddItem(buttonData) as PushButton;
    return button;
}

The Application class

Remaining Application members:

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System;
using System.Reflection;

namespace DockablePaneExample
{
    public class Application : IExternalApplication
    {
        static AddInId addInId = new AddInId(new Guid("B7CC71C6-6725-4D92-8044-1F16B12631DC"));
        private readonly string assemblyPath = Assembly.GetExecutingAssembly().Location;

        public Result OnStartup(UIControlledApplication application)
        {
            SetupDockablePane(application);
            SetupPanel(application);
            return Result.Succeeded;
        }

        public Result OnShutdown(UIControlledApplication application)
        {
            return Result.Succeeded;
        }

        // other methods shown above
    }
}

With the code in place, the solution can be built, the add-in manifest added, and Revit launched.

image

That’s it — a fully custom Revit DockablePane is now implemented.

The source code from this article is available on GitHub.