This project has moved and is read-only. For the latest updates, please go here.

Managing Task Panes in Multiple Word Documents

Feb 13, 2014 at 7:15 PM
Hello, Had a need to generate a new customtaskpane per word document window. Out of the box netoffice will generate your custom taskpane when the first window opens. A call to CTPFactoryAvailable happens once by default. Subsequent window don't have a taskpane. What I found is that in order to get a new pane to open for new document I had to call CTPFactoryAvailable again. This requires saving a reference to CTPFactoryInst.

There was some confusion as to the difference between TaskPanes and TaskPaneInstances. Feel that TaskPanes should rather be called RegisterTaskPanes. TaskPaneInstances is a list the contentControl of the CustomTaskPane generated by CreateCTP.

What I had issues with was removing of the TaskPaneInstances when a window would close. See this article [http://msdn.microsoft.com/en-us/library/bb264456.aspx](Managing Task Panes in Multiple Word and InfoPath Documents)

Problem was that TaskPaneInstances isn't a list of CustomTaskPane just the control. So there wasn't a way of telling office to remove the pane. Capturing these objects in CTPFactoryAvailable override. Essentially I needed to call CustomTaskPane.Delete() to get the pane to go away:)

Is there a reason that TaskPaneInstances isn't a list of CustomTaskPane. You can reach the necessary control via ContentControl?

Sometimes I get two instances of the TaskPane for a document that is opened twice?

TaskPane and TaskPaneInstances are protected which causes you to pass them around. Could they be friend?

After removing the CustomTaskPane if I don't reassign TaskPanes[?].Pane to a valid CustomTaskPane bad things happen. Currently just finding one and plugging it in. There would be issues if there were multiple panes being created for each window.
private object _CTPFactoryInst;

        private List<_CustomTaskPane> customTaskPanes = new List<_CustomTaskPane>();

        public override void CTPFactoryAvailable(object CTPFactoryInst)
        {
            _CTPFactoryInst = CTPFactoryInst;

            base.CTPFactoryAvailable(CTPFactoryInst);

            foreach (TaskPaneInfo item in TaskPanes)
            {
                customTaskPanes.Add(item.Pane);
            }

        }

        private void RemoveOrphanedTaskPanes()
        {
            for (int i = customTaskPanes.Count(); i > 0; i--)
            {
                var customPane = customTaskPanes[i - 1];
                if (customPane.Window == null)
                {
                    var control = (customPane.ContentControl as NetOffice.WordApi.Tools.ITaskPane);
                    control.OnDisconnection();
                    TaskPaneInstances.Remove(control);
                    customPane.Delete();
                    customTaskPanes.Remove(customPane);
                    
                    //reassign a live customtaskpane to to the Pane property
                    //this will start to fail if there is more than one customtaskpane being created for each window.
                    TaskPanes[0].Pane = customTaskPanes.FirstOrDefault();
                }
            }
        }

    /// <summary>
        /// Called each time a document is opened.  event called before addin exists so only fires after word is loaded.
        /// </summary>
        /// <param name="Doc"></param>
        public void Application_DocumentOpenEvent(NetOffice.WordApi.Document Doc)
        {
            if (Application.ShowWindowsInTaskbar)
            {
                RemoveOrphanedTaskPanes();
               //register a taskpane in TaskPanes if havn't already
                CTPFactoryAvailable(_CTPFactoryInst);
            }

            
        }
Feb 14, 2014 at 2:24 AM
After more testing it seemed like it was mostly working but the custom task pane events were not working. CTPFactoryAvailable is overridden with the guts from the base class and is altered a little. I can see that CTPFactoryAvailable is already different in the newer version of netoffice.

This one you add new task panes to the TaskPanes collection and then call CTPFactoryAvailable.

CTPFactoryAvailable goes through the TaskPanes and loads whatever is not already loaded.

Also the CustomTaskPane is passed as one of the argument to the pane.OnConnection. Had the need to set the visibility of the pane from inside the control.

RemoveOrphanedTaskPanes now just interacts with the TaskPanes.
private object _CTPFactoryInst;

        /// <summary>
        /// ICustomTaskPaneConsumer implementation
        /// </summary>
        /// <param name="CTPFactoryInst">factory proxy from host application</param>
        public override void CTPFactoryAvailable(object CTPFactoryInst)
        {
            _CTPFactoryInst = CTPFactoryInst;

            try
            {
                if (null != CTPFactoryInst)
                {
                    TaskPaneFactory = new NetOffice.OfficeApi.ICTPFactory(Application, CTPFactoryInst);
                    foreach (TaskPaneInfo item in TaskPanes)
                    {
                        if (item.IsLoaded)
                            continue;

                        string title = item.Title;
                        _CustomTaskPane taskPane = TaskPaneFactory.CreateCTP(item.Type.FullName, title);
                        item.Pane = taskPane;
                        item.IsLoaded = true;

                        NetOffice.WordApi.Tools.ITaskPane pane = taskPane.ContentControl as NetOffice.WordApi.Tools.ITaskPane;
                        if (null != pane)
                        {
                            TaskPaneInstances.Add(pane);
                            var argumentArray = new List<object>();

                            argumentArray.Add(item);

                            if (item.Arguments != null)
                                argumentArray.AddRange(item.Arguments);
                            
                            pane.OnConnection(Application, argumentArray.ToArray());
                        }

                        foreach (KeyValuePair<string, object> property in item.ChangedProperties)
                            if (property.Key != "Title")
                                taskPane.GetType().InvokeMember(property.Key, BindingFlags.SetProperty, null, taskPane, new object[] { property.Value });
                    }
                }
            }
            catch (Exception exception)
            {
                NetOffice.DebugConsole.WriteException(exception);
            }
        }

        private void RemoveOrphanedTaskPanes()
        {
            for (int i = TaskPanes.Count(); i > 0; i--)
            {
                var taskPane = TaskPanes[i - 1];

                if ((taskPane.Pane as CustomTaskPane).UnderlyingObject == null)
                    continue;

                if (taskPane.Pane.Window == null)
                {
                    var control = (taskPane.Pane.ContentControl as NetOffice.WordApi.Tools.ITaskPane);
                    control.OnDisconnection();
                    TaskPaneInstances.Remove(control);
                    taskPane.Pane.Delete();
                    taskPane.Pane.Dispose();
                }
            }
        }