Combine ribbon buttons from different addins

Add-in Express™ Support Service
That's what is more important than anything else

Combine ribbon buttons from different addins
 
Eriq VanBibber


Guest


I'm trying to create a modular set of addins. Each feature will be hosted by a separate .net assembly.

I'd like to have a button from ProjectA get added to the same ribbon group as ProjectB.

Is this possible? i have tried simply setting the ribbon group ID to the same value, but then nothing shows.
Posted 02 Oct, 2019 19:36:18 Top
Andrei Smolin


Add-in Express team


Posts: 18829
Joined: 2006-05-11
Hello Eriq,

Please check section Sharing Ribbon Controls across Multiple Add-ins; see the PDF file in the folder {Add-in Express}\Docs on your development PC. Or, find it at https://www.add-in-express.com/docs/net-ribbon-components.php#sharing-ribbon-controls.


Andrei Smolin
Add-in Express Team Leader
Posted 03 Oct, 2019 02:12:12 Top
Eriq VanBibber


Guest


andrei,

thanks for that pointer.

however, it doesn't seem to apply to my case.

i will have a "master" addin for which several "sub-addins" can be installed/added after the main addin has been installed. the "plugin for the plugin" type of thing.

So, i have this separate assembly that is simply an ADXAddinAdditionalModule. Then, i add this to the "Modules" property of the main addin. (though, i don't yet know how to do this at runtime).

So, how would i have a button in my "additional module" merge into an existing ribbon group from the main addin?

Does my question make sense?

Regards,
Eriq
Posted 04 Oct, 2019 18:46:37 Top
Andrei Smolin


Add-in Express team


Posts: 18829
Joined: 2006-05-11
Hello Eriq,

You are right: I was pointing to a wrong direction. I've tested this and found that having an ADXRibbonTab on the add-in module or on an additional module generates a separate XML tab element whatever the settings are. This is by design, unfortunately. A way to solve this would be to intercept the BeforeRibbonCreate event of the add-in module and do the following:
- Identify the target Ribbon container (tab or group) that will have a combined set of controls.
- Identify the source Ribbon container providing Ribbon controls on the additional module.
- Move the Ribbon components from the source container to the target container.
- Delete the source container.

The last step is required because right after raising the BeforeRibbonCreate event, Add-in Express copies Ribbon tabs from the additional module to the main one.


Andrei Smolin
Add-in Express Team Leader
Posted 07 Oct, 2019 05:53:05 Top
Eriq VanBibber


Guest


Ok. I had already thought about that before creating this thread. however, i didn't know about that last part - 'delete the source container'.

yet...how to do this? I see in the designer file that the ribbon tab control is added to the 'components' variable.
Remove it from there?

i have tried disposing of the tab, setting it to null, and removing it from the components collection, but all cases give me an exception with something about GetAttributes.

-Eriq
Posted 07 Oct, 2019 12:05:59 Top
Andrei Smolin


Add-in Express team


Posts: 18829
Joined: 2006-05-11
Hello Eriq,

foreach (var item in this.components.Components)
{
    System.Diagnostics.Debug.WriteLine("!!! " + item.ToString());
}


In a sample add-in, this prints the following:

[29872] !!!  [AddinExpress.MSO.ADXRibbonTab] 
[29872] !!!  [AddinExpress.MSO.ADXRibbonGroup] 
[29872] !!!  [AddinExpress.MSO.ADXRibbonButton] 


The following code fragment is from https://www.add-in-express.com/creating-addins-blog/2012/01/20/load-office-addin-on-condition/:

private void RemoveComponents()
{
    for (int i = this.components.Components.Count - 1; i >= 0; i--)
        this.components.Remove(this.components.Components[i]);
}


Eriq VanBibber writes:
all cases give me an exception with something about GetAttributes


Please provide details.


Andrei Smolin
Add-in Express Team Leader
Posted 08 Oct, 2019 01:14:17 Top
Eriq VanBibber


Guest


Ok. Clearly i'm missing something. I followed similar logic as what you provided.

Here's my code:


    Private Sub AddinModule_OnRibbonBeforeCreate(sender As Object, ribbonId As String) Handles Me.OnRibbonBeforeCreate

        For Each modu In Me.Modules.OfType(Of ADXAddinAdditionalModuleItem)
            If modu.Module IsNot Nothing AndAlso modu.ModuleProgID = "PTFO_MessageHeaderViewer.PTO_MessageHeaderViewer" Then

                Dim ribbon As ADXRibbonTab
                ribbon = modu.Module.GetContainer.Components.OfType(Of IComponent).FirstOrDefault(Function(__) TypeOf __ Is ADXRibbonTab)
                While ribbon IsNot Nothing
                    modu.Module.GetContainer.Remove(ribbon)
                    While ribbon.Controls.Count
                        Dim grp = ribbon.Controls(0)
                        grp.AsRibbon.ADXModule = Me
                        ribbon.Controls.RemoveAt(0)
                        tabPTO.Controls.Add(grp)
                    End While
                    modu.Module.GetContainer.Remove(ribbon)
                    ribbon = modu.Module.GetContainer.Components.OfType(Of IComponent).FirstOrDefault(Function(__) TypeOf __ Is IADXRibbonComponent)
                End While
                Dim rc As IADXRibbonComponent
                rc = modu.Module.GetContainer.Components.OfType(Of IADXRibbonComponent).FirstOrDefault
                While rc IsNot Nothing
                    modu.Module.GetContainer.Remove(rc)
                End While
                For Each grp In GetContainer.Components.OfType(Of ADXRibbonTab)
                    grp.AsRibbon.ADXModule = Me
                Next
            End If
        Next

    End Sub




What i find is that after this method completes, i get a startup exception.
From what i can gather, the 'ProcessChildren' method is calling a 'GetAttributes' method that is failing with a null-ref exception.

using reflection, i find that the ADXRibbonGroup.GetAttributes method could fail here:

        protected override SortedList GetAttributes()
        {
            SortedList sortedLists = new SortedList();
            IADXRibbonComponent asRibbon = base.AsRibbon;
            if (!this.shared)
            {
                sortedLists["getVisible"] = "getVisible_Callback";
                if (this.IdMso == string.Empty)
                {
                    if (asRibbon.ADXModule.Namespace == string.Empty) // <==  This throws the error because ADXModule is null!
                    {
                        sortedLists["id"] = asRibbon.Id;
                    }
                    else
                    {
                        sortedLists["idQ"] = string.Concat("default:", asRibbon.Id);
                    }
         ...
         // other code follows here.



Here's the full error text reported:

Detailed technical information follows: 
---
Date and Time:         10/8/2019 10:53:36 AM
Machine Name:          XXXXXXXXX
IP Address:            XX.XX.XX.XX
Current User:          XXXXXXXXXxx

Application Domain:    C:ProjectsTFSOverlookOutlookToolsForPowerUsersinDebug
Assembly Codebase:     file:///C:/Windows/assembly/GAC_MSIL/AddinExpress.MSO.2005/9.4.4644.0__4416dd98f0861965/AddinExpress.MSO.2005.dll
Assembly Full Name:    AddinExpress.MSO.2005, Version=9.4.4644.0, Culture=neutral, PublicKeyToken=4416dd98f0861965
Assembly Version:      9.4.4644.0

Exception Source:      AddinExpress.MSO.2005
Exception Type:        System.NullReferenceException
Exception Message:     Object reference not set to an instance of an object.
Exception Target Site: GetAttributes

---- Stack Trace ----
   AddinExpress.MSO.ADXRibbonGroup.GetAttributes()
       AddinExpress.MSO.2005.dll: N 0474 (0x1DA) IL 
   AddinExpress.MSO.ADXRibbonCustomControl.AddinExpress.MSO.IADXRibbonComponent.get_Attributes()
       AddinExpress.MSO.2005.dll: N 0000 (0x0) IL 
   AddinExpress.MSO.ADXAddinModule.ProcessChildren(xmlData As XmlTextWriter, ribbonElement As IADXRibbonComponent, ribbonID As String, uniqueId As Int32, ribbonControl As IRibbonControl)
       AddinExpress.MSO.2005.dll: N 0088 (0x58) IL 
   AddinExpress.MSO.ADXAddinModule.ProcessChildren(xmlData As XmlTextWriter, ribbonElement As IADXRibbonComponent, ribbonID As String, uniqueId As Int32, ribbonControl As IRibbonControl)
       AddinExpress.MSO.2005.dll: N 0359 (0x167) IL 
   AddinExpress.MSO.ADXAddinModule.AddinExpress.MSO.IRibbonExtensibility.GetCustomUI(RibbonID As String)
       AddinExpress.MSO.2005.dll: N 1704 (0x6A8) IL 


Posted 08 Oct, 2019 12:53:25 Top
Andrei Smolin


Add-in Express team


Posts: 18829
Joined: 2006-05-11
Hello Eriq,

    Private Sub AddinModule_OnRibbonBeforeCreate(sender As Object, ribbonId As String) Handles MyBase.OnRibbonBeforeCreate
        For Each aModuleItem As AddinExpress.MSO.ADXAddinAdditionalModuleItem In Me.Modules
            If aModuleItem.Module IsNot Nothing AndAlso aModuleItem.ModuleProgID = "ClassLibrary1.AddinAdditionalModule1" Then

                Dim componentsToMove As List(Of IComponent) = New List(Of IComponent)()

                Dim sourceContainer As IContainer = aModuleItem.Module.GetContainer
                Dim targetContainer As IContainer = Me.GetContainer

                Dim sourceTab As ADXRibbonTab = Nothing
                Dim targetTab = Me.AdxRibbonTab1

                For Each aComponent As IComponent In sourceContainer.Components
                    If TypeOf aComponent Is ADXRibbonTab Then
                        sourceTab = CType(aComponent, ADXRibbonTab)
                        Exit For
                    End If
                Next

                For Each aComponent As IComponent In sourceContainer.Components
                    If TypeOf aComponent Is IADXRibbonComponent Then
                        Dim cmp As IADXRibbonComponent = CType(aComponent, IADXRibbonComponent)
                        If cmp IsNot sourceTab Then
                            componentsToMove.Add(aComponent)
                        End If
                    End If
                Next

                For Each item As IComponent In componentsToMove
                    If TypeOf item Is IADXRibbonComponent Then
                        Dim cmp As IADXRibbonComponent = CType(item, IADXRibbonComponent)
                        If cmp.Root Is sourceTab Then
                            cmp.ADXModule = Me
                        End If
                        If TypeOf cmp Is ADXRibbonGroup Then
                            sourceTab.Controls.Remove(CType(cmp, ADXRibbonGroup))
                            targetTab.Controls.Add(CType(cmp, ADXRibbonGroup))
                        End If
                    End If
                    sourceContainer.Remove(item)
                    targetContainer.Add(item)
                Next

                sourceContainer.Remove(sourceTab)
            End If
        Next
    End Sub



Andrei Smolin
Add-in Express Team Leader
Posted 09 Oct, 2019 08:46:41 Top
Eriq VanBibber


Guest


Thanks!!!

It seems the component.Root thing was something i missed and didn't know about.

Regards,
Eriq
Posted 09 Oct, 2019 10:47:54 Top
Andrei Smolin


Add-in Express team


Posts: 18829
Joined: 2006-05-11
Welcome!


Andrei Smolin
Add-in Express Team Leader
Posted 10 Oct, 2019 02:37:00 Top