2
votes

Overview

I have an Outlook add-in created with VSTO. The add-in has a single ribbon (visual designer) for the Mail.Compose ribbon type. The ribbon tab ControlIdType is set to "Custom". The only code in the add-in other than designer code is the following Load handler for the ribbon. this.Context.CurrentItem is unexpectedly returning null.

Code

private void RibbonComposeMail_Load(object sender, RibbonUIEventArgs e)
{
    try
    {
        var inspector = this.Context as Outlook.Inspector;
        if (inspector == null)
        {
            throw new ApplicationException("Fail - Step 1");
        }

        var currentMailItem = inspector.CurrentItem as Outlook.MailItem;
        if (currentMailItem == null)
        {
            throw new ApplicationException("Fail - Step 2");
        }

    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

Steps

  1. Open draft email. Ribbon loads OK.
  2. Open email from inbox.
  3. Open same draft email. Ribbon fails at step 2, inspector.CurrentItem is null.

Notes

  • I've tested this in Outlook 2007, 2010 and 2013, with an Outlook 2007 and 2010 add-in created in VS2010, and Outlook 2010 add-in created in VS2012. All behave the same.
  • Repeatedly opening the draft email doesn't appear to cause the issue, an Email.Read inspector has to be opened in between.
  • The ribbon tab ControlidType matters. "Custom" will cause the issue, but the default option of "Office" doesn't exhibit the issue.
  • Flipping the scenario on its head and setting the ribbon type to Mail.Read gives the same result, provided the sequence of opening is reversed to Inbox > Draft > Inbox (fail).
  • All possible permutations of calls to Marshal.ReleaseComObject on the inspector and currentMailItem objects makes no difference.
3
Can anyone else at least replicate the error? As far as I can see I'm doing nothing wrong. This looks to be the accepted way to get the item associated with a ribbon, but it's failing! Even this VSTOTeam blog uses much the same technique - blogs.msdn.com/b/vsto/archive/2010/02/23/… - I tried their code, which is only subtly different, and it too exhibited the error. Could this be a bug that's been kicking around un-noticed for the last 3 Outlook releases?Snixtor

3 Answers

4
votes

I had the same issue myself.

I have designed a Ribbon Bar for Outlook Calendar appointments, with a few extra fields that I wanted to save with each appointment (eg "Does this Meeting save travel ?")

I managed it, but, it was tricky to do.

As you've said, when your Ribbon1_Load function gets launched, the ActiveInspector() is null... so how are you supposed to get details about the current Email message or Calendar appointment ?

Here's what you need to do. This example is based about Calendar appointments, but it's very easy to adapt to EmailItems instead.

First, in the ThisAddIn.cs file, you need to make a few changes:

public partial class ThisAddIn
{
    Outlook.Inspectors inspectors;
    public static Outlook.AppointmentItem theCurrentAppointment;

    private void ThisAddIn_Startup(object sender, System.EventArgs e)
    {
        inspectors = this.Application.Inspectors;
        inspectors.NewInspector += new Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
    }

When the user opens or creates a new Outlook item, our "Inspectors_NewInspector" function will get called, and at that point, we are able to get details about the item:

void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
{
     //  This function (apparently) gets kicked off whenever a user opens a new or existing item
     //  in Outlook (Calendar appointment, Email, etc).  
     //  We can intercept it, modify it's properties, before letting our Ribbon know about it's existance.
     //
     theCurrentAppointment = null;

     object item = Inspector.CurrentItem;
     if (item == null)
         return;

     if (!(item is Outlook.AppointmentItem))
         return;

     theCurrentAppointment = Inspector.CurrentItem as Outlook.AppointmentItem;
}

With this code in place, we can adapt our Ribbon1_Load function to take this "theCurrentAppointment" variable, and read in details about the Calendar appointment which the user is creating/modifying.

private void Ribbon1_Load(object sender, RibbonUIEventArgs e)
{
     //  When this function gets called, "Globals.ThisAddIn.Application.ActiveInspector()" is always NULL, so we have
     //  to fetch the selected AppointmentItem via the ThisAddIn class.

     if (ThisAddIn.theCurrentAppointment != null)
     {
         //  Our Ribbon control contains a TextBox called "tbSubject"
         tbSubject.Text = ThisAddIn.theCurrentAppointment.Subject
     }
}
3
votes

Mikes comments helped reveal to me something a little more curious about the behaviour. At step 1, the RibbonComposeMail_Load event is called once. But at step 3 it is called twice. The first time the event is called at step 3, this.Context.CurrentItem is null, but the second time the event is called, the property holds the email.

It was comparing item values in the NewInspector event with those in the ribbon Load event which made me notice this. Because the sequence of events at step 3 is: Ribbon_Load, NewInspector, Ribbon_Load. I was getting Ribbon_Load to MessageBox.Show the subject of the mail item in ThisAddIn.CurrentMailItem, but was quite surprised to see it was the subject of the previous email opened, i.e. the inbox email at step 2!

As it turns out, the solution then is to ignore everything in the Ribbon_Load event if this.Context.CurrentItem is null, because a second Ribbon_Load event is about to be triggered with the correct values set. As to why we see this strange behaviour in step 3 of my example, but not step 1? That's probably a question for the people the implemented the Outlook Object Model.

0
votes

I'm not 100% sure, but it sounds like the garbage collector have cleared your this.Context.

Could you try to put your Context in a private field and get the currentitem from it:

private readonly Context _context = new Context(); 


private void RibbonComposeMail_Load(object sender, RibbonUIEventArgs e)
{
    try
    {
        var inspector = _context as Outlook.Inspector;
        if (inspector == null)
        {
            throw new ApplicationException("Fail - Step 1");
        }

        var currentMailItem = inspector.CurrentItem as Outlook.MailItem;
        if (currentMailItem == null)
        {
            throw new ApplicationException("Fail - Step 2");
        }

    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

If this doesn't help, i normaly use the Outlook.Application object to get the currentitem:

readonly Outlook._Application _application = new Outlook.Application();

var selectionList = _application.ActiveExplorer().Selection;

foreach (Object selObject in selectionList)
{
    if (selObject is Outlook.MailItem)
    {
        var outlookMail = (selObject as Outlook.MailItem);
    }
}

Or if you only need the currentitem, as you do in your case:

var mailItem = _application.ActiveExplorer().Selection[0];