Other ribbons

Thus far we've ended up with a nicely populated mail composition window. It would be nice to offer the facility to see the action status on received messages (OK, you can infer it from the lack of ability to reply, but bear with me for the sake of this post!) or even to be able to re-enable an erroneously disabled action. This is trivially easy for the mail viewing window - the one you see when you double click an email in Outlook's main window. This window is another Inspector and behaves in exactly the same way as the email composition window, at least as far as our ribbon interaction is concerned. Although we can use exactly the same callback processing (hmmm, perhaps I should have used the prefix "Inspector" in the previous posts rather than "Compose" - now, where's that refactoring tool?) we can't use exactly the same ribbon XML for two reasons: first, back where we first extended the ribbon, I specified TabNewMailMessage as the reference point for adding our controls; the corresponding tab on the email viewing window's ribbon is TabReadMessage. Second, as far as I'm aware, ribbon control ids all have to be unique - certainly, it should be obvious that those we want to touch, for example to invalidate so that they refresh their state and redraw themselves, need to be uniquely identifiable.

You could create this new XML by adding another ribbon object to the project, and deleting the C# file (as I said before, while you can have multiple ribbon definitions, you can have only one object offering IRibbonExtensibility). Or you could just add a plain old XML file and set its build action to "Embedded Resource." I'm not going to show the XML here because it really is just the same as the compose ribbon definition, with the two changes indicated above. (It did cross my mind to investigate some sort of templating process to have to define the XML once and then spit out two almost identical copies, but then I figured that in reality it would be fairly unlikely that any non-trivial project would repeat exactly the same ribbon layout in multiple places.) Equally simple is making the ribbon XML available. If you recall, Ribbon.GetCustomUI returned the email composition window ribbon definition when the ribbon id was Microsoft.Outlook.Mail.Compose - we just need to add an extra if statement to return the new XML when the id is Microsoft.Outlook.Mail.Read, as shown below:

 public string GetCustomUI(string ribbonID)
 {
    switch (ribbonID)
    {
        case "Microsoft.Outlook.Mail.Compose":
            return GetResourceText("NoReplyAddin.ComposeRibbon.xml");
        case "Microsoft.Outlook.Mail.Read":
            return GetResourceText("NoReplyAddin.ReadRibbon.xml");
        default:
            return null;
    }
} 

Something to pay attention to is the onLoad callback on the XML root element, customUI. The documentation is a bit hazy, but experimentation suggests that no matter how many bits of custom ribbon XML you have, exactly one of these onLoad callbacks is invoked - I guess whichever is specified in the first XML to be requested by Outlook. There's no guarantee which XML fragment is loaded first (for example, will you double click an email to read it first, causing the read ribbon to load, or will you create a new email first, loading the compose ribbon?) so what I do is specify the same callback for all of these onLoad handlers. You'll find that if you put a breakpoint in the onLoad handler, it will be hit once and once only, but you can see different call stacks depending on how you caused it to be executed.

Let's move on to something more interesting: displaying the action enabled state for a message showing in the preview pane of the main Outlook window. This will work only for Outlook 2010, of course, since Outlook 2007 doesn't have a ribbon on the main window. There are no surprises in the ribbon XML, which is returned this time in response to a request for ribbon id "Microsoft.Outlook.Explorer" (the tab to specify at the top level is TabMail this time), but the callback code will have to be different, so the differences between this and the original XML are a bit more extensive. The Context of the button reference passed into the callback function represents the window containing the ribbon: for the previous windows, as mentioned earlier, this is an Inspector object; for the main window, it's an Explorer object, hence we have different code for getting hold of the message and its associated action status values.

An Explorer provides a user interface for a number of items - our familiar MailItems in the case of the inbox (or any other mail folder), for example and it also provides a means of selecting one or more items. For the main Outlook window, if you have the preview pane enabled, the first selected message is the one that's displayed there. The rule I chose for the no-reply ribbon controls on the main window is: if there is a single selected item, use that to read and write the action state. Conversely, if there is no selection, or multiple items are selected, I make the ribbon buttons non-functional. Other approaches are possible, such as having the operation of setting the state apply to multiple messages, or have it apply to only the first selected, but I feel that either of those could lead to surprises and confusion... The following code fragment is how I get the MailItem in my onAction and getPressed handlers:

 Outlook.MailItem item = null;
var explorer = control.Context as Outlook.Explorer;
if (explorer != null)
{
    var selection = explorer.Selection;
    if (selection.Count == 1)
        item = selection[1] as Outlook.MailItem;
    Marshal.ReleaseComObject(selection);
    Marshal.ReleaseComObject(explorer);
}

If I wasn't so lazy, I'd implement a getEnabled handler to disable the controls when there isn't a single selected item to make it more obvious that the buttons are non-functional, but the current NoReplyAll release (2.3.3) just ignores button clicks in that case. A tweak for v.next perhaps.

I've only shown interactions with MailItems but now you should be able to put controls on any of the Outlook ribbons and interact with the appropriate item type.

Incidentally, you might be concerned about granting the recipient the ability to remove the protection against inappropriate replying to all or forwarding. Don't forget that this "protection" is incredibly weak: it works only under specific circumstances, namely both sender and recipient are using desktop Outlook and on the same Exchange system. In addition, the savvy user can trivially re-enable the flags using, say, VBA built into Outlook or PowerShell. See my reply to this query on the Research Desktop forum for a few more details.