none
[Outlook 2013] AttachmentSelection and own ContextMenu extentions RRS feed

  • Question

  • Hi,

    i have problems with the attachment contextmenu in outlook 2013, but in ol2010 it works fine. The problem is: If you select the message tab in the reading pane and right-click on an attachment, the button "TestLabel" will not be visible, because the AttachmentSelection (see below - in the function Ribbon1.isSelected(...)) is not set.

    If you click on the attachment first (left-click) and then right-click it, the button is available.

    I made a seperate add-in, which is shrinked down to only show this issue.
    Here is the ribbon.xml of my reproduce-project:

    <?xml version="1.0" encoding="UTF-8"?>
    <customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui">
      <contextMenus>
        <contextMenu idMso="ContextMenuAttachments">
          <button id="myId" insertAfterMso="SaveAttachments" getVisible="isSelected" onAction="DoSomething" label="TestLabel" />
        </contextMenu>
      </contextMenus>
    </customUI>
    

    Here is the ThisAddIn.cs:

    using System.Windows.Forms;
    using Microsoft.Office.Interop.Outlook;
    
    namespace OutlookAddIn2
    {
    	public partial class ThisAddIn
    	{
    		Ribbon1 _ribbon;
    		private void ThisAddIn_Startup(object sender, System.EventArgs e)
    		{
    			foreach (Explorer exp in Application.Explorers)
    			{
    				exp.AttachmentSelectionChange += AttachmentSelectionChange;
    			}
    		}
    
    		private void ThisAddIn_Shutdown(object sender, System.EventArgs e) { }
    
    		internal void AttachmentSelectionChange()
    		{
    			Ribbon1.AttachmentSelection = null;
    
    			if (_ribbon != null)
    				_ribbon.Invalidate();
    		}
    
    		protected override Microsoft.Office.Core.IRibbonExtensibility CreateRibbonExtensibilityObject()
    		{
    			_ribbon = new Ribbon1();
    			return _ribbon;
    		}
    
    		#region VSTO generated code
    		private void InternalStartup()
    		{
    			this.Startup += new System.EventHandler(ThisAddIn_Startup);
    			this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
    		}
    		#endregion
    	}
    }
    

    And the Ribbon1.cs:

    using System.Windows.Forms;
    using Microsoft.Office.Core;
    using Microsoft.Office.Interop.Outlook;
    
    namespace OutlookAddIn2
    {
    	[System.Runtime.InteropServices.ComVisible(true)]
    	public class Ribbon1 : IRibbonExtensibility
    	{
    		private IRibbonUI ribbon;
    
    		private static AttachmentSelection _selection = null;
    		public static AttachmentSelection AttachmentSelection
    		{
    			get
    			{
    				if (_selection != null)
    					return _selection;
    
    				dynamic activeWindow = Globals.ThisAddIn.Application.ActiveWindow();
    				return activeWindow == null ? null : activeWindow.AttachmentSelection;
    			}
    			set
    			{
    				_selection = value;
    			}
    		}
    
    		public Ribbon1() { }
    
    		internal void Invalidate()
    		{
    			if (ribbon != null)
    				ribbon.Invalidate();
    		}
    
    		#region IRibbonExtensibility Members
    		public string GetCustomUI(string ribbonID) { return GetResourceText("OutlookAddIn2.Ribbon1.xml"); }
    		#endregion
    
    		#region Ribbon Callbacks
    
    		public void OnLoad(IRibbonUI ribbonUI)
    		{
    			this.ribbon = ribbonUI;
    			Invalidate();
    		}
    
    		public bool isSelected(IRibbonControl control)
    		{
    			AttachmentSelection selection = null;
    			try
    			{
    				selection = AttachmentSelection;
    				return selection != null && selection.Count == 1;
    			}
    			catch (System.Exception ex)
    			{
    				MessageBox.Show(ex.ToString());
    			}
    
    			return false;
    		}
    
    		public void DoSomething(IRibbonControl control) { MessageBox.Show("It works"); }
    
    		#endregion
    
    		#region Helpers
    		private static string GetResourceText(string resourceName)
    		{
    			System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
    			string[] resourceNames = asm.GetManifestResourceNames();
    			for (int i = 0; i < resourceNames.Length; ++i)
    			{
    				if (string.Compare(resourceName, resourceNames[i], System.StringComparison.OrdinalIgnoreCase) == 0)
    				{
    					using (System.IO.StreamReader resourceReader = new System.IO.StreamReader(asm.GetManifestResourceStream(resourceNames[i])))
    					{
    						if (resourceReader != null)
    						{
    							return resourceReader.ReadToEnd();
    						}
    					}
    				}
    			}
    			return null;
    		}
    		#endregion
    	}
    }
    

    Thursday, February 14, 2013 10:08 AM

Answers

  • I'm a bit confused by your code example for your problem. In your isSelected() callback handling getVisible, you have this:
     
     public bool isSelected(IRibbonControl control)
    {
        AttachmentSelection selection = null;
    try
    {
        selection = AttachmentSelection;
        return selection != null && selection.Count == 1;
    }
    catch (System.Exception ex)
    {
          MessageBox.Show(ex.ToString());
    }

        return false;
    }

    I see where you're getting your static AttachmentSelection object from handling AttachmentSelectionChange(), but that seems a bit overly complicated to me. Why not just use control.Context to get the AttachmentSelection collection directly in the ribbon callback event?
     
    My guess is that in 2010 Attachments[1] is selected by the right-click action, in 2013 it's not. That would account for the disparity of what you see.

    --
    Ken Slovak
    [MVP-Outlook]
    http://www.slovaktech.com
    Author: Professional Programming Outlook 2007
    "Ranfty2000" <=?utf-8?B?UmFuZnR5MjAwMA==?=> wrote in message news:c9ec1f36-4ba8-439e-ba89-1c53ed627a2a...

    Thank you Ken for this hints, the real project is coded like that. I release every single com object right after i used it and in deed i use an InspectorEventHandler class and so on.

    But the meaningful part of my issue is still unsolved. Has anyone an idea, why the attachment context menu doesn't show my button? In Outlook 2010 it works fine, only in Outlook 2013 it doesn't show without first selecting the attachment.


    Ken Slovak MVP - Outlook
    • Marked as answer by Ranfty2000 Thursday, February 21, 2013 8:30 AM
    Wednesday, February 20, 2013 4:01 PM
    Moderator

All replies

  • Hi Ranfty2000,

    Thank you for posting in the MSDN Forum.

    It will be nice if you could upload your project to SkyDrive make it accessible and then share the link.

    private static AttachmentSelection _selection = null;
    public static AttachmentSelection AttachmentSelection

    It seems there're something wrong with the code above and I cannot get them compiled.

    I look forward to your reply.

    Best regards,


    Quist Zhang [MSFT]
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Friday, February 15, 2013 8:08 AM
    Moderator
  • Your event handlers are going out of scope. Your code does this:

       foreach (Explorer exp in Application.Explorers)
    {
    exp
    .AttachmentSelectionChange += AttachmentSelectionChange;
    }

    The Explorer objects are only instantiated inside the loop and go out of scope as soon as the loop ends. When the garbage collector runs no more event handlers.

    Set up a collection of classes that hold an Explorer and handler for the event. Instantiate an instance of the class and add it to the collection/list/dictionary/whatever.

    Don't use foreach() loops with COM objects. You create an object instance that cannot be explicitly released in each loop pass. Instead use a for() loop with a loop variable. Declare your object before the loop so it's only created once. Set the object from the loop index counter, release the object at the end of the loop pass.

     


    Ken Slovak MVP - Outlook

    Friday, February 15, 2013 4:26 PM
    Moderator
  • Thanks Ken, i will eliminate the foreach and add an instance list:

    List<Explorer> explorerList = new List<Explorer>();
    
    private void ThisAddIn_Startup(object sender, System.EventArgs e)
    {
    	for (int i = 1; i <= Application.Explorers.Count; i++)
    	{
    		Explorer exp = Application.Explorers[i];
    		explorerList.Add(exp);
    		exp.AttachmentSelectionChange += AttachmentSelectionChange;
    	}
    }
    
    But the explorer objects will not go out of scope in the real project - there is already an instance list.
    Tuesday, February 19, 2013 8:48 AM
  • Usually what I do is encapsulate each Explorer or Inspector in a wrapper class. It has an Explorer or Inspector object, handlers for events and any specialized code for the window. That way each wrapper knows its own Inspector or Explorer and can handle events and button clicks discretely in its own class. I then add a reference to the wrapper class to the list to keep it alive.
     
    Another tip for managed code is to avoid compound dot operators. Each usage creates an internal variable that you can't release: Application.Explorers.Count creates an intrinsic Explorers object that can't be explicitly released. In loops or if you use a lot of such constructs you can easily run out of Outlook RPC channels and suddenly start getting out of memory errors.

    --
    Ken Slovak
    [MVP-Outlook]
    http://www.slovaktech.com
    Author: Professional Programming Outlook 2007
    "Ranfty2000" <=?utf-8?B?UmFuZnR5MjAwMA==?=> wrote in message news:19e31371-c992-461d-9522-63191903a311...

    Thanks Ken, i will eliminate the foreach and add an instance list:

    List<Explorer> explorerList = new List<Explorer>();
    
    private void ThisAddIn_Startup(object sender, System.EventArgs e)
    {
    	for (int i = 1; i <= Application.Explorers.Count; i++)
    	{
    		Explorer exp = Application.Explorers[i];
    		explorerList.Add(exp);
    		exp.AttachmentSelectionChange += AttachmentSelectionChange;
    	}
    }
    
    But the explorer objects will not go out of scope in the real project - there is already an instance list.

    Ken Slovak MVP - Outlook
    Tuesday, February 19, 2013 3:48 PM
    Moderator
  • Thank you Ken for this hints, the real project is coded like that. I release every single com object right after i used it and in deed i use an InspectorEventHandler class and so on.

    But the meaningful part of my issue is still unsolved. Has anyone an idea, why the attachment context menu doesn't show my button? In Outlook 2010 it works fine, only in Outlook 2013 it doesn't show without first selecting the attachment.

    Wednesday, February 20, 2013 8:20 AM
  • I'm a bit confused by your code example for your problem. In your isSelected() callback handling getVisible, you have this:
     
     public bool isSelected(IRibbonControl control)
    {
        AttachmentSelection selection = null;
    try
    {
        selection = AttachmentSelection;
        return selection != null && selection.Count == 1;
    }
    catch (System.Exception ex)
    {
          MessageBox.Show(ex.ToString());
    }

        return false;
    }

    I see where you're getting your static AttachmentSelection object from handling AttachmentSelectionChange(), but that seems a bit overly complicated to me. Why not just use control.Context to get the AttachmentSelection collection directly in the ribbon callback event?
     
    My guess is that in 2010 Attachments[1] is selected by the right-click action, in 2013 it's not. That would account for the disparity of what you see.

    --
    Ken Slovak
    [MVP-Outlook]
    http://www.slovaktech.com
    Author: Professional Programming Outlook 2007
    "Ranfty2000" <=?utf-8?B?UmFuZnR5MjAwMA==?=> wrote in message news:c9ec1f36-4ba8-439e-ba89-1c53ed627a2a...

    Thank you Ken for this hints, the real project is coded like that. I release every single com object right after i used it and in deed i use an InspectorEventHandler class and so on.

    But the meaningful part of my issue is still unsolved. Has anyone an idea, why the attachment context menu doesn't show my button? In Outlook 2010 it works fine, only in Outlook 2013 it doesn't show without first selecting the attachment.


    Ken Slovak MVP - Outlook
    • Marked as answer by Ranfty2000 Thursday, February 21, 2013 8:30 AM
    Wednesday, February 20, 2013 4:01 PM
    Moderator
  • I will try control.Context tomorrow (I'm about to drive home in some minutes) and give you feedback as soon as possible. Thanks for your time :)
    Wednesday, February 20, 2013 4:14 PM
  • Great Thanks to you Ken, i better don't ask you, how you found that :) But it is working very well.

    AttachmentSelection selection = control.Context as AttachmentSelection


    • Edited by Ranfty2000 Thursday, February 21, 2013 8:30 AM
    Thursday, February 21, 2013 8:29 AM
  • The best place to look first when you have questions about anything in the Outlook object model is the Outlook VBA project Object Browser Help file. It has code snippets (mostly in VBA code) for almost everything in the object model as well as other valuable information about the methods/properties/events/objects in the object model.

    --
    Ken Slovak
    [MVP-Outlook]
    http://www.slovaktech.com
    Author: Professional Programming Outlook 2007
    "Ranfty2000" <=?utf-8?B?UmFuZnR5MjAwMA==?=> wrote in message news:a6b42f07-f062-4871-83ff-5ae98f785787...

    Great Thanks to you Ken, i better don't ask you, how you found that :) But it is working very well.

    AttachmentSelection selection = control.Context as AttachmentSelection



    Ken Slovak MVP - Outlook
    Thursday, February 21, 2013 2:37 PM
    Moderator