<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://msmvps.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Excel Do, Dynamic Does : dynamic, addin</title><link>http://msmvps.com/blogs/xldynamic/archive/tags/dynamic/addin/default.aspx</link><description>Tags: dynamic, addin</description><dc:language>en</dc:language><generator>CommunityServer 2008.5 SP2 (Build: 40407.4157)</generator><item><title>Ribbon On The Fly Part2</title><link>http://msmvps.com/blogs/xldynamic/archive/2011/03/03/ribbon-on-the-flky-part2.aspx</link><pubDate>Thu, 03 Mar 2011 12:49:00 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1789221</guid><dc:creator>Bob Phillips</dc:creator><slash:comments>1</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/xldynamic/rsscomments.aspx?PostID=1789221</wfw:commentRss><comments>http://msmvps.com/blogs/xldynamic/archive/2011/03/03/ribbon-on-the-flky-part2.aspx#comments</comments><description>&lt;p&gt;



&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In my last blog post, I described how I thought that I could build an Excel 2007 ribbon on th fly. The technique was founded upon having a &amp;lsquo;worker&amp;rsquo; addin that handled the main functionality as well as the version management and ribbon building; and a simpler Excel 2007 ribbon wrapper addin.&lt;/p&gt;
&lt;p&gt;This approach would be similar to the myriad of table driven menu solutions around, but the &amp;lsquo;table&amp;rsquo; would be a configuration file in this case, to allow the user to control the menu/ribbon.&lt;/p&gt;
&lt;p&gt;The &amp;lsquo;worker&amp;rsquo; addin would read the  configuration file that held the details of the each procedure to be run, and construct the menu or ribbon on the fly. In the case of the ribbon, the customUI XML would be generated, written back to the (as yet) unopened ribbon wrapper addin, then open the ribbon wrapper addin so as to display the changed ribbon in all its pristine glory.&lt;/p&gt;
&lt;p&gt;At that point, this was just an idea, albeit an idea that I was confident that it could be implemented. Since then I have implemented it, so it is time to share the details.&lt;/p&gt;
&lt;h2&gt;Linking the Ribbon Addin to the &amp;lsquo;Worker&amp;rsquo; Addin&lt;/h2&gt;
&lt;p&gt;Having a &amp;lsquo;worker&amp;rsquo; addin and a separate ribbon presentation addin does create one problem, namely how dos a button on the ribbon run a procedure in the &amp;lsquo;worker&amp;rsquo; addin. This is easily resolved using &lt;b&gt;Application.Run&lt;/b&gt;, rather than a simple call to the procedure, as this allows specifying the file as well as the procedure name.&lt;/p&gt;
&lt;p&gt;In fact, this allows even greater flexibility when deploying in the &amp;lsquo;real&amp;rsquo; world. If we release our super application, with a configuration file, the &amp;lsquo;worker&amp;rsquo; addin, and a ribbon addin, a user can add items to the configuration file as long as it runs a procedure already defined in our &amp;lsquo;worker&amp;rsquo; addin. But what if they want to run a completely new process, how do we provide the ability to extend the applications overall functionality? We could just open up the addin and tell the user to do add thewir code to the &amp;lsquo;worker&amp;rsquo; addin, but is this a good idea? I don&amp;rsquo;t think this is good, it could break the whole application. A better way is to tell the user that they can build their code in an entirely separate project, and addin, Person.als/xlsm, or whatever they wish, and the include the full file/procedure call in the configuration file procedure column.&lt;/p&gt;
&lt;h2&gt;Format of Configuration  File&lt;/h2&gt;
&lt;p&gt;It is probably a good point to describe the configuration file at this point.&lt;/p&gt;
&lt;p&gt;I have set it up as a standard Excel workbook, in Excel 2003 format for backwards compatibility, and I have a separate tab for each (user) functional ribbon group (or sub-menu for Excel 2003).&lt;/p&gt;
&lt;p&gt;I built this technique to service the running of a set of user reports, which are all template files and generally all run the same process, but it could be anything that you want, you would just setup the configuration data, and extract it in the configuration management code.
Figure 1 - Configuration File Group shows a typical layout. If you have any other attributes that you need to capture, just insert extra columns in the first part (in my application, I was importing XML files, so I had to specify the XML table worksheet etc.).&lt;/p&gt;
&lt;p&gt;As you can see, some of the columns are relating to the report running, and some relate to the presentation of the ribbon.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://msmvps.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/xldynamic/7801.Config-Group.png"&gt;&lt;img src="http://msmvps.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/xldynamic/7801.Config-Group.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
Figure 1 - Configuration File Group
&lt;p&gt;I have a column for Report Type, this is so that the client can have the templates in different locations, say company reports on a central server, departmental reports on a departmental server, and the user can have their own report templates locally.&lt;/p&gt;
&lt;p&gt;This is all defined on the Client worksheet, Figure 2 - Configuration File Client. &lt;/p&gt;
&lt;p&gt;&lt;a href="http://msmvps.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/xldynamic/7382.Config-Client.png"&gt;&lt;img src="http://msmvps.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/xldynamic/7382.Config-Client.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
Figure 2 - Configuration File Client
&lt;p&gt;This simply allows the user to provide the client name to appear on the ribbon/menu, a logo file, and the various template directories. Again, if you have a requirement for other client related details, add them here.&lt;/p&gt;
&lt;p&gt;The only amendable field here is the client name, the directory values are added by clicking the browse buttons.&lt;/p&gt;
&lt;p&gt;I have also added a button to add new groups sheets, so as to keep a consistent format.&lt;/p&gt;
&lt;h2&gt;Check the Configuration File&lt;/h2&gt;
&lt;p&gt;Writing the XML back to the ribbon addin is an expensive process, cracking open the zip file, updating the customUI.xml file and then re-writing the file, so I have added a check to ensure this is only done if and when the configuration file is updated.&lt;/p&gt;
&lt;p&gt;This is simply achieved with some simple change event code in the configuration file, Figure 3 - Configuration File Change Code, lines 440-450 simply setup a defined name with a value of TRUE. This is checked in the &amp;lsquo;worker&amp;rsquo;; addin initialise code to determine whether to update the ribbon addin or not.&lt;/p&gt;
&lt;p&gt;As you can see, there is a lot of code here for creating a simple name. In addition to this, the code also checks for a duplicate report ID, resetting if a duplicate value is added (this is to avoid compromising the ribbon).&lt;/p&gt;
&lt;pre&gt;&lt;span class="codekeyword"&gt;Private&lt;/span&gt; mcPrevValue &lt;span class="codekeyword"&gt;As&lt;/span&gt; Variant&lt;br /&gt;&lt;br /&gt;&lt;span class="codekeyword"&gt;Private Sub&lt;/span&gt; Workbook_SheetActivate(&lt;span class="codekeyword"&gt;ByVal&lt;/span&gt; Sh &lt;span class="codekeyword"&gt;As&lt;/span&gt; Object)&lt;br /&gt;    &lt;span class="codekeyword"&gt;If&lt;/span&gt; Sh.Name = WS_TEMPLATE &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;    &lt;br /&gt;        wsClient.Activate&lt;br /&gt;    &lt;span class="codekeyword"&gt;End If&lt;br /&gt;End Sub&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="codekeyword"&gt;Private Sub&lt;/span&gt; Workbook_SheetChange(&lt;span class="codekeyword"&gt;ByVal&lt;/span&gt; Sh &lt;span class="codekeyword"&gt;As&lt;/span&gt; Object, &lt;span class="codekeyword"&gt;ByVal&lt;/span&gt; Target &lt;span class="codekeyword"&gt;As &lt;/span&gt; Range)&lt;br /&gt;      &lt;span class="codekeyword"&gt;Dim&lt;/span&gt; mpName &lt;span class="codekeyword"&gt;As &lt;/span&gt; Name&lt;br /&gt;      &lt;span class="codekeyword"&gt;Dim&lt;/span&gt; mpSheet &lt;span class="codekeyword"&gt;As&lt;/span&gt; Worksheet&lt;br /&gt;      &lt;span class="codekeyword"&gt;Dim&lt;/span&gt; mpDuplicateRepID &lt;span class="codekeyword"&gt;As Boolean&lt;/span&gt; &lt;br /&gt;      &lt;span class="codekeyword"&gt;Dim&lt;/span&gt; mpDuplicateGroupID &lt;span class="codekeyword"&gt;As Boolean&lt;/span&gt; &lt;br /&gt;      &lt;span class="codekeyword"&gt;Dim&lt;/span&gt; mpLastrow &lt;span class="codekeyword"&gt;As Long&lt;/span&gt;&lt;br /&gt;      &lt;span class="codekeyword"&gt;Dim&lt;/span&gt; i &lt;span class="codekeyword"&gt;As Long&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;10        &lt;span class="codekeyword"&gt;On Error GoTo&lt;/span&gt; ws_exit&lt;br /&gt;          &lt;br /&gt;20        Application.EnableEvents = &lt;span class="codekeyword"&gt;False&lt;/span&gt;&lt;br /&gt;          &lt;br /&gt;30        &lt;span class="codekeyword"&gt;If&lt;/span&gt; Sh.Name = &amp;quot;Client&amp;quot; &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;          &lt;br /&gt;40        &lt;span class="codekeyword"&gt;ElseIf&lt;/span&gt; Sh.Name = &amp;quot;_template&amp;quot; &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;          &lt;br /&gt;50        &lt;span class="codekeyword"&gt;Else&lt;/span&gt;&lt;br /&gt;          &lt;br /&gt;60            &lt;span class="codekeyword"&gt;If&lt;/span&gt; Target.Column = 1 &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;              &lt;br /&gt;70                mpDuplicateRepID = &lt;span class="codekeyword"&gt;False&lt;/span&gt;&lt;br /&gt;80                &lt;span class="codekeyword"&gt;For Each&lt;/span&gt; mpSheet &lt;span class="codekeyword"&gt;In&lt;/span&gt; Sh.Parent.Worksheets&lt;br /&gt;                  &lt;br /&gt;90                    &lt;span class="codekeyword"&gt;If&lt;/span&gt; mpSheet.Name &amp;lt;&amp;gt; WS_CLIENT &lt;span class="codekeyword"&gt;And&lt;/span&gt; mpSheet.Name &amp;lt;&amp;gt; WS_TEMPLATE &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;                      &lt;br /&gt;100                       mpLastrow = mpSheet.Cells(mpSheet.Rows.Count, &amp;quot;A&amp;quot;).End(xlUp).Row&lt;br /&gt;110                       &lt;span class="codekeyword"&gt;If&lt;/span&gt; mpLastrow &amp;gt; 2 &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;                          &lt;br /&gt;120                           &lt;span class="codekeyword"&gt;For&lt;/span&gt; i = 3 &lt;span class="codekeyword"&gt;To&lt;/span&gt; mpLastrow&lt;br /&gt;                              &lt;br /&gt;130                               &lt;span class="codekeyword"&gt;If&lt;/span&gt; Sh.Name &amp;lt;&amp;gt; mpSheet.Name &lt;span class="codekeyword"&gt;Or&lt;/span&gt; Target.Row &amp;lt;&amp;gt; i &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;                                  &lt;br /&gt;140                                   &lt;span class="codekeyword"&gt;If&lt;/span&gt; mpSheet.Cells(i, &amp;quot;A&amp;quot;).Value2 = Target.Value &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;                                  &lt;br /&gt;150                                       mpDuplicateRepID = &lt;span class="codekeyword"&gt;True&lt;/span&gt;&lt;br /&gt;160                                       &lt;span class="codekeyword"&gt;Exit For&lt;/span&gt;&lt;br /&gt;170                                   &lt;span class="codekeyword"&gt;End If&lt;/span&gt;&lt;br /&gt;180                               &lt;span class="codekeyword"&gt;End If&lt;/span&gt;&lt;br /&gt;190                           &lt;span class="codekeyword"&gt;Next&lt;/span&gt; i&lt;br /&gt;                              &lt;br /&gt;200                           &lt;span class="codekeyword"&gt;If&lt;/span&gt; mpDuplicateRepID &lt;span class="codekeyword"&gt;Then Exit For&lt;/span&gt;&lt;br /&gt;210                       &lt;span class="codekeyword"&gt;End If&lt;/span&gt;&lt;br /&gt;220                   &lt;span class="codekeyword"&gt;End If&lt;/span&gt;&lt;br /&gt;230               &lt;span class="codekeyword"&gt;Next&lt;/span&gt; mpSheet&lt;br /&gt;                  &lt;br /&gt;240               &lt;span class="codekeyword"&gt;If&lt;/span&gt; mpDuplicateRepID &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;250                   ShowMessage Replace(MSG_ERROR_DUPLICATE_REPORT, _&lt;br /&gt;                                      &amp;quot;&amp;quot;, Target.Value), vbOKOnly + vbExclamation&lt;br /&gt;260                   Target.Value = mcPrevValue&lt;br /&gt;270               &lt;span class="codekeyword"&gt;End If&lt;/span&gt;&lt;br /&gt;280           &lt;span class="codekeyword"&gt;ElseIf Not&lt;/span&gt; Intersect(Sh.Range(NAME_GROUP_ID), Target) Is Nothing &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;              &lt;br /&gt;290               mpDuplicateGroupID = &lt;span class="codekeyword"&gt;False&lt;/span&gt;&lt;br /&gt;300               &lt;span class="codekeyword"&gt;For Each&lt;/span&gt; mpSheet In Sh.Parent.Worksheets&lt;br /&gt;                  &lt;br /&gt;310                   &lt;span class="codekeyword"&gt;If&lt;/span&gt; Sh.Name &amp;lt;&amp;gt; WS_CLIENT &lt;span class="codekeyword"&gt;And&lt;/span&gt; Sh.Name &amp;lt;&amp;gt; WS_TEMPLATE &lt;span class="codekeyword"&gt;And&lt;/span&gt; Sh.Name &amp;lt;&amp;gt; mpSheet.Name &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;                      &lt;br /&gt;320                       &lt;span class="codekeyword"&gt;If&lt;/span&gt; mpSheet.Range(NAME_GROUP_ID).Value2 = Target.Value &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;                                  &lt;br /&gt;330                           mpDuplicateGroupID = &lt;span class="codekeyword"&gt;True&lt;/span&gt;&lt;br /&gt;340                           &lt;span class="codekeyword"&gt;Exit For&lt;/span&gt;&lt;br /&gt;350                       &lt;span class="codekeyword"&gt;End If&lt;/span&gt;&lt;br /&gt;360                   &lt;span class="codekeyword"&gt;End If&lt;/span&gt;&lt;br /&gt;370               &lt;span class="codekeyword"&gt;Next&lt;/span&gt; mpSheet&lt;br /&gt;                  &lt;br /&gt;380               &lt;span class="codekeyword"&gt;If&lt;/span&gt; mpDuplicateGroupID &lt;span class="codekeyword"&gt;Then&lt;/span&gt;&lt;br /&gt;390                   ShowMessage Replace(MSG_ERROR_DUPLICATE_GROUP, _&lt;br /&gt;                                      &amp;quot;&amp;quot;, Target.Value), vbOKOnly + vbExclamation&lt;br /&gt;400                   Target.Value = mcPrevValue&lt;br /&gt;410               &lt;span class="codekeyword"&gt;End If&lt;/span&gt;&lt;br /&gt;420           &lt;span class="codekeyword"&gt;End If&lt;/span&gt;&lt;br /&gt;430       &lt;span class="codekeyword"&gt;End If&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;440       &lt;span class="codekeyword"&gt;Set&lt;/span&gt; mpName = ThisWorkbook.Names.Add(Name:=&amp;quot;_Changed&amp;quot;, RefersTo:=&amp;quot;=TRUE&amp;quot;)&lt;br /&gt;450       mpName.Visible = &lt;span class="codekeyword"&gt;False&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;ws_exit:&lt;br /&gt;460       mcPrevValue = Target.Value&lt;br /&gt;470       Application.EnableEvents = &lt;span class="codekeyword"&gt;True&lt;/span&gt;&lt;br /&gt;End Sub&lt;br /&gt;&lt;br /&gt;&lt;span class="codekeyword"&gt;Private Sub&lt;/span&gt; Workbook_SheetSelectionChange(&lt;span class="codekeyword"&gt;ByVal&lt;/span&gt; Sh &lt;span class="codekeyword"&gt;As&lt;/span&gt; Object, &lt;span class="codekeyword"&gt;ByVal&lt;/span&gt; Target &lt;span class="codekeyword"&gt;As&lt;/span&gt; Range)&lt;br /&gt;    mcPrevValue = Target.Value&lt;br /&gt;&lt;span class="codekeyword"&gt;End Sub&lt;/span&gt;&lt;/pre&gt;
Figure 3 - Configuration File Change Code
&lt;h2&gt;Testing Excel Version&lt;/h2&gt;
&lt;p&gt;Determining whether to build a menu or a ribbon is very straight-forward, all we need to do is to check the application version, as shown in Figure 4 - Test Excel Version. There is nothing particularly trick here, but there are a few call to other procedures that will be covered in the next post, namely&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AppInitialise &amp;ndash; opens the configuration file, does some basic validation of the template paths, and  then grabs all of the report details&lt;/li&gt;
&lt;li&gt;OpenRibbonAddin &amp;ndash; this checks if the configuration file has been changed since the last run, builds the XML, and writes it back to the ribbon addin.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is one other procedure call, BuildMenus, which I will not go into, it is a standard commandbar menu builder.&lt;/p&gt;
&lt;pre&gt;Private Const mmModule As String = &amp;quot;ThisWorkbook&amp;quot;	&lt;br /&gt;&lt;br /&gt;&amp;#39;-----------------------------------------------------------------&lt;br /&gt;Private Sub Workbook_BeforeClose(Cancel As Boolean)&lt;br /&gt;&amp;#39;-----------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;    If Val(Application.Version) &amp;lt; 12 Then&lt;br /&gt;&lt;br /&gt;        Call DeleteMenus&lt;br /&gt;    Else&lt;br /&gt;&lt;br /&gt;        mgInsightRibbon.Close&lt;br /&gt;    End If&lt;br /&gt;End Sub&lt;br /&gt;&lt;br /&gt;&amp;#39;-----------------------------------------------------------------&lt;br /&gt;Private Sub Workbook_Open()&lt;br /&gt;&amp;#39;-----------------------------------------------------------------&lt;br /&gt;Const mpProcedure As String = &amp;quot;Workbook_Open&amp;quot;&lt;br /&gt;&lt;br /&gt;    On Error GoTo Workbook_Open_Error&lt;br /&gt;    PushProcedureStack mpProcedure, True&lt;br /&gt;&lt;br /&gt;    Call AppInitialise&lt;br /&gt;    If Val(Application.Version) &amp;lt; 12 Then&lt;br /&gt;&lt;br /&gt;        Call BuildMenus&lt;br /&gt;        mgConfigWB.Close SaveChanges:=False&lt;br /&gt;        Set mgConfigWB = Nothing&lt;br /&gt;&lt;br /&gt;    Else&lt;br /&gt;&lt;br /&gt;        Call OpenRibbonAddin&lt;br /&gt;    End If&lt;br /&gt;&lt;br /&gt;Workbook_Open_Tidy:&lt;br /&gt;    PopProcedureStack&lt;br /&gt;&lt;br /&gt;Workbook_Open_Exit:&lt;br /&gt;    Application.DisplayAlerts = True&lt;br /&gt;    If Not mgConfigWB Is Nothing Then mgConfigWB.Close SaveChanges:=False&lt;br /&gt;    Set mgConfigWB = Nothing&lt;br /&gt;    Exit Sub&lt;br /&gt;&lt;br /&gt;Workbook_Open_Error:&lt;br /&gt;    If Err.Number = AppBypassErrorNum Then Resume Workbook_Open_Tidy&lt;br /&gt;    If AppErrorHandler(mmModule, mpProcedure, True) Then&lt;br /&gt;        Stop&lt;br /&gt;        Resume&lt;br /&gt;    Else&lt;br /&gt;        Resume Workbook_Open_Exit&lt;br /&gt;    End If&lt;br /&gt;End Sub&lt;/pre&gt;
Figure 4 - Test Excel Version
&lt;h2&gt;Next Post&lt;/h2&gt;
&lt;p&gt;In the next post, I will give details on the getting the configuration file details, building the ribbon XML, and using the addin.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1789221" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/xldynamic/archive/tags/Ribbon/default.aspx">Ribbon</category><category domain="http://msmvps.com/blogs/xldynamic/archive/tags/Excel+2007/default.aspx">Excel 2007</category><category domain="http://msmvps.com/blogs/xldynamic/archive/tags/dynamic/default.aspx">dynamic</category><category domain="http://msmvps.com/blogs/xldynamic/archive/tags/commandbars/default.aspx">commandbars</category><category domain="http://msmvps.com/blogs/xldynamic/archive/tags/addin/default.aspx">addin</category></item></channel></rss>