Author Topic: Add Custom RibbonTab at Startup  (Read 8850 times)

0 Members and 1 Guest are viewing this topic.

BlackBox

  • King Gator
  • Posts: 3770
Add Custom RibbonTab at Startup
« on: April 18, 2013, 05:27:37 PM »
Lately, I've been looking to integrate some UI aspects to plug-ins, and in preparing some projects for Autodesk Exchange (which require some form of UI via CUIx or code), stumbled upon an issue that I cannot seem to find documentation on.

I know that different UI components are available to be modified programmatically at different times, for example, ComponentManager.ApplicationMenu.Opening works, but others such as QAT require Application.Idle to succeed.

The issue presented by Autoloader (Autodesk Exchange requirement), is the CUIx... Specifically, each plug-in providing a given RibbonTab, and applicable RibbonPanel(s). Non issue if each plug-in create it's own RibbonTab, but when offering multiple, single panel plug-ins seems cumbersome, and unnecessary.

I know that partial CUIx RibbonPanels can load into the same RibbonTab, given matching RibbonTab.Id as Stephen Preston shows here, but this is dependent on said RibbonTab.Id existing within the Main CUIx prior to Autoloader loading a plug-in's associated CUIx... Matching RibbonTab.Id from partial CUIx alone is not enough, and results in multiple, like named RibbonTabs.

I am now looking to use a code snippet (that once working will be included in all my projects), to first look for my RibbonTab (via FindTab()), and if not found add my RibbonTab to Main CUIx, and only then will my code programmatically load the associated CUIx for my plug-in as needed.

I have tried several different events, given ComponentManager.Ribbon Object, and found Application.Idle to be successful where Object-specific events such as Initialized, and Loaded were not.

My goal is to use Initialize() to add my RibbonTab to Main CUIx, and Terminate() to remove it.

Admittedly, I find it silly that the RibbonTab (with matching Id) must reside within Main CUIx (since a Tab from any CUIx, Main, Enterprise, or Partial can be added to workspace)... I speculate the only alternative as being to implement my RibbonTab, and all RibbonPanels via code instead of simply including a CUIx?

TIA
"How we think determines what we do, and what we do determines what we get."

n.yuan

  • Bull Frog
  • Posts: 347
Re: Add Custom RibbonTab at Startup
« Reply #1 on: April 19, 2013, 11:08:21 AM »
You can dynamically add Ribbon tab (then panels, then buttons, text box... inside the panels. There is a AU class provides by Wayne Brill on this topic. I could not find its link on the net any more, but I have the courseware in *.doc format. If you are interested in, send me a private message (click my name) with your email address, I can forward the courseware to you.

I just recently did the same thing (loading a ribbob tab for my set of tool when the tools' assembly is loaded), and it works well for me.

The clode look like (simplified version):

Code: [Select]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Autodesk.Windows;
using Autodesk.AutoCAD.Ribbon;
using Autodesk.AutoCAD.ApplicationServices;

namespace LegalDrafting
{
    public class LegalDraftingRibbonBuilder
    {
        private const string TAB_TITLE = "FSJ CAD Tools";
        private const string TAB_ID = "FSJ_Custom_Tab";
        private const string PANEL_TITLE = "Legal Drafting Tools";

        private RibbonControl _ribbonControl;
        private RibbonTab _ribbonTab;
        private RibbonPanel _ribbonPanel;
        private RibbonPanelSource _panelSource;

        public LegalDraftingRibbonBuilder()
        {
            //Create ribbon tab: FSJ_Custom_Tab
            _ribbonControl = RibbonServices.RibbonPaletteSet.RibbonControl;
           
            _ribbonTab = GetExistingFSJCadToolsRibbonTab();
            if (_ribbonTab == null)
            {
                //Create new tab
                _ribbonTab = new RibbonTab();
                _ribbonTab.Title = TAB_TITLE;
                _ribbonTab.Id = TAB_ID;

                _ribbonControl.Tabs.Add(_ribbonTab);
            }
        }

        public void CreateLegalDraftingRibbonPanel()
        {
            //get existing or create new ribbon panel
            if (GetExistingLegalDraftingRibbonPanel()) return;

            //Create new panel, if not exists
            _panelSource = new RibbonPanelSource();
            _panelSource.Title = PANEL_TITLE;

            _ribbonPanel = new RibbonPanel();
            _ribbonPanel.Source = _panelSource;

            _ribbonTab.Panels.Add(_ribbonPanel);

            //Add buttons
            RibbonButton button = new RibbonButton();
            button.Text = "Legend";
            button.CommandParameter = "LegalLegend ";
            button.ShowText = true;
            button.CommandHandler = new LegalDraftingRibbonCommandHandler();

            RibbonToolTip toolTip = new RibbonToolTip();
            toolTip.Command = "LegalLegend"; ;
            toolTip.Title = "Command: LegalLegend";
            toolTip.Content = "Generate legal plan's legend list";
            toolTip.ExpandedContent = "Generate legal plan's legend list " +
                "by searching drawing content visible in the viewports of layouts";
            button.ToolTip = toolTip;

            _panelSource.Items.Add(button);
            _panelSource.Items.Add(new RibbonRowBreak());
        }

        #region private methods

        private RibbonTab GetExistingFSJCadToolsRibbonTab()
        {
            //Find existing ribbon tab
            foreach (var t in _ribbonControl.Tabs)
            {
                if (t.Title.ToUpper() == TAB_TITLE.ToUpper() &&
                    t.Id.ToUpper() == TAB_ID.ToUpper())
                {
                    return t;
                }
            }

            return null; 
        }

        private bool GetExistingLegalDraftingRibbonPanel()
        {
            //Find existing panel
            foreach (var p in _ribbonTab.Panels)
            {
                if (p.Source.Title.ToUpper() == PANEL_TITLE.ToUpper())
                {
                    _ribbonPanel = p;
                    _panelSource = p.Source;
                    return true;
                }
            }

            return false;
        }

        #endregion
    }

    public class LegalDraftingRibbonCommandHandler : System.Windows.Input.ICommand
    {

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            RibbonButton btn = parameter as RibbonButton;
            if (btn != null)
            {
                Document dwg = Autodesk.AutoCAD.ApplicationServices.Application.
                    DocumentManager.MdiActiveDocument;

                dwg.SendStringToExecute((string)btn.CommandParameter, true, false, true);
            }
        }
    }
}

Then in the IExtensionApplication implementing code:

Code: [Select]
#region IExtensionApplication
       
        public void Initialize()
        {   
            Document dwg = Application.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
            try
            {
                ed.WriteMessage("\nLoading legal drafting utility...");

                //Get database connection
                _connectionString = SetConnectionString();

                //Load ribbon panel for legal drafting command access
                Autodesk.AutoCAD.Ribbon.RibbonServices.RibbonPaletteSetCreated +=
                    new EventHandler(RibbonServices_RibbonPaletteSetCreated);
               
            }
            catch(System.Exception ex)
            {
                ed.WriteMessage("\nLoading legal drafting utility loaded failed:");
                ed.WriteMessage("\n{0}", ex.ToString());
            }

            Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
        }

        private void RibbonPaletteSet_Loaded(object sender, EventArgs e)
        {
            LoadLegalDraftingRibbonPanel();
        }

        private void RibbonServices_RibbonPaletteSetCreated(object sender, EventArgs e)
        {
            Autodesk.AutoCAD.Ribbon.RibbonServices.RibbonPaletteSet.Loaded +=
                new EventHandler(RibbonPaletteSet_Loaded);
        }

        private static void LoadLegalDraftingRibbonPanel()
        {
            Editor ed = null;
            Document dwg = Application.DocumentManager.MdiActiveDocument;
            if (dwg != null) ed = dwg.Editor;

            try
            {
                LegalDraftingRibbonBuilder rib = new LegalDraftingRibbonBuilder();
                rib.CreateLegalDraftingRibbonPanel();
                if (ed != null)
                {
                    ed.WriteMessage("\nFSJ Custom CAD ribbon tab loaded.");
                }
            }
            catch (System.Exception ex)
            {
                if (ed != null)
                {
                    ed.WriteMessage("\nLoading FSJ Custom CAD ribbon tab failed\n{0}.", ex.ToString());
                }
            }
        }

        public void Terminate()
        {
           
        }

        #endregion

Notice the Ribbon is actually loaded in event handler, not directly in the Initialize(). That is because then Initialize() is called, AutoCAD's Ribbon control may not be ready to be manipulated. We can only start adding our custom ribbon table when RibbonPaletteSet is fully loaded. This would not be a problem if you load custom ribbon tab vis command method, but with Initialize(), you have to wait until the Ribbon is fully loaded, hence the loading via event handler.

Hope this helps.

BlackBox

  • King Gator
  • Posts: 3770
Re: Add Custom RibbonTab at Startup
« Reply #2 on: April 20, 2013, 01:20:15 PM »
n.yuan, thank you for your reply... I am in the process of testing that now, but seem to be having some Autoloader-specific trouble. More on that below.



Continuation from another thread here:

Methinks 'the best way' of loading one's assembly should depend on the nature of the code... You don't want to NETLOAD an assembly that hooks RibbonServices.RibbonPaletteSetCreated Event to fully initialize, as by the time you're able to enter NETLOAD that event has already come and gone.

There seems to be a lot of confusion about ribbon initialization.  It's pretty simple, and you don't have to concern yourself with how/when your app is loaded.

If when your app is loaded, the ribbon exists, then you add a handler to the Application.Idle event, and the first time the event fires, you remove the Idle event handler so it doesn't fire again, and you initialize your ribbon components.

If upon loading of your app the ribbon doesn't exist, then you hook the RibbonPalleteSetCreated event. If/when that event fires, you add a handler to the Application.Idle event, and when that event fires, remove that handler from the Idle event and initialize your ribbon components.

So, in all cases, you are doing initialization in a handler for the Idle event, and the only question is when to add the Idle event handler, which depends on whether the ribbon exists when your app is loaded.

I haven't seen a case where that fails, including when the ribbon exists but is not visible. If there is some issue with the initializing your ribbon components when the ribbon isn't visible, I'd like to know about it, as that would seem to be the only possible gotcha in this case.

Firstly, thank you Tony for confirming what I have experienced via Application.Idle event's success.

When only evaluating if a RibbonTab is successfully created, the code works fine, but is not able to accommodate the Autoloader requirement I am trying to meet by adding said RibbonTab prior to other partial CUIx being loaded into UI.

I have tried both, adding the RibbonTab to ComponentManager.Ribbon, and directly to Main CUIx & nested Workspaces as shown here... In both cases, I end up with non-Combined RibbonTabs with common Tab ID's.

What I am finding is that simply adding a RibbonTab to ComponentManager.Ribbon is irrelevant (for the purposes of Partial CUIx), and adding to Main CUI is required. However, given startup sequence, my doing so is still not successful as is done after Autoloader has loaded partial CUIx.

Put simply, what I am attempting to achieve, is to have sufficient code such that a 'root' RibbonTab is added (seemingly to Main CUIx) prior to partial CUIx added via Autoloader... I'm even okay with changing the CUIx component within PackageContents.xml to not load on startup, and instead by CommandInvocation, where the 'add root RibbonTab' snippet would end with SendStringToExecute if necessary... My problem is getting it to actually work.

If I absolutely have to implement RibbonTab & RibbonPanels via code, it's possible, but I'd like to avoid if reasonably possible... It's important to me to supply CUIx for user's ability to customize Workspace to their liking. This 'model' is something I am trying to work out for both internal purposes (i.e., 'root' company RibbonTab, with basic items, supplemented by Sector / Discipline-specific Partials being added), as well as Autodesk Exchange Autoloader (i.e., multiple, small plug-ins, that load individual RibbonPanels to same 'root' RibbonTab).

Perhaps I shouldn't be surprised, but given the enthusiasm with which Autodesk is 'pushing' Autoloader, one might expect that this Main CUIx requirement would have a workaround by now... Perhaps that's my naivete.



So there it is, Tony... Just like with AutoCAD Security, the only 'gotcha' I've found, is Autoloader.

As always, any insight would be most helpful.

Cheers
"How we think determines what we do, and what we do determines what we get."

TheMaster

  • Guest
Re: Add Custom RibbonTab at Startup
« Reply #3 on: April 21, 2013, 09:03:06 AM »
n.yuan, thank you for your reply... I am in the process of testing that now, but seem to be having some Autoloader-specific trouble. More on that below.



Continuation from another thread here:

Methinks 'the best way' of loading one's assembly should depend on the nature of the code... You don't want to NETLOAD an assembly that hooks RibbonServices.RibbonPaletteSetCreated Event to fully initialize, as by the time you're able to enter NETLOAD that event has already come and gone.

There seems to be a lot of confusion about ribbon initialization.  It's pretty simple, and you don't have to concern yourself with how/when your app is loaded.

If when your app is loaded, the ribbon exists, then you add a handler to the Application.Idle event, and the first time the event fires, you remove the Idle event handler so it doesn't fire again, and you initialize your ribbon components.

If upon loading of your app the ribbon doesn't exist, then you hook the RibbonPalleteSetCreated event. If/when that event fires, you add a handler to the Application.Idle event, and when that event fires, remove that handler from the Idle event and initialize your ribbon components.

So, in all cases, you are doing initialization in a handler for the Idle event, and the only question is when to add the Idle event handler, which depends on whether the ribbon exists when your app is loaded.

I haven't seen a case where that fails, including when the ribbon exists but is not visible. If there is some issue with the initializing your ribbon components when the ribbon isn't visible, I'd like to know about it, as that would seem to be the only possible gotcha in this case.

Firstly, thank you Tony for confirming what I have experienced via Application.Idle event's success.

When only evaluating if a RibbonTab is successfully created, the code works fine, but is not able to accommodate the Autoloader requirement I am trying to meet by adding said RibbonTab prior to other partial CUIx being loaded into UI.

I have tried both, adding the RibbonTab to ComponentManager.Ribbon, and directly to Main CUIx & nested Workspaces as shown here... In both cases, I end up with non-Combined RibbonTabs with common Tab ID's.

What I am finding is that simply adding a RibbonTab to ComponentManager.Ribbon is irrelevant (for the purposes of Partial CUIx), and adding to Main CUI is required. However, given startup sequence, my doing so is still not successful as is done after Autoloader has loaded partial CUIx.

Put simply, what I am attempting to achieve, is to have sufficient code such that a 'root' RibbonTab is added (seemingly to Main CUIx) prior to partial CUIx added via Autoloader... I'm even okay with changing the CUIx component within PackageContents.xml to not load on startup, and instead by CommandInvocation, where the 'add root RibbonTab' snippet would end with SendStringToExecute if necessary... My problem is getting it to actually work.

If I absolutely have to implement RibbonTab & RibbonPanels via code, it's possible, but I'd like to avoid if reasonably possible... It's important to me to supply CUIx for user's ability to customize Workspace to their liking. This 'model' is something I am trying to work out for both internal purposes (i.e., 'root' company RibbonTab, with basic items, supplemented by Sector / Discipline-specific Partials being added), as well as Autodesk Exchange Autoloader (i.e., multiple, small plug-ins, that load individual RibbonPanels to same 'root' RibbonTab).

Perhaps I shouldn't be surprised, but given the enthusiasm with which Autodesk is 'pushing' Autoloader, one might expect that this Main CUIx requirement would have a workaround by now... Perhaps that's my naivete.



So there it is, Tony... Just like with AutoCAD Security, the only 'gotcha' I've found, is Autoloader.

As always, any insight would be most helpful.

Cheers

I haven't done much WRT to manipulating the CUI from applications that use the ribbon, so I can't tell you much about it.

This is a watered-down version of a custom class that I use to deal with ribbon initialization, and it doesn't use the Hack that was suggested by someone from Autodesk (using the ItemInitialized event, which fires for every ribbon item).

Code - C#: [Select]
  1.  
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using Autodesk.AutoCAD.ApplicationServices;
  7. using Autodesk.AutoCAD.Ribbon;
  8. using Autodesk.Windows;
  9. using Autodesk.AutoCAD.Runtime;
  10.  
  11. [assembly: ExtensionApplication(typeof(MyExtension.MyApplication))]
  12.  
  13. namespace MyExtension
  14. {
  15.    // IExtensionApplication showing example
  16.    // usage of the RibbonHelper class:
  17.  
  18.    public class MyApplication : IExtensionApplication
  19.    {
  20.       public void Initialize()
  21.       {
  22.          try
  23.          {
  24.             /// When the ribbon is available, do
  25.             /// initialization for it:
  26.             RibbonHelper.OnRibbonFound( this.SetupRibbon );
  27.  
  28.             /// TODO: Initialize your app
  29.          }
  30.          catch( System.Exception ex )
  31.          {
  32.             Document doc = Application.DocumentManager.MdiActiveDocument;
  33.             if( doc != null )
  34.                doc.Editor.WriteMessage( ex.ToString() );
  35.          }
  36.       }
  37.  
  38.       public void Terminate()
  39.       {
  40.       }
  41.  
  42.       /// A method that performs one-time setup
  43.       /// of Ribbon components:
  44.       void SetupRibbon( RibbonControl ribbon )
  45.       {
  46.          /// TODO: Place ribbon initialization code here
  47.  
  48.          /// Example only: Verify our method was called
  49.          Application.ShowAlertDialog( "SetupRibbon() called" );
  50.       }
  51.    }
  52. }
  53.  
  54. /// RibbonHelper class
  55. namespace Autodesk.AutoCAD.Ribbon
  56. {
  57.    public class RibbonHelper
  58.    {
  59.       Action<RibbonControl> action = null;
  60.       bool idleHandled = false;
  61.       bool created = false;
  62.  
  63.       RibbonHelper( Action<RibbonControl> action )
  64.       {
  65.          if( action == null )
  66.             throw new ArgumentNullException( "initializer" );
  67.          this.action = action;
  68.          SetIdle( true );
  69.       }
  70.  
  71.       /// <summary>
  72.       ///
  73.       /// Pass a delegate that takes the RibbonControl
  74.       /// as its only argument, and it will be invoked
  75.       /// when the RibbonControl is available.
  76.       ///
  77.       /// If the RibbonControl exists when the constructor
  78.       /// is called, the delegate will be invoked on the
  79.       /// next idle event. Otherwise, the delegate will be
  80.       /// invoked on the next idle event following the
  81.       /// creation of the RibbonControl.
  82.       ///
  83.       /// </summary>
  84.  
  85.       public static void OnRibbonFound( Action<RibbonControl> action )
  86.       {
  87.          new RibbonHelper( action );
  88.       }
  89.  
  90.       void SetIdle( bool value )
  91.       {
  92.          if( value ^ idleHandled )
  93.          {
  94.             if( value )
  95.                Application.Idle += idle;
  96.             else
  97.                Application.Idle -= idle;
  98.             idleHandled = value;
  99.          }
  100.       }
  101.  
  102.       void idle( object sender, EventArgs e )
  103.       {
  104.          SetIdle( false );
  105.          if( action != null )
  106.          {
  107.             var ps = RibbonServices.RibbonPaletteSet;
  108.             if( ps != null )
  109.             {
  110.                var ribbon = ps.RibbonControl;
  111.                if( ribbon != null )
  112.                {
  113.                   action( ribbon );
  114.                   action = null;
  115.                }
  116.             }
  117.             else if( !created )
  118.             {
  119.                created = true;
  120.                RibbonServices.RibbonPaletteSetCreated +=
  121.                   ribbonPaletteSetCreated;
  122.             }
  123.          }
  124.       }
  125.  
  126.       void ribbonPaletteSetCreated( object sender, EventArgs e )
  127.       {
  128.          RibbonServices.RibbonPaletteSetCreated
  129.             -= ribbonPaletteSetCreated;
  130.          SetIdle( true );
  131.       }
  132.    }
  133. }
  134.  
  135.  
  136.