📜 ⬆️ ⬇️

VSTO and CAB: Integrating .NET applications in Microsoft Word

VSTO stands for Visual Studio Tools for Office. These tools make it quite easy to cross already with a hedgehog — write .NET applications executed by the CLR in the Microsoft Office environment. In particular, programmers have the ability to create plug-ins (plug-ins) and "customized" document templates for almost the entire main family of Microsoft Office products.

The article presents the Windows Forms infrastructure of the project, in which Microsoft Word is perceived by the application as a shell. The article reveals several interesting points of using the Composite UI Application Block, in particular, connecting the infrastructure of the Word domain model to the framework expansion services, as well as some facts and features of development using VSTO tools.

Task


Suppose we have several dozen people who write documents and constantly work with their editors. For some reason, Sharepoint and other portals are not suitable, therefore the implementation of its own business logic is required. Let's complicate the task - people work in an area that is very far from computer subjects and only know Microsoft Office. Even more complicated - the office is poor, the Microsoft Office of the 2003 version of most people. In some places, the big bosses and the main boss are worth 2007th. The car park is heterogeneous - from the 2000th Windows to Windows Vista.

Decision

One solution is a plug-in for Microsoft Office. You can write it on COM, through the bare Office Interop, or through the same interop, but wrapped in VSTO tools in a clever way. These funds and use. The user will run Word, the VSTO runtime will hook the .NET assemblies and somehow complement the Microsoft Word controls, allowing the user to execute the business logic necessary for the task.
')

And why all this?

(This is someone who can not wait to find out how it will end). At the exit, I will lay out a Visual Studio solution that will allow you to create your application connected to Microsoft Word in half-ping. Along the way, I will explain almost everything that is used in this solution.

What is required for work

  1. Microsoft Word 2003 SP3
  2. VSTO 2005 SE
  3. VSTO 2005 SE Runtime
  4. CAB
  5. Visual Studio 2005 or 2008

Why CAB?

In order to determine the explicit separation of the functionality associated with Microsoft Word from simple Windows Forms logic. Simply put, our plugin will be a CAB module, and if you wish, we can easily connect this logic to other CAB applications. Simply put, using CAB minimizes the amount of glue code.

There are two more reasons why I included the CAB in the solution. The first is that I have already given a description of this framework here , and now I want to give a full-fledged working example. The second reason is more banal - I like CAB, it makes the world and things in it easier :)

More on VSTO

Generally speaking, VSTO not only Word plugins can create. This is a powerful system, now it is already in the third version. VSTO is the gateway for a developer to the world of automation and programming of Office-based solutions. In a nutshell, a .NET developer gains access to the domain model of an Office application and does what it wants with it.

All these solutions can be divided into two types:
  1. Document Level Customization
  2. Application Level Customization

These are independent levels. The first creates document templates for Microsoft Office applications. You can take an Excel template, diversify it with different user controls (buttons, grids), and add any logic — for example, so that the user-generated data is sent to a server through a web service and processed there. At this level, the developer has access to the Action Pane (in the 2003 version), where you can connect your controls, plus a lot more in the 2007 version.

Application Level Customization means that an extension module (so-called add-in) is connected to the Microsoft Office application environment. Actually, I initially started talking about him. This plug-in is a locomotive of the business application and allows you to connect all the capabilities of the .NET platform to Microsoft Office. I will show exactly the solution of the second level.

If anyone is interested in the possibilities of VSTO - there is a lot about them in msdn .

Pseudo task

Let's create a small example illustrating the above. Suppose there is a requirement that a user can select text from a Microsoft Word document and receive this selected text in the window of a user element (I know, a foolish example, but I don’t want to do a more complicated one, but it is difficult to invent a less complex one).

In details. User launches Microsoft Word. After starting among the toolbars, the user will have access to the TP - extension toolbar (i.e., our extension). It has a button, by pressing which the user is shown a window with the button “Get text” and a multiline textbox control element. The user clicks on the button "Get text", the selected text in the document is copied to the textbox. Everyone is happy.

It is clear that the example is not suitable for life. But the infrastructure being created will make it quite easy to build muscles on it, if, of course, it is necessary for someone.

Creating a solution

After installing VSTO in the studio when creating a new solution, Office projects will be available:



Be careful with the name - it will be at the same time the name of the root namespace and you can change it only with pens through the unloading of the project.

The output is a solution with two projects:


WordCAB is actually an add-in. The second - no less important, is the deployment project for the expansion module. Why is he important?

The fact is that installing an expansion module on users' workstations is a very laborious undertaking. It is not only to just copy the library in the desired folder. For successful module operation, it is required to register a bunch of keys in the registry. If you dig deeper, it turns out that the extension module is connected to the Microsoft Office as a COM library, with all the consequences. To whom it is interesting, you can have a look at all the necessary registry keys in the deployment project (Right-click, View-> Registry).

The ThisAddIn class is the access point to the module:
public partial class ThisAddIn
{
private void ThisAddIn_Startup( object sender, System. EventArgs e)
{
new AddInApplication().Run();
}

private void ThisAddIn_Shutdown( object sender, System. EventArgs e)
{
}

#region VSTO generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this .Startup += new System.EventHandler(ThisAddIn_Startup);
this .Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}

#endregion
}


* This source code was highlighted with Source Code Highlighter .


In ThisAddIn_Startup we will write code. To be more precise, we will launch a CAB application.

What is a CAB application?

This is a class like that. It has a launch point — the Run() method, and some framework infrastructure — in particular, have access to the main use case “Application” (ie, the main, root WorkItem) and the ability to override system services added to default This class has already been implemented in the base view, and the programmer needs to parameterize it - specify the type of root precedent and the type of the main form:

internal sealed class AddInApplication : WindowsFormsApplication<AddInWorkItem, object >
{
protected override void Start()
{
}
}


* This source code was highlighted with Source Code Highlighter .


The type of the main form (shell form) is object. Why? Because the application runs in Microsoft Word, so there is no need to create the main form. The Microsoft Word form is the main form, but without its traditional capabilities.

In the course of the play, it occurred to me to clarify a bit the model of the application for the extension module in Microsoft Word.

Word Add-in Application Model
So, the user works with documents. Moreover, per unit of time, it can work with only one document. Therefore, the extension module has a clear context - the current open document. (By the way, in the Microsoft Word VSTO model this document is designated as Globals.ThisAddIn.Application.ActiveDocument ). From here (I made) the conclusion (or simplification) - it makes sense to show custom business elements in a modal mode, because otherwise the context is broken. Controls that are open to one document should only exist while this document is active.

Example - the user opened the document and opened his card (not in modal mode). Switched to another document, and the card remained for the previous one - a violation of the context and integrity of perception. Of course, such behavior can be controlled (and even necessary, in some cases), but the modality of controls and their rigid binding to the current active document makes life easier.

Modal workspace

User controls in the CAB framework are displayed in special so-called workspaces (Workspace). According to the behavior described above, we need to show controls in a modal mode. Since WindowWorkspace's native CAB window was turned out to be dubious and looked ugly, I wrote my own simple one - I will not give the code, because its a couple of screens. You can enjoy creativity by downloading the solution with the code (link to the archive is given at the end of the article).

Registration of a modal working area occurs in the InitializeServices() method of the main use case - AddInWorkitem :

Workspaces.Add(( new SimpleModalWorkspace() ), WorkspaceNames.MainWorkspace);

* This source code was highlighted with Source Code Highlighter .


Total: under the key ID WorkspaceNames.MainWorkspace registered a modal work area. It can now be accessed from anywhere, where there is a link to the main use case: IWorkspace interface IWorkspace from the Workspaces collection using the above key. It's just like an orange! (c) c / f "Terminator 2"

Microsoft Word Controls Factory

It's about the Word toolbar and the buttons on it. In VSTO, these are wrappers over COM objects — the CommandBar and CommandBarButton from the Microsoft.Office.Core namespace. Terribly buggy things, to be honest, especially their animation. It was possible to understand all the subtleties and details only after shock trolling on the VSTO forums.

What is the idea - the idea is to save the programmer and the developers of business modules from the need to work with COM wrappers. For this, we (that is, I) integrate the Misrosoft Word model into the mechanism of the so-called expansion sites (UiExtensionSite) of the CAB framework.

The mechanism of expansion sites consists of functional bundles-additions. Simply put, in the code you need to visualize the relationship of subordinate elements. In our case, these relationships are:
  1. Into an array of Microsoft Word toolbars, a toolbar is inserted.
  2. A button is inserted into the toolbar button collection.
  3. Nothing is inserted into the button (we do not consider the case of combo boxes, although they are present), so this is a terminal object

So we got three subordinate entities:
  1. IWordCommandBarContainer - Microsoft Word IWordCommandBarContainer Container
  2. IWordCommandBar - a container of buttons on the toolbar
  3. IWordButton - button


By the way, in addition to the definition of Word controls, specific types of interfaces IWordCommandBar and IWordButton are adapters to the above-mentioned CommandBar and CommandBarButton respectively.

In order for the framework to understand what, where and, most importantly, how to insert, it needs to register the adapter factory for user controls. In our case, you can insert toolbars (into the toolbar collection) and buttons (into the button collection on the toolbar). Therefore, registering the adapter factory is necessary for the IWordCommandBarContainer and for the IWordCommandBar . Then, when the user receives the expansion location, the framework will look for instances of the classes for adding subordinate elements for this expansion location, and then using them for their intended purpose — to add elements. Well, in order not to bother, these instances are generated by the factory:

public class CommandBarUIAdapterFactory : IUIElementAdapterFactory
{
public IUIElementAdapter GetAdapter( object uiElement)
{
if ( uiElement is IWordCommandBarContainer ) //
return new CommandBarUIAdapter(( IWordCommandBarContainer )uiElement);
if ( uiElement is IWordCommandBar ) //
return new CommandBarButtonUIAdapter(( IWordCommandBar )uiElement);

throw new ArgumentException( "uiElement" );
}

public bool Supports( object uiElement)
{
return ( uiElement is IWordCommandBarContainer ) || ( uiElement is IWordCommandBar );
}
}


* This source code was highlighted with Source Code Highlighter .


But the implementation of the collection of toolbars (with the ability to add a new one!)
internal class BarCollection : IWordCommandBarContainer
{
#region IWordCommandBarContainer Members

public void AddBar(IWordCommandBar bar)
{
CommandBar commandBar = null ;
//todo: ,
try
{
commandBar = Globals.ThisAddIn.Application.CommandBars[bar.Id];
}
catch ( ArgumentException ) { }

if ( commandBar == null )
commandBar = Globals.ThisAddIn.Application.CommandBars.Add(bar.Id, ( object )MsoBarPosition.msoBarTop, _null, true );

commandBar.Visible = true ;
}

private object _null = System.Reflection.Missing.Value;

#endregion
}


* This source code was highlighted with Source Code Highlighter .

The buttons on the toolbars are similar. Look at the code at your leisure.

It was necessary, by the way, to create a factory of buttons and toolbars themselves (see IWordUIElementFactory ). The fact is that the CAB modules work with the IWordCommandBar and IWordButton . The specific types of these interfaces are in the VSTO Word-AddIn assembly and are tied to Microsoft.Office.Core . Therefore, in the modules (where there is no link to Office) there was a possibility to receive instances of the specified elements, a factory is created. It is registered in the main WorkItem.

Registration of factories and places of expansion for toolbars:
Services.AddNew<WordUIElementFactory, IWordUIElementFactory>();
IUIElementAdapterFactoryCatalog factoryService = base .Services.Get<IUIElementAdapterFactoryCatalog>();
factoryService.RegisterFactory( new CommandBarUIAdapterFactory());

UIExtensionSites.RegisterSite(UIExtensionSiteNames.WordBarsSite, new BarCollection());


* This source code was highlighted with Source Code Highlighter .


If you are confused, I'm sorry :) I myself understand that here without half a liter you will not understand. (If you take care of it, then much becomes clear.)

CAB module

This is a regular build. It should have a ModuleInit class. In this class there is a link to the main use case AddInWorkitem and, as a result, there is access to all the good about which I wrote above.

The task of the CAB module is to load to the main precedent, insert a toolbar, insert a button on it, describe the button handler:

Voila:
UIExtensionSite site = _rootWorkItem.UIExtensionSites[UIExtensionSiteNames.WordBarsSite];
IWordCommandBar mainBar = site.Add<IWordCommandBar>(_factory.CreateBar( "AddInToolbar" ));

IWordButton btn = _factory.CreateButton(mainBar, CommandNames.OpenForm, ToolStripItemDisplayStyle.ImageAndText, " " ,
" Custom Control" , Resources.OpenForm, false );

mainBar.AddButton(btn);
btn.Click += new EventHandler<WordButtonClickArgs>(ButtonClick);


* This source code was highlighted with Source Code Highlighter .


The button handler will create a custom window with the “get text” button and a text field, and then show it (using the special display modifier WindowSmartPartInfo ) in the previously mentioned work area (which, as we remember, I registered in the case under the WorkspaceNames.MainWorkspace key):
private void ButtonClick( object sender, WordButtonClickArgs e)
{
object smartPart = _rootWorkItem.SmartParts.AddNew<SampleSmartPart>();
WindowSmartPartInfo info = new WindowSmartPartInfo();
info.FormStartPosition = FormStartPosition.CenterScreen;
info.MaximizeBox = false ;
info.MinimizeBox = false ;
info.Resizable = false ;
info.Title = "Custom Control" ;
info.ShowInTaskbar = false ;
_rootWorkItem.Workspaces[WorkspaceNames.MainWorkspace].Show(smartPart, info);
}


* This source code was highlighted with Source Code Highlighter .


Module loading

Here I made a feint knee. Usually, modules are loaded into the framework through a special declarative loading format - the so-called ProfileCatalog. Usually, this is a good way to connect everything you need. But, given the harsh Soviet realities, there is a non-zero probability that the programmer will need a non-declarative module connection logic. To do this, we will override the special service of listing loadable modules - IModuleEnumerator . I made it very simple - it looks into the folder of the executable assembly and searches in it for a module called CustomModule.dll. Finds and loads. Well, or does not load, if it does not find:
public class CustomModuleEnumerator : IModuleEnumerator
{
#region Constants
private const string ModuleName = "CustomModule.dll" ;
#endregion

#region IModuleEnumerator Members

public IModuleInfo[] EnumerateModules()
{
List <IModuleInfo> result = new List <IModuleInfo>();

string path = GetModulePath(ModuleName);
if ( File .Exists(path) )
result.Add( new ModuleInfo(ModuleName));
return result.ToArray();
}

#endregion

#region Private Methods
private string GetModulePath( string assemblyFile)
{
if ( !Path.IsPathRooted(assemblyFile) )
assemblyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, assemblyFile);

return assemblyFile;
}
#endregion
}


* This source code was highlighted with Source Code Highlighter .


Now this service is very simple, but if you wish, of course, you can pile up even a bald feature in it. For example, load modules from the database :)

You need to overload this service in the CAB application class:
protected override void AddServices()
{
base .AddServices();

RootWorkItem.Services.Remove<IModuleEnumerator>();
RootWorkItem.Services.AddOnDemand<CustomModuleEnumerator, IModuleEnumerator>();
}


* This source code was highlighted with Source Code Highlighter .


Doctor, I'm tired. What happened?

It turned out that was conceived in the original pseudo-example:

In the Microsoft toolbars, there is our toolbar, a button with an icon hangs on it, the user control comes out of the button click event, and you can push the text selected in the document into the text field of the button :) Trivial, but notice how insignificant the connectivity between objects is! I would like to note that the frame, with proper treatment, has ideal code maintainability.

Attention! Underwater rocks!


Security
When you compile and run the example, nothing will start. Moreover, a message will fall out, which will tell you - they say, there are not enough rights to launch a third-party module (in our case, CustomModule.dll). The problem is that by default the VSTO application (in development mode!) Gives Full Trust the right only to the executable assembly and to all the assemblies on which it depends - i.e. on WordCAB.dll. In order to allow the use of code of third-party libraries, perform the following action:
C:\Windows\Microsoft.NET\Framework\v2.0.50727>caspol -u -ag All_Code -url "D:\Projects\WordCAB\bin\Debug\*" FullTrust
Microsoft (R) .NET Framework CasPol 2.0.50727.3053
Copyright (c) Microsoft Corporation. All rights reserved.

The operation you are performing will alter security policy.
Are you sure you want to perform this operation? (yes/no)
yes
Added union code group with "-url" membership condition to the User level.
Success


Instead of "D:\Projects\WordCAB\bin\Debug\*" you need to specify the folder from which the Add-in is launched. Do not forget the star.

Add-in stopped loading!
Sometimes, when a raw Exception occurs in a module, Word blocks the execution of this module on the next boot. Go to Help-> About-> Disabled Items and see if your extension is in the list. If there is, remove it from there. At the next run-debug it will appear.

How to remove
Removing an extension is not so trivial. Pull out the COM-AddIns button on the Microsoft Word toolbar. To do this, go to Tools-> Customize-> Tools -> (Drag'n'Drop) COM-AddIns. Click on it and uncheck your extension. He will unload. To reload, on the contrary, set the checkbox back. Another way is to go to the control panel and remove from the programs.

And what about Word 2007?
The add-in is remarkably loaded into the Ribbon on the last tab.

There's still a bunch of small pitfalls. But I have already written so much here that I am not sure that anyone will finish reading to the end :)

Where to get the code

github.com/head-thrash/VSTO-CAB

findings

In general, you can take VSTO, Microsoft Office 2003, .NET Framework 2.0 and make a decision. I have resulted in the basic decision on which it is possible to increase functionality in this article. Who wants - use on health. I would be happy to answer questions and correct inaccuracies, if there are such. Thank you very much for your attention!

Source: https://habr.com/ru/post/54880/


All Articles