Compartir a través de


Backstage and property pages

In the discussion so far the add-in requires the sender to remember to click the no-reply-all button before sending a message. What about causing all newly created messages to have reply-all disabled automatically (though letting the user manually enable it before sending if required)? It's pretty straightforward to do this: in the NewExplorer hander, if the window is showing a newly created message (i.e. , is an email composition window), then disable the reply-all action; and one way to distinguish between viewing a newly created event and reading an existing email is to look at the MailItem.Sent property. If Sent is false, it's a new message. Perhaps it's a bit draconian to do this unconditionally and maybe a nicer user experience would be to offer the choice about doing this or not via some configuration options, and that's the point of this post.

A new feature in Office 2010 is the Backstage View, which brings (almost) all of the program settings into one window and this is where we can add the add-in's customization options. Guess what? You define the backstage contents via ribbon XML, in an extra section on the blob returned in response to a request for the Explorer custom UI. Here's a variant which defines a single checkbox (I've elided previously discussed content):

 <customUI ...>
 <ribbon>
  <tabs>
   <tab idMso="TabMail">
    ...
   </tab>
  </tabs>
 </ribbon>
 <backstage>
  <tab id="tabNoReply" insertBeforeMso="TabHelp" label="NoReplyAll">
   <firstColumn>
    <group id="grpOptions" label="NoReplyAll Settings">
     <topItems>
      <checkBox id="chkNoReplyAll" label="Disable reply all for new messages" tag="DisableReplyAll"
                getPressed="Flag_GetPressed" onAction="Flag_Action" />
     </topItems>
    </group>
   </firstColumn>
  </tab>
 </backstage>
</customUI>

There's a very detailed description in the MSDN library so I'll refer you to that for the details of what you can put here. As with other ribbon controls, the checkbox can make use of callbacks: here I have callbacks for getting the current state of the disable reply setting and to set it.

 public bool Flag_GetPressed(Office.IRibbonControl control)
{
    return GetFlag(control.Tag);
}

public void Flag_Action(Office.IRibbonControl control, bool pressed)
{
    SetFlag(control.Tag.Split, pressed);
}

I store the setting in the registry using the control's tag as the value name (so that I can use the exact same callback methods in future for any similar boolean value in my configuration settings), using access methods like the following:

 private const string key = @"HKEY_CURRENT_USER\Software\SOME_NAME_HERE";

internal static bool GetFlag(string name)
{
    bool value = false;
    try
    {
        value = (int)Registry.GetValue(key, name, 0) != 0;
    }
    catch
    {
    }
    return value;
}

internal static void SetFlag(string name, bool value)
{
    try
    {
        Registry.SetValue(key, name, value ? 1 : 0);
    }
    catch
    {
    }
}

I'm being rather lazy here and silently ignoring write failures and treating read failure as if the value were false.

Recall how ribbon control getXxx callbacks are called automatically once only, when the ribbon XML is first shown: the same applies the the backstage controls, and I haven't yet determined when I can intercept the backstage being shown to invalidate this checkbox in order to force a re-read from the registry. Suggestions welcome...

Right, we've got the checkbox and flag; how do we use it as outlined at the start of this article? In the NewInspector handler, if GetFlag("DisableReplyAll") returns true, set the new mail item's Actions[2].Enabled to false. That's it.

As mentioned earlier, Outlook 2007 has no backstage so, if we want to offer the user settings, we need to provide a property page. A property page is nothing more than a Windows Forms UserControl which implements the Outlook.PropertyPage interface (yes, an interface whose name does not start with "I"), typical methods for that interface implementation being:

 public void Apply()
{
    // Save stuff to registry here
}

public bool Dirty { get; private set; }

public void GetPageInfo(ref string HelpFile, ref int HelpContext)
{
    // Can safely ignore this method, and do nothing
}

The property page code does have to set the Dirty flag when any control has changed, so that Outlook will enable the save button correctly. Well, we're getting closer to correct working anyway, but not quite there. Unfortunately, this does not appear to be sufficient, but the solution can be found via a web search, or in Carter & Lippert's book that I've mentioned before. As well as setting the Dirty flag, you need to poke Outlook to tell it to notice the change, via PropertyPageSite.OnStatusChange. Unfortunately, again, this interface isn't easy to find and you need to do a bit of reflection to find it - put the following in the property page's loaded handler/override to squirrel away a reference to the PropertyPageSite interface:

 var windowsFormsStringName = typeof(Form).Assembly.FullName;

Type oleObjectType = Type.GetType(
                                       Assembly.CreateQualifiedName(windowsFormsStringName,
                                                                    "System.Windows.Forms.UnsafeNativeMethods")).
                                 GetNestedType("IOleObject");
MethodInfo getClientSiteInfo = oleObjectType.GetMethod("GetClientSite");
 
this.site = (Outlook.PropertyPageSite)getClientSiteInfo.Invoke(this, null);

(This pretty much lifted directly from the VSTO book, so thank you to the two authors for letting me show it here.) We need to tell Outlook that the property page exists - Outlook.Application has an OptionsPageAdd event, used to request new property pages, so add this to ThisAddin_Startup:

 this.Application.OptionsPagesAdd += (pages) => { pages.Add(new YourPropertyPage(), "seems to be ignored..."); };

It's not a mistake to have property pages in Outlook 2010 but there's not much need here after all that work on the backstage earlier. You could use something like this to only attach the property page with earlier versions of Outlook:

 int majorVersion = int.Parse(this.Application.Version.Split('.')[0]);
bool hasBackstage = majorVersion >= 14;
if (!hasBackstage)
    ... add property page ... 

If you run this code, you'll see that, while the property page appears, the title is somewhat lacking. Add another few lines to your property page class:

 [DispId(-518)]
public string PageCaption
{
    get { return "NoReplyAll Options"; }
}

That -518 magic number is something Outlook looks for to get the title for the property page. And, with that, we have a perfect property page, and a modern looking backstage view, depending on the version of Outlook.