次の方法で共有


There's more to Outlook than MailItems

I've published a new version of the NoReplyAll Outlook add-in with pretty much the only change being the addition of a no-forward button to meeting requests.

As indicated in a couple of previous posts, everything in the add-in until this point has been implemented in terms of Outlook MailItem objects.A look around the object model will show you that most items that Outlook looks after for the user - appointment items, meeting items, etc. - tend to have similar properties, such as sender, subject, and so on. Unfortunately, there is no common OutlookItem base class/interface from which all these derive - there's no common ground between a MailItem and a MeetingItem more sophisticated than System.Object (actually System.__ComObject if I'm being precise). This means that I can't easily generalise a snippet of code like the following to handle any sort of Outlook Item:

 var item = inspector.CurrentItem as Outlook.MailItem;
if (item != null)
     item.Actions[2].Enabled = !pressed;

Well, that's not quite true... One incredibly easy way is the new(ish) dynamic keyword in C#:

 dynamic item = inspector.CurrentItem;
if (item != null)
     item.Actions[2].Enabled = !pressed;

Using dynamic pushes off the property lookup until runtime, so I can write code that tries to touch any named property regardless of the compile-time type of the variable. If the property does not exist, the attempt to access it will throw an exception, which is one disadvantage of this technique - I'd need to surround my accesses with try...catch to be robust. Another easily worked around disadvantage is that, as written, because item is dynamic, then so is Actions and element 2 of that, and the Enabled flag, which is a smidge inefficient - the "fix" is to do something like:

 dynamic item = inspector.CurrentItem;
if (item != null)
{
     var actions = item.Actions as Outlook.Actions;
     actions[2].Enabled = !pressed;
} 

The biggest disadvantage of this for me, though, is that for various reasons, I need to keep my published add-in running with .NET Framework 3.5, and dynamic only comes in with version 4. Before the days of dynamic, we had to use reflection, which isn't actually all that scary...

Here's a routine to get hold of a (public) property - a poor man's dynamic lookup:

 internal static object GetProperty(this object item, string propertyName)
{
    return item.GetType().InvokeMember(propertyName,
                                       BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty,
                                       null,
                                       item,
                                       null);
}

A couple of things to say about that. First, notice the "this" on the first parameter - this (along with the "static") indicates that this is an extension method, which means I can use it as:

 var ob =  item.GetProperty("Actions") as Outlook.Actions;

making it look like any "normal" property access. The second thing to say is that there is a number of ways to access properties via reflection - why did I choose InvokeMember here instead of the rather more obvious and much less typing GetProperty? It turns out that the objects we're interested in, as mentioned above, are represented as System.__ComObject, and the Type access methods, such as GetProperty only tell you about that object and not about the COM object behind it: InvokeMethod, on the other hand, does reach through the wrapper.

Putting that together, I can write code almost as concise as using dynamic:

 var item = inspector.CurrentItem;
if (item != null)
{
     var actions = item.GetProperty("Actions") as Outlook.Actions;
     actions[2].Enabled = !pressed;
} 

With this in place, and all references to specific types of Outlook item removed, I've almost reached a state in which I can set no-reply, no-forward, etc. on pretty much anything that Outlook can display. The one extra bit of work is connecting things to ribbons - although the functions for setting, displaying and clearing the action flags are identical for all the object inspector windows, the appropriate ribbons have different identifiers and, in some cases, different tab identifiers, so I have to create a few more snippets of XML and wire them up. It's all pretty much as I described before but just, for example, a new blob of ribbon XML for "Microsoft.Outlook.Appointment" which references tab TabAppointment instead of the one I showed earlier for "Microsoft.Outlook.Mail.Compose" which adds controls to TabNewMailMessage.

Now, I can prevent (well, slow them down a little) people for forwarding meeting requests... Maybe not as common as wanting to disable reply-all on emails, but some people asked me for it.