Welcome to MSDN Blogs Sign in | Join | Help

The reports of VBA's demise have been greatly exaggerated.

There have been spurious reports in the media saying that VBA is dead. Caused quite a firestorm around here as you might imagine -- 'cause it just plain isn't true.

There is an official statement on the VSTO blog here:

\http://blogs.msdn.com/vsto2/archive/2008/01/16/the-reports-of-vba-s-demise-have-been-greatly-exaggerated.aspx

Posted by TQuinn | 1 Comments

VSTO Architecture Demo from TechEd and MSDN WebCast

Good news here -- for me especially :). The demo code from my VSTO Architecture talk at TechEd and in the recent MSDN WebCast is already (mostly) posted here. My colleague Andrew Whitechapel had written the WPF Control and the WCF service that I used in my demo -- which really makes it mostly his demo (thanks Andrew). Anyway, I went to ask him if I could post the code, and noticed the latest issue of MSDN Magazine on his desk with a VSTO article, and lo and behold, he published virtually the same demo on MSDN. Almost completely identical. Thanks Andrew!

The only thing that is different is that his code does the Custom Task Pane the "easy" way, the standard way. In other words, he doesn't override RequestService and implement ICustomTaskPaneConsumer. But this was a small part of the demo and used, really, to demonstrate how VSTO works under the covers. In real life very few of you should ever have a need to override this functionality. However, if you do, feel free to ping me here.

Posted by TQuinn | 2 Comments

TechEd and MSDN Webcast Demos

Geez, I'm looking at this blog, and I'm realizing I've only posted once this year, and that post said I would post more often. What a lame-o!

Since my last post I have been busy. I've spoken at two TechEds -- in the US in Orlando and in Europe in Barcelona. I totally recommend Barcelona for anyone who hasn't been there, what a great city!

But I digress. I've been asked by several people to make available the demo code that I showed at the TechEds. I had a talk on VSTO Architecture at both, and had a "done from scratch" demo showing how to do some advanced things with VSTO. I also did this the other day on an MSDN WebCast, and didn't have time to get through the whole demo (what a lame-o, again!) and said I would post it here. Just want everyone to know that I am cleaning it up for general consumption, and it should be posted here within the next day or so.

 My other demo at Barcelona was integration of VSTA into a WPF application. I could post that demo, too, but it would do you little good at the moment, because VSTA v2 has not yet been released. So, once we have a public release of VSTA (currently planned for early next year) I will post that demo too. I have plans to expand that demo as well -- add fun features over time.

 If you are interested now, and can't wait for VSTA, well we have an answer. We will be having a limited CTP (Customer Technical Preview) of VSTA v2 in the next week. In order to get access to the CTP you need to contact our VSTA partner Summit Software at www.summsoft.com. If you do get the CTP from Summit and want early access to my VSTA WPF Demo, just contact me through the blog and I will get you an early copy. 

Posted by TQuinn | 1 Comments

Back Into the Light

And out of the dark...

I've been dark for a while now, time to come out and let you know what's been going on. I've been heads down writing code for VSTO v3 and VSTA v2 due to release -- hopefully, no promises -- later this year. The new version of VSTO is built upon the new version of VSTA which is built upon the new version of System.Addin. And the new version of System.AddIn is shipping in the .NET Framework 3.5 as promised.

 It has been a ton of work, but we are close to being done now. Those of you who attended TechEd last week got a preview of VSTO v3 in the keynote, and hopefully attended our sessions during the week.

I did a session on VSTO architecture where I gave an overview of the v3 architecture and how we've changed and evolved it from previous versions. I know I promised some of you who were there that I would post my demo sample here -- and I will. It occurred to me, though, that it is of no use to you until you get a more recent drop of Visual Studio that includes all of the bits we showed. As soon as one is available publically I will make sure the sample works with that drop and post it.

I also have some more, in depth, discussion around MAF, VSTA and VSTO planned, stay tuned.

Posted by TQuinn | 0 Comments

VSTA and Generics

VSTA has been officially released now, in InfoPath in the 2007 Office System, and for use by third parties in the VS SDK. So we're getting our first batch of feature requests. Far and away the biggest request so far is generic support. Admittedly, generic support for VSTA hosts is limited. I know it's an excuse, but we had limited time and resources to get VSTA done in time for Office 2007, and we had to make some hard choices. One of the reasons generic support was considered "cut-able" was that with a small amount of work, the user (you) could add the support you needed back in. In other words, there is a workaround. In this blog post, I'm going to talk about how this is done.

So the good news is that the infrastructure supports, to a certain extent, generics. And there is always the flexibility of using custom contracts to support anything you want. I'm going to leave custom contracts to a future post; this post is all about doing the smallest amount of work necessary to minimally support generics in a couple common cases. I'm interested in hearing whether this meets your needs, or if there are more common cases that are not done so easily.

The bad news is that proxygen doesn't support generics at all. It just skips over them when it sees them. But that's one reason why proxygen emits source code. In my mind, anyway (not sure if everyone on the team would agree with this) proxygen was never intended to generate code that gets shipped as is. The code is there to be tweaked. In some cases it may be as simple as renaming some classes. I would expect that many, if not most, hosts will want to use targeted custom contracts for performance – and other – reasons; stay tuned for my next post. And if your OM contains generics, you'll have to write a little code to augment what we put out.

So I took the ShapeApp sample and started changing it. The first case I tried was events. The .NET Framework guidelines tell you to use EventHandler<T> to declare events. ShapeApp didn't use these of course, and, well, that's not too helpful to you, if you followed the guidelines. So I removed all of the custom delegates and changed all of the events to use either System.EventHandler or System.EventHandler<T> when they had custom event args. I also changed the EventArgs class to derive from System.EventArgs, also according to the guidelines. Seems like a bug to me that it wasn't this way in the first place. I did this on both sides, in the ShapeApp app itself and in the proxy assembly that the addins consume.

This worked very well. All I had to do was change the code that populates the TypeInfrastructureManager: I just changed the typeof declarations to use the qualified generic types, and events were done.

The next common case is collections. Again, the guidelines tell us to use the generic collection types when we define collections – and it is ton easier if you've done it this way. ShapeApp has two collections, the DrawingCollection and the ShapeCollection. The first thing I did was change the application's implementation of these collections. As you'll see, I removed a bunch of code from these by using the built-in Framework classes. I derived them both from System.Collections.ObjectModel.Collection<T>. In the DrawingCollection the only thing additional thing I had to do was to override the InsertItem method to set up the drawingNumber field correctly. Notice I made the DrawingCollection type itself internal, and everywhere it was exposed I changed it to System.Collections.Generic.IList<Drawing> just to make sure we were really making things generic.

I had to do a little more work in the ShapeCollection. First off, it supported indexing by string as well as integer. Built-in collections don't support this, of course. So I defined a new interface IShapeList that derives from IList<IShape>. My ShapeCollection class derives from System.Collections.ObjectModel.Collection<IShape> and also implements IShapeList. The old ShapeCollection also fired a couple internal events: ShapeAdded and ShapeRemoved. In order to fire these at the right time (the new generic version of ShapeAdded and the new System.EventHandler version of ShapeRemoved) I had to override InsertItem and RemoveItem. Again, ShapeCollection was changed to an internal type. In this case, in the places where ShapeCollection had been exposed before, I changed it to my new interface: IShapeList. But System.Collections.Generic.IList<IShape> will show up again.

I turned out that just updating the TypeInfrastructureManager population code didn't complete the story – you do need to update it, and in fact we'll take advantage of some additional functionality it supports, but it wasn't as simple as the events. The problem turned out to be that the type returned by GetEnumerator was IEnumerator<T> and not just IEnumerator.

I decided to create a custom adapter for these collections. I created a new generic class (hey, I'm all about generics, now) called CollectionAdapter<T>. I decided to derive CollectionAdapter<T> from RemoteObjectAdapter (rather than ContractAdapterBase). This means that addins would have the ability to late-bind to it in addition to the work we're doing here. I don't actually use this late-binding capability in this sample, but all of the other objects that use the built-in adapters support late-binding, so I wanted to be consistent. And I do plan on taking advantage of this in a future post.

Here is the code from CollectionAdapter<T> is at the bottom. As you can see, there are really two classes here. The EnumeratorAdapter<T> is necessary to support GetEnumerator, of course. The EnumeratorAdapter *does* derive from ContractAdapterBase, because it is only used as an enumerator and never late-bound. The CollectionAdapter wraps an IList<T> and implements IRemoteArgumentArrayListContract which is in System.AddIn.Contract.Collections. The implementation of the contract (and its base contracts) defers to the wrapped list, and where necessary unwraps the RemoteArguments to local instances of T, or wraps them up. The where T : class constraint on the Adapter classes is there so we can use the typeof(T).

OK, so we have an Adapter, how does it get created? Go back to the code in extension.cs and notice we handle the AdapterResolve event. The TypeInfrastructureManager (I'm just going to call it TIM from now on, tired of typing it out) has this hook just for this purpose; for the host to provide custom adapters. The implementation is simple enough, if the TIM asks for an adapter for an IList<Drawing> or an IList<IShape> we instantiate a CollectionAdapter<T> and return it.

But what about the other side? The generated ShapeCollection and DrawingCollection didn't work correctly, so I implemented some new ones. Unfortunately, on the proxy side, I couldn't use System.Collections.ObjectModel.Collection<T> as the base class. This is because I had to have custom enumerators (to call into the IRemoteArgumentEnumeratorContract) and System.Collections.ObjectModel.Collection<T> doesn't expose out the ability to override it. In fact, its Enumerator implementation is internal. So the proxies were a little more work but not much. I had them implement ICollection<T>. They also implement IProxy to identify them to the infrastructure as proxies. Here the implementation just defers to the contract, and when necessary uses the helpers to wrap and unwrap RemoteArguments. The DrawingCollection is implemented as ReadOnly. The ShapeCollection supports Adding and Removing. It also implements a string based indexer like the application's OM class. However, as you can see, this implementation just loops through the elements until it finds the right one. I doesn't use the IShapeList. Why? Because IShapeList is not a contract, and in order to call into it we'd need a custom contract. And I'm not covering custom contract's here. Of course, it would be *far* more efficient to do the looping locally, instead of retrieving each object, pulling across the AppDomain boundary and checking it there. An ideal place for a custom contract – guess I have more work to do ;).

On the proxy side, we handle the ProxyResolve event rather than the AdapterResolve event off of TIM. Makes sense. The code there is equally simple. I also changed the name of IShapeClass to Shape, just 'cause it makes more sense.

If you have macro or addin code written against another version of ShapeApp, it should still compile, but you *will* have to recompile since the types have changed.

Please let me know if this sample is helpful – or not, especially not.

//Collection Adapter

using System;

using System.Collections.Generic;

using System.Text;

using System.AddIn.Contract;

using Microsoft.VisualStudio.Tools.Applications;

using System.AddIn.Contract.Collections;

using System.Runtime.Remoting;

namespace Microsoft.VisualStudio.Tools.Applications.Samples.ShapeApp

{

internal class EnumeratorAdapter<T> : ContractAdapterBase, IRemoteArgumentEnumeratorContract

where T : class

{

internal EnumeratorAdapter(IEnumerator<T> wrappedEnumerator, TypeInfrastructureManager tim) :

base(tim)

{

this.wrappedEnumerator = wrappedEnumerator;

this.tim = tim;

}

private IEnumerator<T> wrappedEnumerator;

private TypeInfrastructureManager tim;

#region Base Class Overrides

protected override IContract QueryContract(string contractIdentifier)

{

if (String.Compare(contractIdentifier, typeof(IRemoteArgumentEnumeratorContract).AssemblyQualifiedName) == 0)

return (IRemoteArgumentEnumeratorContract)this;

return base.QueryContract(contractIdentifier);

}

protected override int GetRemoteHashCode()

{

return wrappedEnumerator.GetHashCode();

}

protected override bool RemoteEquals(IContract contract)

{

if (contract == null)

return false;

if (!RemotingServices.IsObjectOutOfAppDomain(contract))

{

EnumeratorAdapter<T> contractAdapter = contract as EnumeratorAdapter<T>;

if (contractAdapter == null)

return false;

return this.wrappedEnumerator.Equals(contractAdapter.wrappedEnumerator);

}

return false;

}

protected override string RemoteToString()

{

return this.wrappedEnumerator.ToString();

}

#endregion

#region IRemoteArgumentEnumeratorContract Members

RemoteArgument IRemoteArgumentEnumeratorContract.GetCurrent()

{

return new RemoteArgument( new RemoteObjectAdapter(typeof(T), wrappedEnumerator.Current, tim));

}

bool IRemoteArgumentEnumeratorContract.MoveNext()

{

return wrappedEnumerator.MoveNext();

}

void IRemoteArgumentEnumeratorContract.Reset()

{

wrappedEnumerator.Reset();

}

#endregion

}

internal class CollectionAdapter<T> : RemoteObjectAdapter, IRemoteArgumentArrayListContract

where T : class

{

internal CollectionAdapter(IList<T> wrappedList, TypeInfrastructureManager tim)

{

this.wrappedList = wrappedList;

this.tim = tim;

}

#region Base Class Overrides

protected override IContract QueryContract(string contractIdentifier)

{

if (String.Compare(contractIdentifier, typeof(IRemoteArgumentArrayListContract).AssemblyQualifiedName, StringComparison.Ordinal) == 0)

return (IRemoteArgumentArrayListContract)this;

if (String.Compare(contractIdentifier, typeof(IRemoteArgumentArrayContract).AssemblyQualifiedName, StringComparison.Ordinal) == 0)

return (IRemoteArgumentArrayContract)this;

if (String.Compare(contractIdentifier, typeof(IRemoteArgumentCollectionContract).AssemblyQualifiedName, StringComparison.Ordinal) == 0)

return (IRemoteArgumentCollectionContract)this;

if (String.Compare(contractIdentifier, typeof(IRemoteArgumentEnumerableContract).AssemblyQualifiedName, StringComparison.Ordinal) == 0)

return (IRemoteArgumentEnumerableContract)this;

return base.QueryContract(contractIdentifier);

}

#endregion

private IList<T> wrappedList;

private TypeInfrastructureManager tim;

#region IRemoteArgumentArrayListContract Members

void IRemoteArgumentArrayListContract.Add(System.AddIn.Contract.RemoteArgument newItem)

{

T localItem = TypeServices.ObjectFromRemoteArgument(newItem, typeof(T), tim) as T;

if (localItem == null)

throw new ArgumentException();

wrappedList.Add(localItem);

}

void IRemoteArgumentArrayListContract.Clear()

{

wrappedList.Clear();

}

void IRemoteArgumentArrayListContract.Insert(int index, System.AddIn.Contract.RemoteArgument item)

{

T localItem = TypeServices.ObjectFromRemoteArgument(item, typeof(T), tim) as T;

if (localItem == null)

throw new ArgumentException();

wrappedList.Insert(index, localItem);

}

void IRemoteArgumentArrayListContract.Remove(System.AddIn.Contract.RemoteArgument item)

{

T localItem = TypeServices.ObjectFromRemoteArgument(item, typeof(T), tim) as T;

if (localItem == null)

throw new ArgumentException();

wrappedList.Remove(localItem);

}

void IRemoteArgumentArrayListContract.RemoveAt(int index)

{

wrappedList.RemoveAt(index);

}

bool IRemoteArgumentArrayListContract.Contains(System.AddIn.Contract.RemoteArgument item)

{

T localItem = TypeServices.ObjectFromRemoteArgument(item, typeof(T), tim) as T;

if (localItem == null)

return false;

return wrappedList.Contains(localItem);

}

int IRemoteArgumentArrayListContract.IndexOf(System.AddIn.Contract.RemoteArgument item)

{

T localItem = TypeServices.ObjectFromRemoteArgument(item, typeof(T), tim) as T;

if (localItem == null)

throw new ArgumentException();

return wrappedList.IndexOf(localItem);

}

#endregion

#region IRemoteArgumentArrayContract Members

System.AddIn.Contract.RemoteArgument IRemoteArgumentArrayContract.GetItem(int index)

{

T localVal = wrappedList[index];

return new RemoteArgument(new RemoteObjectAdapter(typeof(T), localVal, tim));

}

void IRemoteArgumentArrayContract.SetItem(int index, System.AddIn.Contract.RemoteArgument value)

{

T localVal = TypeServices.ObjectFromRemoteArgument(value, typeof(T), tim) as T;

if (localVal == null)

throw new ArgumentException();

wrappedList[index] = localVal;

}

#endregion

#region IRemoteArgumentCollectionContract Members

int IRemoteArgumentCollectionContract.GetCount()

{

return wrappedList.Count;

}

#endregion

#region IRemoteArgumentEnumerableContract Members

IRemoteArgumentEnumeratorContract IRemoteArgumentEnumerableContract.GetEnumeratorContract()

{

return new EnumeratorAdapter<T>(this.wrappedList.GetEnumerator(), this.tim);

}

#endregion

}

}

Posted by TQuinn | 2 Comments
Filed under:

Attachment(s): ShapeAppAdvancedCSharpGenerics.zip

Outlook 2007 Form Region Sample – Second Edition

Office 2007 is official today, so it is time to update my Form Region Sample. The big difference is new methods on the _FormRegionStartup interface. With input from us here in VSTO, Outlook changed the interface to make it easier to use from VSTO addins and more  friendly to managed dlls. The two new methods mean I could remove a bunch of the workarounds in the previous version. The new interface looks like this:

public interface _FormRegionStartup
{
   
void BeforeFormRegionShow(FormRegion FormRegion);
   
object GetFormRegionStorage(string FormRegionName, object Item, int LCID, OlFormRegionMode FormRegionMode, OlFormRegionSize FormRegionSize);
   
object GetFormRegionIcon(string FormRegionName, int LCID, OlFormRegionIcon Icon); |
   
object GetFormRegionManifest(string FormRegionName, int LCID);
}

The first two methods we've seen before, but the next two make it nice. Let's look at GetFormRegionManifest first. This means that the form region manifest that had to be installed on disk and the path registered in the form region section of the registry, can now be stored as a resource in the dll. This means a change in the registration, of course, which I'll cover below. The implementation of this method is quite trivial. We use the name we register it with to identify the actual manifest. It gets returned as a string.

public object GetFormRegionManifest(string FormRegionName, int LCID)
{
   
if (FormRegionName == "TQForms.RssFormRegion")
   
{
       
return RSSMessages.Properties.Resources.RSSFormRegion;
   
}
   
else if (FormRegionName == "TQForms.PreviewRssFormRegion")
   
{
        
return RSSMessages.Properties.Resources.PreviewRSSFormRegion;
   
}

   
return null;
}

The next method – GetFormRegionIcon – solves one problem from before, but introduces another issue. We now no longer have to store icons as Win32Resources, so we can remove the workarounds we had before to make that happen. But instead, we have to convert the icon from a managed object to a COM compatible object that Outlook can use. The implementation from the sample looks like this:

public object GetFormRegionIcon(string FormRegionName, int LCID, OlFormRegionIcon Icon)
{
   
if (FormRegionName == "TQForms.RssFormRegion" && Icon == OlFormRegionIcon.olFormRegionIconPage)
   
{
       
return Converter.PictureUtils.ToIPictureDisp(RSSMessages.Properties.Resources.FeedIcon); 
    
}
   
   
return null;
}

As you can see, we call a Converter class to turn the resource into an IPictureDisp. Unfortunately, there is no built in .NET way to do this conversion, the Converter class is included in the sample. I'll leave it up to you to check out how it works.

So the xml in the manifest changes a little. Rather than putting in a resource ID, you just put the work addin to tell Outlook to load the particular addin from via the interface. You must do them all the same way – part of getting this work done at that late stage in the cycle was simplifying the assumptions: if one icon is loaded via the addin, they all must be. The xml is here:

<FormRegion xmlns="http://schemas.microsoft.com/office/outlook/12/formregion.xsd">
<name>TQForms.RssFormRegion</name>
<title>View In Browser</title>
<formRegionType>separate</formRegionType>
<showInspectorCompose>false</showInspectorCompose>
<showInspectorRead>true</showInspectorRead>
<showReadingPane>false</showReadingPane>
<hidden>true</hidden>
<addin>RSSMessages</addin>
<version>1.0</version>
<icons>
   
<page>addin</page>
</icons>
</FormRegion>

In fact, if you register the form region as coming from the addin, you must load your icons this way. The change in registration makes it much simpler, but it is subtle. Rather than putting a path in the registry, you put the name of the addin – the same name that goes under the Addins key. But you first prefix it with an equals sign (=). This is the clue to Outlook that the manifest and icons are loaded via these new methods. If the addin is registered this way, the new methods *must* be implemented. If it is not registered this way, the new methods will not be called. This is the the registry script:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Office\Outlook\FormRegions\IPM.Post.RSS]
"TQForms.RssFormRegion"="=RSSMessages"
"TQForms.PreviewRssFormRegion"="=RSSMessages"

Hopefully, you be as happy about these new methods as I am. It makes form regions much easier.

Posted by TQuinn | 2 Comments
Filed under:

Attachment(s): RSSMessages.zip

Outlook 2007 Form Region Sample

I've finally finished my Form Region Sample for Outlook 2007 that was promised. I've been working on it in earnest for a couple weeks now in my spare time. I am now resisting the urge to continue tweaking it: it is time to lockdown and ship. The urge to tweak is there because this is something I'm actually going to use all of the time. Outlook 2007, you may have noticed, has a new folder for RSS feeds, so you can get your blogs right in Outlook. This is cool, but I wanted to change how the posts were displayed – they always excerpt them and then include a link to the full post. The link then opens IE in another window. I wanted to be able to see the full post in the Outlook view: perfect use for a FormRegion!

I've created a FormRegion that hosts the Windows WebBrowser Control, and automatically shows the link. For the purposes of the sample I decided to put it two places and do it two different ways. I show it as an "adjoining" reason in the Preview pane, and as a "separate" region in the Inspector "Reading" view. I'll leave it up to you to experiment with mixing and matching these views. I'll go into where these are specified when I talk about the FomrRegion manifest below.

Implementation

First, let's go through the implementation. We'll start with the ThisAddin class. If you created and Outlook addin in VSTO 2005, you'll notice that this is different. We called it ThisApplication in VSTO 2005 and it derived directly from Outlook.Application. In VSTO 2005 SE, we have restructured this so the ThisAddin class is separate from the Application. It still gives you strongly typed access to Outlook.Application, it just doesn't derive from it anymore. In my Channel9 interview, I discussed why we made this change. If you have any feedback on this change – good, bad or neutral – please send it along.

The important piece of code in the ThisAddin class is this:

//Override the RequestService call to handle

//requests for the FormRegion service

protected override object RequestService(Guid serviceGuid)

{

if (serviceGuid == typeof(Outlook._FormRegionStartup).GUID)

{

if (this.formRegionManager == null)

this.formRegionManager = new RSSMessages.Regions.FormRegionManager();

return this.formRegionManager;

}

return base.RequestService(serviceGuid);

}

ThisAddin provides a new overridable function that we can be used to provide "services" to Office. If you add a ribbon to the project, you will see a similar piece of code for hooking it up. Office queries VSTO addins for various services (ribbons and FormRegions for now, there are others coming). We worked with the Office team to make this mechanism extensible, so that we could add more integrated support for the important services – you won't have to write this code in VSTO Orcas because we're providing a designer – and Office can add services over time and you can have access to them without VSTO having to change.

Anyway, Office requests the services by their COM guid. In this case it is the guid of the _FormRegionStatup interface. The FormRegionManager class in the project provides the implementation of _FormRegionStartup. Let's look at that next. These are the two methods on _FormRegionStartup in Outlook 2007 Beta2 TR. I make that distinction because this interface has changed since then. I'll discuss those changes – and update this sample – in a future post once they ship. Suffice it to say the changes will make some of the difficulties I discuss below go away. They are a good thing.

#region _FormRegionStartup Members

public void BeforeFormRegionShow(FormRegion formRegion)

{

RegionInstance ri = new RegionInstance(formRegion);

ri.OnFormRegionClose += new FormRegionCloseHandler(ri_OnFormRegionClose);

this.regionInstances.Add(formRegion, ri);

}

public object GetFormRegionStorage(string FormRegionName, object Item, int LCID, OlFormRegionMode FormRegionMode, OlFormRegionSize FormRegionSize)

{

if (FormRegionName == "TQForms.RSSFormRegion" || FormRegionName == "TQForms.PreviewRSSFormRegion")

{

return RSSMessages.Properties.Resources.Rss_Form_Region;

}

return null;

}

#endregion

The first method that gets called is GetFormRegionStorage. The FormRegionName parameter corresponds with the name in the form region manifest file, I'll get to that below. It returns the byte array that is the .ofs file you get from outlook. I'll talk about that below, too. Suffice it to say you simply add the ofs file to your project as a File resource and it is a simple line of code to return it as above. Then you get called on BeforFormRegionShow and get passed the instance of Outlook's FormRegion object. This gets called for each instance of the form region that is visible, and there can be any number visible at any time. So I created a separate class to represent each instance of a form region, called RegionInstance. It holds on to Outlook's FormRegion object and pulls all of the controls on the form out of that. See the function InitItems in RegionInstance.cs to see how this is done.

And that's about it for the implemetation: you hook up to events on the controls the way you would on any control and make them work the way you want. There are some interesting details about what I had to do to get the Icon for View in Browser to show up in the Ribbon, though. I'll cover those when I talk about the manifest below.

Creating a Form Region

Of course, to show a form region you have to create one, this is the .ofs file I alluded to above. The way you create one is to go into Outlook, then go to Tools…Forms…Design a Form. You'll get a dialog to choose which form you want to design – I chose RSS Article for this sample. Then you are presented with an Explorer window with the default view for that form and a bunch of tabs. From there you choose Form Region … New Form Region from the ribbon. It will add a new, blank tab and pop up a tool window with fields. You can drag and drop fields from there, and these are automatically hooked up with the outlook fields for this item. For instance the Subject and RSS Feed boxes in my form are done this way – no coding necessary. You can also show the standard Control Toolbox from the right-click menu on the form and add other controls – like the web browser control and progress bar control that I used – to the tool box and use them as well. When you have the form the way you want it, go back to the Form Region button on the Ribbon and select Save Form Region. You'll get the standard Save dialog and this will save your .ofs file. Then just copy the .ofs file to your project and add it to your resources.

You can also open up my .ofs file in the designer. Just go to the Form Region button on the designer and select Open Form Region. You'll see how my form regions was designed.

Form Region Manifest

In order for the Form Region to show up in Outlook at runtime, it must be described in a manifest and this manifest must be registered. The manifest, as you might imagine, is an XML file with a particular schema. Here is an example from my project:

<?xml version="1.0" encoding="utf-8"?>

<FormRegion xmlns="http://schemas.microsoft.com/office/outlook/12/formregion.xsd">

<name>TQForms.RSSFormRegion</name>

<title>View In Browser</title>

<formRegionType>separate</formRegionType>

<showInspectorCompose>false</showInspectorCompose>

<showInspectorRead>true</showInspectorRead>

<showReadingPane>false</showReadingPane>

<addin>RSSMessages</addin>

<version>1.0</version>

<icons>

<page>..\bin\debug\rssmessages.dll,101</page>

</icons>

</FormRegion>

As noted above, the Name element corresponds to what gets passed in to GetFormRegionStorage. The title is what shows up in the Ribbon (in this case) as the visible name. This particular manifest specifies the formRegionType as "separate" meaning it has its own separate window. The other manifest in the project specifies "adjoining" which means it shares the window. The next three elements declare where the form region will be shown. In this case it shown in the reading Inspector, but not the reading pane in the explorer. RSS Articles don't have a Composing inspector, but it they did, this would be false as well. The addin element corresponds to the name of the addin to call when looking for the FormRegionStorage and the code.

I noted above that there was some weirdness with the icon. The good news is that this weirdness goes away when Office ships, but in interest of full disclosure, I'll tell you what I had to do to make it work in this sample. As you can guess from what it says here, the page icon is a resource in my dll. But in this case it *MUST* be "win32resource" not a managed resource. There is no simple way to add a win32resource to a dll in Visual Studio. As you can see I created a simple resource script – res.rc – in my project. I added a custom pre-build step to compile that rc file into a .res file. So far so good. But it turns out to get the res file linked into the dll, I had to manually edit the .csproj file of the project. Open up the .csproj file in notepad and look for the <Win32Resource> element and you'll see what I did. It turns out there is no way that I could find to get VS to add this element, but it is honored by msbuild and passes it on to the compiler as a /win32resource directive. Again, though, the requirement to do this will be going away soon. Stay tuned for further updates.

So, we have a Form Region manifest now, how do you register it? You associate it with a "Message class" – in this case IPM.Post.RSS – and add the path to the manifest as a named value under that key under FormRegions in the registry:

HKEY_CURRENT_USER\Software\Microsoft\Office\Outlook\FormRegions\<MessageClass>.

See the sample registry script in the project to get the exact location.

So that's the story on Form Regions for right now. As I said, this gets easier when Office releases – and easier yet in Orcas. Hope this is enough to get you going in VSTO 2005 SE.

Posted by TQuinn | 2 Comments
Filed under:

Attachment(s): RSSMessages.zip

Back in Redmond

It's good to be home. But we had a great time in Europe and learned a ton. The best thing I learned is that ISVs are using VSTO and using it well. Hopefully they learned from us as well. I have nothing but good things to say about the Frontline program, it was a rousing success.

I was happy to see that VB .NET is being used in several companies. VB suffered a bit of a downturn recently, but it survives and is being used extensively in Office customization. C# is being used extensively as well, of course. One customer asked if they could use ML to customize Office. The short answer is yes -- but you'd have to know what you are doing. If anyone is seriously interested, it could be a future blog post. But the easiest way to do this, of course, would be to write a simple addin in C# of VB and have that load your ML assembly that does all of the work -- that is one of the beauties of .NET, the Common Language Runtime.

 Anyway, I'm going to sit right down and write myself a little Custom Form Region Cypress sample while I wait for my machines to upgrade to Vista.

Posted by TQuinn | 2 Comments
Filed under:

On the road in Europe

It has been almost two weeks now since I arrived in Europe. As noted, I had hoped to blog more, but with all of the working, sight-seeing, eating, drinking and travelling -- not to mention very intermittent internet availability -- I haven't really had a chance.

I am in Vienna now (Wien to the locals, of course). I started in Munich as I noted, and if you are interested I did manage to make an entry on my personal blog about my experiences at Oktoberfest. We then travelled to Nuremburg, which is a lovely old city with a fabulous medieval castle. Next we went to Lindau, which is a little resort city on Lake Constance. Apparently only English speaking people call it that though -- in Germany it is the Bodensee. Anyway, we stayed at a very old hotel that was built over a mineral bath, called Bad Schachen.

On our way to Vienna we spent a couple nights in Salzburg. It is a very interesting city, again, with a fabulous medieval castle. There are some great restaurants in Salzburg. If you want something really unusual check out Carpe Diem.

We have met with three customers so far and a couple interesting and unexpected things came out of the meetings. First, I found a customer that has a real business need for a single addin shared across multiple Office applications (and multiple versions, of course). Another customer wanted it, but had no problem with the factorization described below.

We debated whether we needed an application agnostic addin project in VSTO, and decided against it: in almost every case an addin targets a specific application and desires strongly typed access to the application object. Of course there are common cases where one desires to have the same code run in different apps -- for instance a common Task Pane -- but this code is easily factored out into a separate dll project. I still believe that having a single addin for multiple apps is a bit of an edge case, so I don't know if we'll add a project specifically for this case. It is possible, however, to take an app specific VSTO addin project and alter some of the hidden code and make it work across apps. Perhaps a subject for another post.

The other thing I have seen more than once is that VSTO customers build addins that then serve as platforms for further addins. There seems to be a trend here. Fortunately we have a solution for this: MAF and VSTA. It is no problem to utilize the same addin model with which we built VSTO and use it to further customize these addins. A virtuous cycle.

As you may have guessed, I haven't yet gotten to my Cypress-based Form Region sample. If you are waiting patiently for it and want to send me a message, do just that, send me an e-mail and it will help me prioritize my work.

Posted by TQuinn | 2 Comments

Catching up: Cypress (VSTO 2005 SE), Frontline and more...

Wow, I can't beleive that it has been more than two months since I posted, but the numbers don't lie. It has been an eventful summer. We've been working like dogs on new features that you will be hearing about in the fullness of time. I also had a nice long vacation in Chatham, MA. Worth a visit if you can make it, it's on the elbow of Cape Cod.

Anyway, I'm sure you've all heard by now about Cypress-- er, I mean VSTO 2005 SE (if you want to see the full name -- and download the beta -- click here ).VSTO 2005 SE is an addon pack to VS 2005. It adds app-level addin support for several Office apps for both Office 2003 and Office 2007 (or should I say 2007 Office...). This is a step on the way to Orcas, which I've been blogging about for sometime now. We wanted to get something in the market for 2007 Office as soon as it shipped, and we took the opportunity to backfill some support for Office 2003. If you want to hear about some of the thinking behind what we chose to do and not do for VSTO 2005 SE check out channel 9 in the next week or so. I did a little interview for them and covered some of this ground there. I don't have a direct link because it hasn't made it up there yet, but any day now.

One of the things I said in the interview was that I had an Outlook Form Regions sample on my blog. Clearly, it had been too long since I had been here, since this is not true. I was thinking of the WPF sample, I guess. I had been showing a Form Region sample at various conferences, but I never got it to the point where it was sharable. So now I have some work to do. Cypress is out, (I can't get used to VSTO 2005 SE I guess) so I will create a simple form region sample to show you how to do these with Cypress and 2007 Office Beta 2 Technical Refresh. It isn't totally simple with Cypress, but there is a lot of power there. I'll try to get this done in the next couple weeks, maybe I'll get it up before the interview gets posted if I'm lucky.

I am planning on blogging quite a bit in the next couple weeks. The reason being that I am on the road. I am currently in the air above Canada on my way to Munich, Germany. (SAS airlines has WiFi on the plane!)

I am embarkinng on some customer engagements for the Frontline program. Some of my fellow Server and Tools bloggers have mentioned this already. My division has a great program where senior R and D folks go out "into the field" to work directly with customers. We choose customers who are using our products and give them direct access to Microsoft's people. Everything I've read about this is extremely positive. I'm especially lucky in that I get to go to Europe for some of my engagements. And I swear that I happen to be in Munich during Oktoberfest is just a lucky coincidence. One that I will take full advantage of, of course.

I'm travelling with another VSTO dev lead and we will be visiting Nuremberg and Lindau, Germany and Vienna, Austria in addition to Munich. I'll try to post several times on the trip. Hopefully I'll be able to share some frontline VSTO experiences.

Posted by TQuinn | 0 Comments

Using Windows Presentation Foundation in VSTO -- with "Cross-bow"

I've been working on this on and off since TechEd. I had a customer ask if it was possible to use WPF (formerly Avalon) in a VSTO Custom Task Pane. It is possible, and we're dealing with CTP (Community Technical Preview) releases on top of beta releases, so considering that, it works fairly well. I told the customer I'd write up a quick little sample to show him how it is done, and I decided to share that sample here as well.

It didn't turn out to be as quick as I'd hoped, but at long last it is done. As with any sample code, it is provided "as is" with no warranties or promises implied or stated. It is just to show you how to get it going.

To use this sample, the first thing you need to do is get everything installed. You need Visual Studio Tools for Office 2005 (or one of the Visual Studio Enterprise Editions that contains the VSTO projects) to get started. Then you need to start installing the betas and CTPs. I installed all of this stuff onto a Virtual PC. I definitely don't recommend just installing this beta stuff on your main machine. VPC is a convienient way to go.

The first beta you need is the Office 2007 Beta 2. You can get that here.

Next you need the .NET Framework 3.0 June CTP. You may recall that we recently reverted the name of the new .NET Framework pieces from WinFX back to the .NET Framework. There is still a download for WinFX 3.0 Runtime Components Beta 2 out there. In fact, this is what I was using up until today when I started writing this up. One of the things that took this sample so long to get done was that I was trying to workaround some UI painting bugs in Beta 2. Then I noticed that there was an updated the drop (and that we changed the name). The good news is that these painting bugs have been fixed. So I have updated my sample to use this new CTP. If you still have beta 2 of WinFX, then the code will likely compile fine, but you will probably have trouble getting the WPF control to show up. Resizing the Custom Task Pane got it to show up. But as I said, with the latest drop, this problem is fixed. You can download the .NET Framework 3.0 June CTP here. If you are updating from Beta 2 be sure to read the release notes about removing Beta 2 and installing the CTP.

Next you need the June CTP of VSTO "v3." This is available here, but first you must perform the workaround I described in my previous post (here) that will allow it to install over the .NET Framework 3.0.

And finally you need the June CTP of the .NET Framework 3.0 Development Tools (codename "Cider"). To use these you will need the June CTP of the .NET Framework 3.0. The older drop that worked with WinFX Beta 2 seems to have been removed – I couldn’t find it anyway.

So now your machine is set up, let’s look at the solution I provided. You’ll notice there are six projects. ExcelAddin1 and WordAddin1 are what you’d expect them to be, addins for Excel and Word respectively. We’ll get to those in a minute. There are also the associated setup projects for those addins. You can ignore these – they come along with the addin projects and would be used if you wanted to publish your addins.

Finally there are two WPF projects: TaskPaneControlDesign and TaskPaneControls. The first – TaskPaneControlDesign – I created as a convenience. You’ll see why when you look at the TaskPaneControls.UserControl1.xaml. When you double-click on this, it brings up the “designer” and as you can see the designer for user controls has not been implemented yet.

So I created the TaskPaneControlDesign project simply so I could use the designer to layout my control. Open up Window1.xaml and you’ll see my form. I then went into the XAML view and cut and pasted the xaml from the <grid> tag on down into the WPFUserControl.xaml. As you can see, WPFUserControl is very simple: it has a TextBox and a Button. When you click the button it throws up a message box showing whatever is in the TextBox. Just something quick and easy to get this going.

The "cross-bow" integration code is then duplicated in each of the addin projects. It is the same, so this applies to both. The first step is to make the proper references. You’ll need to add references to the WPF assemblies: PresentationCore, PresentationFramework and WindowsBase. You’ll also need to reference the “cross-bow” assembly: WindowsFormsIntegration. “Cross-bow” contains the ElementHost class that is used to bridge between WinForms and WPF.

You’ll note that I have my WPF control in a separate project from the Addin. I did this on purpose. I believe it is possible to mingle WinForms and WPF code in the same project, but you get into a bunch of name collisions between the different namespaces, since both WinForms and WPF have a Button class, among many others. I found it much easier to keep the two worlds separate. So, next you’ll need to reference your WPF project so you can see the control.

Then you'll need to add a WinForms UserControl to the addin project. You will use this as the basis for the Custom Task Pane. Go into the code view of the UserControl in the sample and you will see this code:

private System.Windows.Forms.Integration.ElementHost elemHost;

public UserControl1()
{
    InitializeComponent();
    this.elemHost = new System.Windows.Forms.Integration.ElementHost();
    this.elemHost.Child = new TaskPaneControls.WPFUserControl();
    this.elemHost.Dock = DockStyle.Fill;
    this.Controls.Add(this.elemHost);
}

So it is really very simple. ElementHost is a WinForms based control that can host WPF Controls. We create an instance of ElementHost, then create an instance of our WPF Control (TaskPaneControls.WPFUserControl) and set it as the Child on the ElementHost. I then set the DockStyle to DockStyle.Fill on ElementHost to fill up the WinForms UserControl. Then I add the elementHost to the Controls collection on the WinForms UserControl.

The final piece of code is in ThisApplication.cs, and is the standard code for adding a CustomTaskPane to Word or Excel:

private void ThisApplication_Startup(object sender, System.EventArgs e)
{
        Microsoft.Office.Tools.CustomTaskPane ctp =
            this.CustomTaskPanes.Add(
            new WinFormsUserControl(), "WPF in Crossbow");
        ctp.Visible = true;
}

Of course in this code, we add the WinFormsUserControl to the Custom Task Pane.

Hit F5 and everything should work. I’ve set the WordAddin as the start up project because it works better. There is a bug with focus somewhere that affects the ExcelAddin – it seems impossible to actually type into the text box in the WPF control.

But this is what Betas and CTPs are for, after all, finding bugs. Please try this stuff out and report bugs as you find them. Try out the other Office applications, use a bunch of controls, make them do fun things. The more we bang on it now, the better it will be when we ship.

 

 

Posted by TQuinn | 0 Comments
Filed under:

Attachment(s): WPF in VSTO.zip

TechEd 06: Chalk-talk today, MAF 101

I'm doing a "chalk-talk" today at TechEd -- at 3:45 in DEV (Blue area) Theatre 1 in the TLC. The listed subject is Designing Managed Add-ins for Reliability, Security and Versioning, the same subject of the talk Jim and I did at PDC. I've refined the talk a lot since then. I've been giving this presentation internally at Microsoft (and JackG and I have been doing webcasts with it recently) for the MS developers that are now implementing MAF-based models. Of course VSTA is based on MAF. So InfoPath 2007, the earliest adopter of VSTA has used MAF, and now the next version of VSTO that comes out with Orcas will be VSTA\MAF based. And we're talking to a bunch of other teams; it is taking root.

I've been calling this talk MAF 101. I dig into the details of the architecture as well as the Framework, and talk about the lessons we learned while building it. I'll also look at some samples to show how it works in the real world.

Posted by TQuinn | 0 Comments
Filed under:

TechEd 06: VSTO 'v3' talk this afternoon

Mike Hernandez and I are speaking today (3:15 in room 153) about VSTO 'v3' and what we are doing with Office 2007. We'll be showing several demos of the new programmability features enabled by the next version of VSTO in Office 2007. VSTO 'v3' will release with VS 'Orcas,' and our talk will focus on the features we'll be enabling then.

However, there are some late-breaking additions to our talk. We'll also be talking about the VSTO "Cypress" release and some of the demos show what you can expect there as well.

We hope to see you this afternoon. There are also VSTO booths in both the Green (Office) and Blue (Dev) areas. So come see us at the booth, too.

Posted by TQuinn | 0 Comments
Filed under:

VSTO 'v3' June CTP can run VSTO 2005 customizations

One of the biggest complaints we had on the February CTP of VSTO v3 was that you couldn't run your VSTO 2005 customizations in Office 2007. I'm happy to say we fixed that problem in the latest CTP for June.

What this means is that when you get the VSTO v3 June CTP your old customizations will continue to run in Office 2007. It does not mean there won't be any problems, though. It is a CTP, and there are likely issues. We haven't done extensive testing in this area yet, but we've recieved anecdotal evidence the things seem to run OK. Of course, I can't make any promises on pre-release software, as I'm sure you know. The reason we do these things is to get as much testing as possible and to actually find the problems, so please let us know where you have problems.

I also want to set realistic expectations. We've tried to make the runtime work, but we haven't gotten to the design time yet. This means that your VSTO 2005 projects will not work in Visual Studio once Office 2007 is on the machine. So don't put this on your main development machine. As with any Customer Technical Preview, we recommend you install it on a secondary machine that can be wiped. I use Virtual PC for this, it works well.

We wanted to get early Office 12 support out there as soon as possible, that was the main goal here. We also did not want to break existing customizations at runtime. That's hopefully fixed now too. The next goal is to make the design-time happier. There are many issues around different versions of Office on the same machine and how that interacts with VSTO projects. I'll post more on the design-time later.

Posted by TQuinn | 0 Comments
Filed under:

VSTO 'v3' June CTP and WinFX Beta 2

So many of you have noticed that we got a little surprised by WinFX Beta2. The June Customer Technical Preview (CTP) of VSTO 'v3' requires February CTP of the WinFX Runtime Components along with Beta 2 of Office 2007. Well those Windows guys went and got their Beta 2 out at the same time. So it stands to reason that you would want the Beta 2 version of WinFX instead of the old Febrary CTP.

Unfortunately, you have also noticed that the VSTO v3 CTP won't install over the Beta 2 version of WinFX. But I have good news, there is a simple workaround to enable it. The VSTO v3 CTP looks for a particular value under a particular registry key to verify that WinFX is installed. You can just add that registry key and value, and the VSTO v3 June CTP will install:

[HKEY_LOCAL_MACHINE\Software\Microsoft\WinFX RunTime\3.0\Setup\Indigo]
"InstallSuccess"=dword:00000001

 

Now as you can probably guess, the problem happened because this key changed between the WinFX February CTP and Beta 2. Indigo was the code name, it is now called Windows Communication Foundation. So when Beta 2 makes its registry keys, it doesn't do an Indigo key any more, it makes a Windows Communcation Foundation key. But of course the VSTO v3 setup looks for Indigo, which isn't there .... you get the idea.

So, this should get you unblocked, as far as the install is concerned. I don't know of any problems with actually using WinFX Beta 2 instead of the February CTP version, but that doesn't mean there aren't any. With this much Beta and CTP code at the same time, there are bound to be issues. If you run into further blocking issues with this configuration, please let us know.

 

Posted by TQuinn | 10 Comments
Filed under:
More Posts Next page »
 
Page view tracker