OnStartup/OnShutdown RibbonControl using IExtensionApplication

OnStartup/OnShutdown RibbonControl using IExtensionApplication

ricaun
Advisor Advisor
945 Views
18 Replies
Message 1 of 19

OnStartup/OnShutdown RibbonControl using IExtensionApplication

ricaun
Advisor
Advisor

Hello,

 

I'm new in AutoCAD development but I have a lot of experience in Revit API.

 

In Revit the IExternalApplication.OnStartup is the best place to register a RibbonPanel when the application start.

 

In AutoCAD the IExtensionApplication.Initialize looks similar but the ComponentManager.Ribbon is null at that moment.

 

I see some users using Application.Idle to register the Ribbons as a solution, and found a different approach that looks better in my opinion that use the ComponentManager.ItemInitialized.

 

I create an abstract class to help with OnStartup/OnShutdown the Ribbon.

public abstract class ExtensionApplication : IExtensionApplication
{
    public virtual void Initialize()
    {
        ComponentManager.ItemInitialized += ComponentManager_ItemInitialized;
        if (ComponentManager.Ribbon is not null) ComponentManager_ItemInitialized(null, null);
        Application.QuitWillStart += Application_QuitWillStart;
    }
    public virtual void Terminate()
    {
        ComponentManager.ItemInitialized -= ComponentManager_ItemInitialized;
        Application.QuitWillStart -= Application_QuitWillStart;
    }
    private void ComponentManager_ItemInitialized(object sender, RibbonItemEventArgs e)
    {
        ComponentManager.ItemInitialized -= ComponentManager_ItemInitialized;
        OnStartup(ComponentManager.Ribbon);
    }
    private void Application_QuitWillStart(object sender, EventArgs e)
    {
        Application.QuitWillStart -= Application_QuitWillStart;
        OnShutdown(ComponentManager.Ribbon);
    }
    public abstract void OnStartup(RibbonControl ribbonControl);
    public abstract void OnShutdown(RibbonControl ribbonControl);
}

 

And my main application would be something like:

public class App : ExtensionApplication
{
    public override void OnStartup(RibbonControl ribbonControl)
    {
        ribbonControl.CreateOrSelectPanel("PanelName", "TabName");
    }
    public override void OnShutdown(RibbonControl ribbonControl)
    {
            
    }
}

 

If someone with more experience with AutoCAD API could judge my approach, any improvement would be appreciate.

 

See yaa

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Accepted solutions (2)
946 Views
18 Replies
Replies (18)
Message 2 of 19

ActivistInvestor
Mentor
Mentor
Accepted solution

Your code is well-written, but its reliance on ComponentManager events is flawed.

 

That's because the ItemInitialized event fires for every RibbonItem, including those on the QAT (Quick Access Toolbar), and on the Application menu that drops down when you click on the 'big A' in the upper-left corner of the app window. 

 

IOW, because not all RibbonItems are on the ribbon, that event fires even if the user has the ribbon turned off, and it is never created during the life of the application.

 

There are several issues related to managing application-provided ribbon content. You have to wait until the ribbon is created at startup. To do that, you handle an event that fires when the ribbon is created, which may not necessarily be when the application starts, because the user may have the ribbon turned off, and then at some point, issues the RIBBON command to display it, so if the ribbon does not exist when your startup code runs, then the RibbonServices.RibbonPaletteSetCreated event needs to be handled.

 

Another issue is that you must add your custom ribbon content not only when you first encounter the Ribbon after startup, but also after the user issues the CUI, MENULOAD, MENUUNLOAD, and CUSTOMIZE commands. That's because when that happens, the entire ribbon contents is discarded and reloaded to account for whatever changes were made to loaded CUI files and so forth, which causes anything you've added to it to be removed. So, we have another event (RibbonPaletteSet.WorkspaceLoaded) that must be handled to be notified about that, and in the handler for that event you must add all of your ribbon content to the ribbon again. So, the problem is actually a bit more complicated than it might at first seem looking at it from afar.

 

Because I've been through all of this, I built a reusable solution that allows me to deal with the problem very easily, and have shared it so others can also avoid having to deal with each of the various issues I mention about above.

 

You can have a look at the RibbonEventManager class, along with this example showing how it helps to arrive at a simplified solution. You can either adopt RibbonEventManager, or just use it as a guide to creating your own solution. The comments in the code files go even further in-depth on solving all the problems related to managing custom ribbon content.

 

You also might want to test the code you posted, as the Terminate() method and the QuitWillStart event are redundant. You should use QuitWillStart to do any finalization because Terminate() isn't called until the process exits, which is too late to do most things, including access APIs. If your code handles QuitWillStart, there is no need or purpose to acting in the Terminate() method, and in fact, it looks like you are executing your shutdown code twice.

Message 3 of 19

ricaun
Advisor
Advisor
Accepted solution

@ActivistInvestorthanks for the great explanation. 


I didn't know that is possible to remove the RIBBON in AutoCAD, and the MENULOAD force the Ribbon the recreate. AutoCAD is weird. 🙃

 

About the ComponentManager.ItemInitialized I'm using just to replace the Idle, at least looks like is a good time to know the RibbonControl is available to receive my custom Ribbon. 

 

The RibbonServices.RibbonPaletteSetCreated trigger when the RibbonControl is available, but does not work if you try to add Ribbons when the event trigger.

 

And If I add the ComponentManager.ItemInitialized inside the RibbonServices.RibbonPaletteSetCreated I know the next time ItemInitialized trigger my custom Ribbon show like expected.

 

And with the RibbonPaletteSet.WorkspaceLoaded and RibbonPaletteSet.WorkspaceUnloading to trigger my OnStartup and OnShutdown to fix the MENULOAD problem.

 

So I don't need to use the Idle event in any time, that my goal. 😊

 

Here is the full abstract ExtensionApplication.cs 

 

I tested in AutoCAD 2021, 2025 and 2026.

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 4 of 19

ActivistInvestor
Mentor
Mentor

@ricaun wrote:

 

The RibbonServices.RibbonPaletteSetCreated trigger when the RibbonControl is available, but does not work if you try to add Ribbons when the event trigger.

 

The code I referred you to uses the Idle event to delay execution of the code that populates the ribbon until it can do its job. That works well for me, and that code is being used by many without issue.

 

Using one event (ItemInitialized) to avoid having to use another event (Idle) doesn't make sense to me, and isn't something I would sanction. I'm not sure what your issue with the Idle event is, but it is routinely used by AutoCAD developers to solve many different problems, not just the ribbon issue. The other thing about using Idle to delay ribbon initialization code is that AutoCAD has finished building/rebuilding the ribbon at that point, whereas the ItemInitialized event fires when AutoCAD starts building/ rebuilding the ribbon. The other flaw with using the ItemInitialized event is that it doesn't allow customization of existing ribbon content created by AutoCAD, because when the event fires, AutoCAD hasn't finished building/rebilding the ribbon. Some of the ribbon customization I've done in the past involved adding content to existing Ribbon Tabs created by AutoCAD. Obviously, I can't do that until AutoCAD has already created the content that I want to add items to. So for that and other reasons, I prefer to add content to the ribbon after AutoCAD has fully-initialized it, which the Idle event allows me to do.

 

If you have some strange aversion to using the Idle event, you could instead use SynchronizationContext to execute your ribbon initialization code asynchronously, without the need to handle any events:

 

static void ribbonPaletteSetCreated(object sender, EventArgs e)
{
   RibbonServices.RibbonPaletteSetCreated -= ribbonPaletteSetCreated;
   var context = Autodesk.AutoCAD.Runtime.SynchronizationContext.Current;
   context.Post(_ =>
   {
      /// Add contents to ribbon here
   }, null);
}

 

There are also other reasons why I prefer to use the Idle event, that are mostly-because the Idle event is needed to deal with other problems unrelated to the ribbon, and in many of those cases its use can't be avoided.

 

So, I prefer to use just one event (Idle) to solve many problems rather than use different events to solve different problems. After you've run into the other problems that are solved using the Idle event, then my logic might be clearer..

 

You may also want to test your code because it uses both the QuitWillStart and the IExtensionApplication.Terminate() method, which is redundant. First, the Terminate() method isn't called until the host process exits, long after the ribbon has been destroyed, and most APIs are no-longer usable. If you need to do something at shutdown, use the QuitWillStart event, and don't use Terminate() because it is basically useless for anything that requires API access.

 

0 Likes
Message 5 of 19

ricaun
Advisor
Advisor

@ActivistInvestor wrote:

The code I referred you to uses the Idle event to delay execution of the code that populates the ribbon until it can do its job. That works well for me, and that code is being used by many without issue.

 

Using one event (ItemInitialized) and having to handle it hundreds of times each time the ribbon is recreated only to avoid having to use another event (Idle) that only needs to be handled once doesn't make sense to me, and isn't something I would sanction. Your latest code handles the ItemInitialized event continuously, which means your event handler is needlessly being called hundreds of times (once for each item added to the ribbon) each time the ribbon is rebuilt. Sorry, that makes no sense to me at all. I'm not sure what your issue with the Idle event is, but it is routinely used by AutoCAD developers to solve many different problems, not just the ribbon issue.

 


I guess I don't want to wait for the Idle to trigger, the ItemInitialized trigger before and in the right time I add stuff in the Ribbon.

 

With ItemInitialized is kinda at the right time, and after the trigger I unsubscribe from the ItemInitialized, only trigger for the code. Exactly the same thing you are doing with the Idle event.

 


@ActivistInvestor wrote:

If you have some strange aversion to using the Idle event, you could instead use SynchronizationContext to execute your ribbon initialization code asynchronously, without the need to handle any events:

 


Interesting, there are some difference to run Autodesk.AutoCAD.Runtime.SynchronizationContext.Current and invoke something inside Idle, I suppose the SynchronizationContext.Current.Post gonna trigger in the next Idle event.

 


@ActivistInvestor wrote:

There are also other reasons why I prefer to use the Idle event, that are mostly-because the Idle event is needed to deal with other problems unrelated to the ribbon, and in many of those cases its use can't be avoided.

 


If is not related to create the ribbon, I suppose if not gonna be a problem in my case.

 


@ActivistInvestor wrote:

So, I prefer to use just one event (Idle) to solve many problems rather than use different events to solve different problems. After you've run into the other problems that are solved using the Idle event, then my logic might be clearer..

 


I have no idea what I'm doing in AutoCAD API, I just want to have a easier and safe way to create ribbon using a IExtensionApplication and using what I know about Revit API.

 

Is kinda odd that I can find an AutoCAD way to register a ribbon IExtensionApplication, in Revit API is so normal...

 

Now thinking, make sense AutoCAD is command base, when I start learning how to use AutoCAD, never click in a RibbonButton, only shortcut/commands.

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 6 of 19

ActivistInvestor
Mentor
Mentor

@ricaun wrote:

I guess I don't want to wait for the Idle to trigger, the ItemInitialized trigger before and in the right time I add stuff in the Ribbon.

For only adding items to the ribbon that's fine, however some ribbon customization involves adding items to existing tabs and panels, which can't be done from an ItemInitialized event handler because it requires the ribbon to have been fully-created first.

 


@ricaun wrote:

I guess I don't want to wait for the Idle to trigger, the ItemInitialized trigger before and in the right time I add stuff in the Ribbon.

As mentioned above, some types of ribbon customization requires a fully-created ribbon, and for that one has to wait for it to happen (as in the Idle event)

 


@ricaun wrote:

Interesting, there are some difference to run Autodesk.AutoCAD.Runtime.SynchronizationContext.Current and invoke something inside Idle, I suppose the SynchronizationContext.Current.Post gonna trigger in the next Idle event.

Post() runs the delegate just before the Idle event is raised. There's no other way to execute code asynchronously on the main thread, because the code can't run until AutoCAD has finished whatever it was doing, so the SynchronizationContext.Post() method and the Idle event are just two different ways of doing the same thing. The former is easier to use because there are no events involved.

 


Is kinda odd that I can find an AutoCAD way to register a ribbon IExtensionApplication, in Revit API is so normal...

 


It's normal in AutoCAD too - You don't have to do any of this to put stuff on the Ribbon. You can just create a partial CUI file with ribbon content in it, and just load that CUI file, and AutoCAD does the work of adding it to the ribbon. The only issue with that is that you are limited to the predefined widgets that come in the box - no custom controls.

 

Message 7 of 19

ricaun
Advisor
Advisor

@ActivistInvestor I really appreciate you knowledge ribbons and AutoCAD, the OnStartup/OnShutdown to create/remove ribbon looks great.


I wonder if you play with Images to change to light/dark theme in AutoCAD automatically, to support high resolution images and so one.


Because I already did that in Revit was easy to import to AutoCAD. Here is my sample Addin 😊

 

frame0329.pngframe0369.png

 

AutoCADAddin - 2025-05-07 19-01-5 - 10.gif

 

 

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 8 of 19

ActivistInvestor
Mentor
Mentor

Not sure what your question is about. It's not hard to change the theme through code. 

0 Likes
Message 9 of 19

ricaun
Advisor
Advisor

@ActivistInvestor wrote:

Not sure what your question is about. It's not hard to change the theme through code. 


How are you doing the theme change check? Using Application.SystemVariableChanged?

* https://www.keanw.com/2014/04/supporting-autocad-2015s-dark-theme.html

 

And to update the image in the RibbonButton after the theme changed how usually you do?

 

Don't know if AutoCAD has some automatic magic for that.

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 10 of 19

ActivistInvestor
Mentor
Mentor

How to deal with theme-dependent images depends on how your ribbon content is defined (e.g., via XAML or code), but there's no built-in way to automatically switch images when the theme changes.

 

This shows how to detect color theme changes without the overhead of watching system variables (which is not recommended):

using Autodesk.AutoCAD.Internal;

public static class ActiveTheme
{
   static ActiveTheme()
   {
      ActiveThemeColor.Instance().ColorThemeChanged += colorThemeChanged;
   }

   private static void colorThemeChanged(object sender, EventArgs e)
   {
      // TODO: Handle color theme change as required.
   }

   // Get current theme and use IsDark property:
   
   public static PaletteTheme CurrentTheme =>
      ThemeManager.PaletteSettings.CurrentTheme;
}

 

0 Likes
Message 11 of 19

ricaun
Advisor
Advisor

Nice!  I actually was the ComponentManager.PropertyChanged, to check if CurrentThemePropertyName changed.

 

The ActiveThemeColor looks great, it's has the ActiveThemeColor.CurrentTheme with some enum.

 

There are some issues with using this?

public static bool IsLight => ActiveThemeColor.CurrentTheme == ColorThemeEnum.Light;

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 12 of 19

ActivistInvestor
Mentor
Mentor

The CurrentTheme property from the class in the previous post is an instance of a PaletteTheme, not an enum.

 

PaletteTheme has an IsDark property:

bool isLight = !ActiveTheme.CurrentTheme.IsDark;

 

PaletteTheme also has a Color property for each UI element:

 

ActivistInvestor_1-1746911202209.gif

 

 

 

 

0 Likes
Message 13 of 19

ricaun
Advisor
Advisor

Yes, I notice there is the ribbon colors. I probably gonna use the enum just because is the same class. Just need to know if is light.

 


@ActivistInvestor wrote:

PaletteTheme also has properties for each color element:

 

ActivistInvestor_1-1746911202209.gif

 

 


Where I can find this useful dockable? Is some kinda or plugin to visualize internal AutoCAD proprieties?

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 14 of 19

ActivistInvestor
Mentor
Mentor

What I said above is not entirely true, about managing theme colors and theme changes for images.

 

Rather than using System.Windows.Controls.Image, you can use this derivative:

 

     Autodesk.Windows.Palettes.Controls.ThemedImage

 

It has LightSource and DarkSource properties that can be assigned to the images to use for light and dark themes, and will automatically update the Image when the theme changes (no need to handle ThemeChanged events).

 


@ricaun wrote:

Yes, I notice there is the ribbon colors. I probably gonna use the enum just because is the same class. Just need to know if is light.

 

Where I can find this useful dockable? Is some kinda or plugin to visualize internal AutoCAD proprieties?

 


As for the control in the clip, that's just a floating PaletteSet that hosts a WinForms UserControl containing a PropertyGrid (which is themed manually using the aforementioned PaletteTheme class). Yes, I made it to display properties of managed objects mostly for debugging, Unfortunately, sharing that control would be very difficult due to many dependencies it has on other code.

Message 15 of 19

ricaun
Advisor
Advisor

@ActivistInvestor wrote:

As for the control in the clip, that's just a floating PaletteSet that hosts a WinForms UserControl containing a PropertyGrid (which is themed manually using the aforementioned PaletteTheme class). Yes, I made it to display properties of managed objects mostly for debugging, Unfortunately, sharing that control would be very difficult due to many dependencies it has on other code.

Nice!

 

I create a simple PaletteSet with a TextBox just to show the Debug.WriteLine/Console.WriteLine just for debugging, one issue that I didn't figure out is how to force the PaletteSet to stay close or open based on the last user section. Or force to be dockable in the right when first time registered, in my sample at start floating in the left corner.... I noticed the Guid kinda force to remember the location after the first time.

 

About your object debugging dockable, I though was something opensource like in Revit, we have the RevitLookUp. Probably one day AutoCADLookUp will be created to snoop AutoCAD objecs.

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 16 of 19

ActivistInvestor
Mentor
Mentor

To control the initial docking location of a PaletteSet, you set the Dock property after making the PaletteSet visible:

 

public class MyPalette : PaletteSet
{
   public MyPalette()
   {
      /// Initialze here, before making visible
      this.DockEnabled = DockSides.Left | DockSides.Right;
      this.Visible = true;
      /// Must be done after setting Visible = true:
      this.Dock = DockSides.Right;
   }
}

 

In regards to RevitLookUp, that is a derivative of RvtMgdDbg, which in-turn, is a descendent of MgdDbg, the original tool that was originally written specifically for AutoCAD, and was for the most-part a port of the original native ArxDbg tool to managed code.

 

I don't use them as they didn't do what I needed, so I wrote my own snooping tool:

ActivistInvestor_0-1747011266890.png

 

ActivistInvestor_0-1747013213736.png

 

That tool was built back during the Windows 7 era, hence the Windows 7-style UI. I've been spending a little time here and there porting it to .NET 10, and a more modern UI, along with support for browsing UI's like the Ribbon and any managed WinForms or WPF control or window.

 

But, it still has a long way to go:

ActivistInvestor_0-1747011909435.png

 

 

0 Likes
Message 17 of 19

ricaun
Advisor
Advisor

@ActivistInvestor I create a new topic about PaletteSet you probably could help in there 🤗

 


@ActivistInvestor wrote:

In regards to RevitLookUp, that is a derivative of RvtMgdDbg, which in-turn, is a descendent of MgdDbg, the original tool that was originally written specifically for AutoCAD, and was for the most-part a port of the original native ArxDbg tool to managed code.

 


Nice, RvtMgdDbg is the first version.

 

The MgdDbg does not have a installer, a bundle to copy or something?

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes
Message 18 of 19

ActivistInvestor
Mentor
Mentor

I'm not sure about MgdDbg because I don't use it but it most likely can be used by NETLOAD command to load the .DLL.

0 Likes
Message 19 of 19

_gile
Consultant
Consultant

@ricaun

You could also try Inspector snoop tool (a .bundle installer is provided).



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes