Skip to content
Flying Animal

How To Remove Panel Ribbon Without Restart Revit

🕒 Published at:

Introduction

It was quite a long time when I was quite interested in issues with Revit API.

Today, I have an interesting discovery to share with you that I recently came across. It's a rather annoying glitch in the Revit API regarding hot reloading the Ribbon. This will be addressed through this article, providing you with an overview of how to hot reload a button or panel from the Ribbon within the Revit API.

Remove Panel

Removing a panel is relatively straightforward, as you can easily see in the example below, but it won't work as expected.

csharp
using AW = Autodesk.Windows;
using Autodesk.Revit.UI;
public static void RemovePanel(string tabName, AW.RibbonPanel panel)
    {
        AW.RibbonControl ribbon = AW.ComponentManager.Ribbon;
        foreach (AW.RibbonTab tab in ribbon.Tabs)
        {
            if (tab.Name == tabName)
            {
                tab.Panels.Remove(panel);
            }
        }
    }

Although the removal command is executed, you'll encounter an error when attempting to add a panel with a similar name back to the Ribbon. This is due to the fact that the Revit API has a Private Dictionary to store RibbonItemDictionary. What you need to do is add a cleanup step for RibbonItemDictionary after removing the panel. Many thanks to @Nice3Point for this brilliant idea.

cs
using AW = Autodesk.Windows;
using Autodesk.Revit.UI;
public static void RemovePanelClear(string tabName, string panelName)
{
    AW.RibbonControl ribbon = AW.ComponentManager.Ribbon;
    AW.RibbonPanel ribbonPanel = GetPanel(tabName, panelName);
    //Remove panel
    foreach (AW.RibbonTab tab in ribbon.Tabs)
    {
        if (tab.Name == tabName)
        {
            tab.Panels.Remove(ribbonPanel);
            var uiApplicationType = typeof(UIApplication);
            var ribbonItemsProperty = uiApplicationType.GetProperty("RibbonItemDictionary",
                BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly)!;
            var ribbonItems =
                (Dictionary<string, Dictionary<string, Autodesk.Revit.UI.RibbonPanel>>)ribbonItemsProperty
                    .GetValue(typeof(UIApplication));
            if (ribbonItems.TryGetValue(tab.Id, out var tabItem)) tabItem.Remove(panelName);
        }
    }
}

Searching for a panel is also easily done within the Revit API:

cs
using AW = Autodesk.Windows;
public static AW.RibbonPanel GetPanel(string tabName, string panelName)
{
    AW.RibbonControl ribbon = AW.ComponentManager.Ribbon;

    foreach (AW.RibbonTab tab in ribbon.Tabs)
    {
        if (tab.Name == tabName)
        {
            foreach (AW.RibbonPanel panel in tab.Panels)
            {
                if (panel.Source.Title == panelName)
                {
                    return panel;
                }
            }
        }
    }

    return null;
}

UIControlledApplication vs UIApplication

With the UIControlledApplication class used in IExternalApplication and UIApplication used in IExternalCommand, it's important to note that you should use UIApplication to make changes in the Ribbon if you intend to trigger changes for a panel from a button. However, both can accomplish this task.

You can identify the Ribbon you are working with through UIApplication.

csharp
var uiApplication = commandData.Application;
var TabName = "Test";
application.GetRibbonPanels(TabName);

Or through UIControlledApplication via IExternalApplication like code below:

csharp
public Result OnStartup(UIControlledApplication a)
{

    var TabName = "Test";
    application.GetRibbonPanels(TabName);
    return Result.Succeeded;
}

To write a unified function between `UIApplication` and `UIControlledApplication`, you can use a common function through the `dynamic` approach supported in C# with the `Microsoft.CSharp` NuGet library:

```csharp
public static void Reload(dynamic application){
    var TabName = "Test";
    application.GetRibbonPanels(TabName);
}

Remove Button

And now, to be clearer, you can easily remove any unwanted button directly without needing to restart Revit.

cs
using AW = Autodesk.Windows;
public void RemoveItem(string tabName, string panelName, string itemName)
{
    AW.RibbonControl ribbon = AW.ComponentManager.Ribbon;

    foreach (AW.RibbonTab tab in ribbon.Tabs)
    {
        if (tab.Name == tabName)
        {
            foreach (AW.RibbonPanel panel in tab.Panels)
            {
                if (panel.Source.Title == panelName)
                {
                    AW.RibbonItem findItem = panel.FindItem("CustomCtrl_%CustomCtrl_%"
                                                            + tabName + "%" + panelName + "%" + itemName,
                        true);
                    if (findItem != null)
                    {
                        panel.Source.Items.Remove(findItem);
                    }
                }
            }
        }
    }
}
public AW.RibbonTab GetTab(string tabName)
{
    AW.RibbonControl ribbon = AW.ComponentManager.Ribbon;

    foreach (AW.RibbonTab tab in ribbon.Tabs)
    {
        if (tab.Name == tabName)
        {
            return tab;
        }
    }

    return null;
}

Conclusion

I hope that through this article, I have helped you address the issue of removing Ribbon Panels and Buttons within the Revit API. If you have any questions or contributions, please feel free to leave a comment below. Wishing you a wonderful day ahead!