This task is straight forward using VSTO 2005 and code-behind in your document. It goes as follows:
Assume you’ve developed a great Windows Forms UserControl called “MyButton”
You can insert it into your word document by doing this:
// Insert the control into a 10x15 square, with "MyButton1"
// serving as the control's unique identifier on the document
ThisDocument.Controls.AddControl(new MyButton(), range, 10, 15, "MyButton1");
Where “ThisDocument” is a Microsoft.Office.Tools.Word.Document (not a Microsoft.Office.Interop.Word.Document) and “range” is some Microsoft.Office.Interop.Word.Range that refers to a range of text in your document.
And, like magic, VSTO “takes care of the business” for you. They generate an OLEControl that hosts your Windows Forms control and exposes it to the native Word document control. Indeed, the above call actually returns an OLEControl object. The native word document hosts your control as an InlineShape.
Life is good, no? It seems like we can insert anything we want into that unmanaged word document widget, right?
Not exactly. If you deviate from the VSTO model, it becomes much more circuitous to get your control inserted into the word document, and in fact, inserting the control as an InlineShape seems to be impossible (more on that later).
Why would you ever want to deviate from the VSTO model? Well, if you want your AddIn to load with the Word application instead of with only a single document or template, and have your toolbars etc. present for all documents, then VSTO can’t help you. They only provide the means to bind to a single document or template. Why not bind to a template then, and make that the default template, so all documents load the code-behind assemblies? Clever… diabolically so… but VSTO does not work with global templates. There is no way to get your VSTO-prepared code to run with Word in the general case, or with all new/existing documents.
Still interested? Do you really have a pressing need to put your own managed controls and have them hosted on a word document? If so, read on, but clothe thyself in fortitude, and prepare for ghastly workarounds, ill-mannered code and harrowing hacks. This scenario is explicitly not supported by the VSTO team, nor is it documented anywhere at the time of writing.
It turns out that you can expose your Windows Forms controls to COM applications, and have the COM containers host and display them just fine. There are some quirks, especially with event processing. In fact, I’ve heard COM apps hosting a textbox sometimes don’t give the textbox a blinking insertion caret, making it impossible to insert text into the textbox.
The strategy is to export and register your WinForms control as a COM control so that it can be picked up by COM applications (like Office apps). You can do most of this conveniently in Visual Studio.
There are two articles that describe how to do this:
[1] - Hosting Windows Forms Controls in COM Control Containers
[2] - Exposing Windows Forms Controls as ActiveX controls
I’ll take the relevant portions from each and present them here.
You need to create a Windows Forms control, and make sure the project containing that control registers it for COM Interop. In VS 2005, you can do this by right clicking on a project, selecting “properties,” and in the “build” pane, check “Register for COM Interop.”
Now when you build your control’s project, VS will register it as a COM component. However, not all of the registry entries needed to stick this sucker into Word (or other COM containers) are there. You can add some custom code on your control that gets called whenever you register it for COM Interop by implementing the “ComRegister” method. In this method, you can write code that manually creates the necessary missing keys needed to get hosted by COM control containers. The function will get called every time VS builds your project. I would recommend slapping this, verbatim, into your control class:
using System.Runtime.InteropServices; // COM attributes
using Microsoft.Win32; // RegistryKey
namespace WindowsControlLibrary1 {
// Stops control from getting a new CLSID
// at each registration
[Guid("E73CD054-1247-4853-AF05-B7D26D993E85")]
[ProgId("UserControl1.UserControl")]
public class UserControl1 : UserControl {
...
[ComRegisterFunction]
static void ComRegister(Type t) {
string keyName = @"CLSID\" + t.GUID.ToString("B");
using( RegistryKey key =
Registry.ClassesRoot.OpenSubKey(keyName, true) ) {
key.CreateSubKey("Control").Close();
using( RegistryKey subkey = key.CreateSubKey("MiscStatus") ) {
subkey.SetValue("", "131457");
}
using( RegistryKey subkey = key.CreateSubKey("TypeLib") ) {
Guid libid = Marshal.GetTypeLibGuidForAssembly(t.Assembly);
subkey.SetValue("", libid.ToString("B"));
}
using( RegistryKey subkey = key.CreateSubKey("Version") ) {
Version ver = t.Assembly.GetName().Version;
string version =
string.Format("{0}.{1}",
ver.Major,
ver.Minor);
if( version == "0.0" ) version = "1.0";
subkey.SetValue("", version);
}
}
}
[ComUnregisterFunction]
static void ComUnregister(Type t) {
// Delete entire CLSID{clsid} subtree
string keyName = @"CLSID\" + t.GUID.ToString("B");
Registry.ClassesRoot.DeleteSubKeyTree(keyName);
}
}
}
* Code taken from [1].
The crazy arcane constant “131457″ in the code above mimics the value used when you create and register COM controls with VB6.
Note that you need to generate your own unique GUID for your control (and use it to attribute your class with the Guid attribute). Here are some quick ways to get a GUID. Use the ProgId attribute to name the control.
The code above adds a few more keys to the appropriate place in the registry. To see what is being made, you can fire up regedit and find the control’s keys here:
HKEY_CLASSES_ROOT\CLSID\{GUID}
Within this branch, you get keys like “ProgId” (name of your control), “CodeBase” (location of your code), RuntimeVersion, and “Assembly” (assembly signature).
The easiest way to test whether your control is registered properly and ready to be inserted into COM containers is to use the tool “tstcon32.exe” - a tool for inserting ActiveX controls onto a container. It comes with Visual Studio… on my machine it’s located here: c:\Program files\Microsoft Visual Studio 8\Common7\Tools\tstcon32.exe
When you run this tool, you can right click on the design surface and select “Insert control.” In here you can see all of the registered ActiveX controls that can be hosted. Yours should be in that list, under the ProgId of your control (e.g. “UserControl1.UserControl”). If your control is in the list and gets inserted successfully, this means you’re ready to embed it into a Word document.

Here is a snippet of code to add it to the document’s shapes collection. “this.Application” refers to the Microsoft.Office.Interop.Word.Application instance.
object classType="UserControl1.UserControl";
object left=100;
object top=100;
object width=10;
object height=10;
object start=0;
object end=0;
object range = this.Application.ActiveDocument.Range(ref start, ref end);
Word.Shape s = this.Application.ActiveDocument.
Shapes.AddOLEControl(ref classType, ref left, ref top,
ref width, ref height, ref range);
And with that, your control should show up as a floating shape on the Word document. I haven’t yet noticed any weird event anomalies; all events seem to fire normally.
If you’ve come this far, surely you are marveling in your own technical magnificence, confident that all of your problems are now solved because of your sublime brilliance. That may be true. However… there is one huge caveat that may dash your hopes and dreams against the rocks of harsh interop reality — it appears these controls can not be hosted as an InlineShape on the word document; only as a floating Shape. InlineShapes exist in the text layer and are treated as characters; they flow with the text. Shapes are hosted on the drawing layer, do not flow with the text, and are anchored either to a pixel (point) position or to an entire paragraph. This may or may not be a huge problem for you.
You can try and insert the control into the InlineShapes collection like so:
object classType = "UserControl1.UserControl";
object start = 0;
object end = 0;
object range = this.Application.ActiveDocument.Range(ref start, ref end);
Word.InlineShape s = Application.ActiveDocument.InlineShapes
.AddOLEControl(ref classType, ref range);
If you try to do this, your document will contain a label with the message “Error! Not a valid embedded object.” instead of your Windows Forms control. The call also causes a COMException with an error code of 80004005. This will also occur if you try to insert the control into a Shape first, and then call Shape.ConvertToInlineShape().
This message comes up elsewhere with Office applications and in Internet Explorer, and it might be related to security settings in general or specifically with the assembly containing the control. Unfortunately, after many long hours, I was unable to get around this problem; I could only insert the Windows Forms control as a floating Shape.
Also note that I was only able to insert the control into word when my addin was a COM Addin, and not with the VSTO code-behind document/template projects. This may have been a weird registration problem on my system; if anyone can confirm that this method works in both situations, let me know (although VSTO gives you direct means to insert Windows Forms controls, so I’m not sure why one would use this in a VSTO project).