If you are creating an Office Add-in using VSTO, it is easy to create task panes. But what if you do not want to involve the VSTO Runtime?
Using VSTO makes creating Add-ins very simple, but that simplicity comes at a cost. ClickOnce is a major pain to deploy to users, VSTO Add-ins have a tendency to mysteriously disable themselves, and they load slow.
I have recently switched to using the Visual Studio Add-in wizard, which created Add-ins using the IDTExtensibility2 interface. It requires you to do a bit more legwork, but the generated solution comes with a pre-configured MSI installer project, just asking to be deployed via Group Policy.
My latest project needs to prompt a user to perform an action when Word documents of a certain type are opened. Sure, I could use a pop-up form, but that is so very un-office-like. Taskpanes are a much more integrated solution, but for some reason the documentation and examples on the internet are pretty bad. And confusing. And WAY over complicated.
While working on this, I almost gave up (deleted and then started over once), but persistence and experimentation paid off – I got it to work, and it was much easier than I expected.
The following assumes you have some experience creating Add-ins using IDTExtensibility2.
Create your add-in project (be sure to change the debug properties to run your desired application – it defaults to Visual Studio) , add a reference to System.Windows.Forms, and add the following using statements:
using System.Windows.Forms; using Microsoft.Office.Core;
First you need to create a user control for your task pane. Microsoft’s example says you have to create a Form and a user control in a separate project, well this is simply not true.
Add a user control to your project. I left the default name of “UserControl1”. Just for fun, we’ll add a button and a label to the user control as well.
Find the modifiers property on the button and change it to public.
We are doing this so we can access the button’s events from the Add-in, and because I am lazy. (The correct way would be to add a public event to your UserControl1 class, and call it from your private button1 event handler)
Next, modify your Connect class to also implement the ICustomTaskPaneConsumer interface.
public class Connect : Object, Extensibility.IDTExtensibility2, ICustomTaskPaneConsumer { // //
Lets also add a couple more class variables, just to keep life simple (and prevent things from going out of scope unexpectedly)
private object applicationObject; private object addInInstance; // New vars for Custom task pane private ICTPFactory myCtpFactory; private CustomTaskPane myPane; private UserControl1 myControl;
Finally, you implement the CTPFactoryAvailable method – this provides you with the ICTPFactory instance that you will use to actually create the task pane. I have also included the example code to create and display our task pane.
public void CTPFactoryAvailable(ICTPFactory CTPFactoryInst) { // Store the CTP Factory for future use. You need this to display // Custom Task Panes myCtpFactory = CTPFactoryInst; // Create the task pane using UserControl1 as the contents // The third parameter, when supplied, is Window object. myPane = myCtpFactory.CreateCTP("MyAddin1.UserControl1", "My Task Pane", Type.Missing); //Set the dock position and show the task pane myPane.DockPosition = Microsoft.Office.Core.MsoCTPDockPosition.msoCTPDockPositionRight; myPane.Visible = true; // CustomTaskPane.ContentControl is a reference to the control object myControl = (UserControl1)myPane.ContentControl; // assuming UserControl1.button1 is public, add a handler to the click event. myControl.button1.Click += new EventHandler(button1_Click); }
And lastly, add an event handler for the click event
void button1_Click(object sender, EventArgs e) { MessageBox.Show("Hello World!"); }
Now run your project, and the task pane should be there to greet you. Clicking the button will then display the hello world messagebox.
In my project, I kept my application logic in a separate class from the Connect class. All you need is a reference to the instance of ICTPFactory supplied by CTPFactoryAvailable to create and hide task panes.
For reference, here is the entire Connect.cs file.
namespace MyAddin1 { using System; using Extensibility; using System.Runtime.InteropServices; using System.Windows.Forms; using Microsoft.Office.Core; /// <summary> /// The object for implementing an Add-in. /// </summary> /// <seealso class='IDTExtensibility2' /> [GuidAttribute("62EFAKE8-FAKE-47EF-82FC-85C5FAKE977E"), ProgId("MyAddin1.Connect")] public class Connect : Object, Extensibility.IDTExtensibility2, ICustomTaskPaneConsumer { public Connect() { } public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom) { applicationObject = application; addInInstance = addInInst; } public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom) { } public void OnAddInsUpdate(ref System.Array custom) { } public void OnStartupComplete(ref System.Array custom) { } public void OnBeginShutdown(ref System.Array custom) { } public void CTPFactoryAvailable(ICTPFactory CTPFactoryInst) { // Store the CTP Factory for future use. You need this to display // Custom Task Panes myCtpFactory = CTPFactoryInst; // Create the task pane using UserControl1 as the contents // The third parameter, when supplied, is Window object. myPane = myCtpFactory.CreateCTP("MyAddin1.UserControl1", "My Task Pane", Type.Missing); //Set the dock position and show the task pane myPane.DockPosition = Microsoft.Office.Core.MsoCTPDockPosition.msoCTPDockPositionRight; myPane.Visible = true; // CustomTaskPane.ContentControl is a reference to the control object myControl = (UserControl1)myPane.ContentControl; // assuming UserControl1.button1 is public, add a handler to the click event. myControl.button1.Click += new EventHandler(button1_Click); } void button1_Click(object sender, EventArgs e) { MessageBox.Show("Hello World!"); } private object applicationObject; private object addInInstance; // New vars for Custom task pane private ICTPFactory myCtpFactory; private CustomTaskPane myPane; private UserControl1 myControl; } }
thank you for the great article. one thing you might want to mention is that the class for the user control needs a guidattribute and progid.
i have certain doubts …. how to get GUID mentioned in above code? either we need to execute GUID.NewGUID() to get it ? This is not clear for me [GuidAttribute(“62E5FF98-E843-47EF-82FC-85C501C9977E”), ProgId(“MyAddin1.Connect”)]. i think we need to create some registry entry also so can u explain that too
dhinesh,
The line you are referencing is created automatically by the Shared Add-In wizard – I really shouldn’t have included the guid. The wizard also handles creating a setup project that handles the component registration.
Hii , Thank you for the nice article. Could you please explain how to do the same thing using VB6. Is it possible to do in VB6 ?
Hi,
Thank you for the great write up. I had one doubt. By user control did you mean CLR user control in Visual Studio.