13
votes

I'm developing a Word 2007-2010 addin using VSTO in Visual Studio 2008. In my addin, I need a custom task pane for each open word document. Basically, I need to create a task pane for each document, show the correct task pane in the document window, do something on document close and then remove the task pane and all references to it.

This is what I have done so far:

Task pane creation

I create a custom task pane for each new, opened or existing on load document like this:

((ApplicationEvents4_Event) Application).NewDocument += CreateTaskPaneWrapper;
Application.DocumentOpen += CreateTaskPaneWrapper;
foreach (Document document in Application.Documents)
{
    CreateTaskPaneWrapper(document);
}

In the CreateTaskPaneWrapper method, I check a Dictionary<Document, TaskPaneWrapper> if a task pane for a document already exists. I do this because the open event fires if I try to open an already open document. If it doesn't exist, I create a new TaskPaneWrapper class. In its constructor, I create a new task pane and add it to the CustomTaskPanes collection with

Globals.ThisAddIn.CustomTaskPanes.Add(taskPane, "Title");

According to MSDN, this associates the task pane with the currently active window.

Task pane shutdown

Both Document.Close and Application.DocumentBeforeClose events don't suit me, because they fire before before the user gives the confirmation to close the document. So I use the Microsoft.Office.Tools.Word.Document.Shutdown event in my TaskPaneWrapper class like this:

_vstoDocument = document.GetVstoObject();
_vstoDocument.Shutdown += OnShutdown;

private void OnShutdown(object sender, EventArgs eventArgs)
{
    Globals.ThisAddIn.CustomTaskPanes.Remove(_taskPane);
    //additional shutdown logic
}

All of this seems to work pretty well, the task panes are created, bound to the corresponding windows, and are successfully removed. However, I still have one problem - when I start Word, a blank document opens. If I then open an existing document without changing the blank one, the blank document and it's window is deleted without the Document.Close, Application.DocumentBeforeClose and Microsoft.Office.Tools.Word.Document.Shutdown events firing. Because OnShutdown is not called and the task pane for the blank document is not deleted, the next document window contains TWO task panes - the very new one, and the very first (orphaned) one. How can I remove this orphaned task pane? Accessing the deleted document or window reference throws a COMException ("Object was deleted"). I'm temporary using this hack:

//A property in my TaskPaneWrapper class
public bool IsWindowAlive()
{
    try
    {
        var window = _vstoDocument.ActiveWindow;
        return true;
    }
    catch (COMException)
    {
        return false;
    }
}

In the CreateTaskPaneWrapper method, I check this property for all existing wrappers and shutdown the ones where the property is false. Catching an exception is somewhat expensive, of course, and this solution is pretty hacky, so I was wondering, is there a better one? In this question CustomTaskPane.Window property is checked for null, but it never returns null for me.

Also, are there any other problems I can run into using my current logic? What's the typical way of managing multiple task panes for multiple documents?

3
+1 for pointing me to the Document.Shutdown Event :-)jreichert

3 Answers

6
votes

This problem is detailed in this MSDN article titled Managing Task Panes in Multiple Word and InfoPath Documents

You have to create a method that removes orphan CTPs (ie those that no longer have windows attached).

I have followed this article and successfully implemented a CustomTaskPane manager that removes the orphans.

6
votes

This answer in MSDN

Instead of reactively cleaning up orphaned task panes after you open an existing document, you can proactively clean up by calling the following method from the DocumentOpen event handler before you call CreateTaskPaneWrapper. This code loops through each of the custom task panes that belong to the add-in. If the task pane has no associated window, the code removes the task pane from the collection.

private void RemoveOrphanedTaskPanes()
{
    for (int i = Globals.ThisAddIn.CustomTaskPanes.Count; i > 0; i--)
    {
        CustomTaskPanes ctp = Globals.ThisAddIn.CustomTaskPanes[i - 1];
        if (ctp.Window == null)
        {
            this.CustomTaskPanes.Remove(ctp);
        }
    }
}

private void Application_DocumentOpen(Word.Document Doc)
{
    RemoveOrphanedTaskPanes();
    CreateTaskPaneWrapper(document);
}
0
votes

Some things to try::

  1. Maybe you can check "Word.Application.Documents.Count" in the DocumentNew- and DocumentOpen-Event to track the actual open document.

  2. To find out if a document is already open (maybe relevant for re-open active document), you can use a dictionary of open documents. The key can the unique document name.

  3. Instead of your IsWindowAlive() method you can maybe catch the Disposed-Event as follows

_vstoDocument.Disposed += _vstoDocument_Disposed;

        void _vstoDocument_Disposed(object sender, EventArgs e)
        {
            //Remove from dictionary of open documents 
        }