C# Office Com 开发 创建任务窗格 CustomTaskPane
在VSTO、ExcelDna的开发方式中,提供了非常容易的实现方法,而在Com开发下则必须自己实现,具体的方法就在下面的博主抄来的内容里。
编译dll的时候注意选择32/64位
以下内容除空格外都是从 DAVID E. CRAIG 这哥们儿的博客抄来的:
CustomTaskPanes in a .NET Shared COM Add-in
I was working with a customer that had a requirement to create a dynamic control for a CustomTaskPane in Excel and Word. In VSTO, this is very easy as the reference object found in ThisAddIn has a CustomTaskPanes collection. From there you can .Add() a CustomTaskPane and pass it a UserForm Control you build at runtime. This is very powerful in that you can create very dynamic TaskPanes.
With a COM Shared Add-in, however, this is not as easy. The main reason is that you have to directly implement the TaskPane interface Office.ICustomTaskPaneConsumer an it does not give you the nifty CustomTaskPanes collection. What it does give you is an ICTPFactory object which exposes a method called CreateCTP(). However, this method requires a registered ActiveX control CLSID name for the TaskPane control you want to appear in the pane. It will not accept a dynamically created User control. How this works is detailed here:
ICustomTaskPaneConsumer.CTPFactoryAvailable Method (Office)
http://msdn.microsoft.com/en-us/library/office/ff863874.aspx
And this article details the full methodology:
Creating Custom Task Panes in the 2007 Office System
http://msdn.microsoft.com/en-us/library/office/aa338197%28v=office.12%29.aspx
A fellow employee (http://blogs.msdn.com/b/cristib/) that also does a lot of blogging on VSTO, pointed me to a possible solution:
- Use the method detailed in the second article
- But create a VSTO UserForm Control and expose it as a COM Control
- However, he suggested adding all the controls I might possibly need to the control form and show/hide them as needed. E.g. making it pseudo dynamic. But my customer needed a fully dynamic pane…
So, I took it two steps further:
- First, I actually simplified the control idea greatly. My exposed COM control was a basic, simple, empty control. My design was to add the control to the TaskPane and then get a reference to the base UserForm control. From there I can call an exposed property (ChildControls) and then add anything I want to it. I can attach a button and then hook to it’s click event, etc. I can build a control dynamically at runtime, or build it as a separate project at design time.
- Next, I recreated the CustomTaskPanes collection and CustomTaskPane objects in mirror classes so working with CustomTaskPanes in a Shared Add-in was as simple as in a VSTO add-in.
First, lets look at the base control I created. You will see that I exposed it as COM Visible. After I built the project, I used REGASM to register it:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe /codebase <path>\CustomTaskPaneControl.BaseControl.dll
Here is the code:
[ComVisible(true)] [ProgId("CustomTaskPaneControl.BaseControl")] [Guid("DD38ADAB-F63A-4F4A-AC1A-343B385DA2AF")] public partial class BaseControl : UserControl { public BaseControl() { InitializeComponent(); } [ComVisible(true)] public ControlCollection ChildControls { get { return this.Controls; } } }
That is it – that is all there is. It is a simple placeholder, empty shell, ready to be filled with anything you add the the ChildControls property.
Next, I created two new classes zCustomTaskPanesCollection and zCustomTaskPanes. I placed these in their own source code file and added it to the project Namespace. I also added the project reference to the base control above so I could directly cast it.
Here is the code:
/// <summary> /// This class mirrors the Office.CustomTaskPaneCollection /// </summary> public class zCustomTaskPaneCollection { // Public list of TaskPane items public List<zCustomTaskPane> Items = new List<zCustomTaskPane>(); private Office.ICTPFactory _paneFactory; /// <summary> /// CTOR - takes the factor from the interface method /// </summary> /// <param name="CTPFactoryInst"></param> public zCustomTaskPaneCollection(Office.ICTPFactory CTPFactoryInst) { _paneFactory = CTPFactoryInst; } /// <summary> /// Adds a new TaskPane to the collection and takes a /// User Form Control reference for the contents of the /// Control Pane /// </summary> /// <param name="control"></param> /// <param name="Title"></param> /// <returns></returns> public zCustomTaskPane Add(Control control, string Title) { // create a new Pane object zCustomTaskPane newPane = new zCustomTaskPane(control, Title, _paneFactory); Items.Add(newPane); // add it to the collection return newPane; // return a reference } /// <summary> /// Remove the specific pane from the list /// </summary> /// <param name="pane"></param> public void Remove(zCustomTaskPane pane) { Items.Remove(pane); pane.Dispose(); // dispose the pane } /// <summary> /// Get a list /// </summary> public int Count { get { return Items.Count; } } } /// <summary> /// This class mirrors the Office.CustomTaskPane class /// </summary> public class zCustomTaskPane { private string _title = string.Empty; private Control _control = null; private Office.CustomTaskPane _taskPane; private BaseControl _base; public string Title { get { return _title; } } public event EventHandler VisibleChanged; /// <summary> /// Get or set the dock position of the TaskPane /// </summary> public Office.MsoCTPDockPosition DockPosition { get { return _taskPane.DockPosition; } set { _taskPane.DockPosition = value; } } /// <summary> /// Show or hide the CustomTaskPane /// </summary> public bool Visible { get { return _taskPane.Visible; } set { _taskPane.Visible = value; } } /// <summary> /// Reference to the control /// </summary> public Control Control { get { return _control; } } /// <summary> /// CTOR /// </summary> /// <param name="control"></param> /// <param name="Title"></param> /// <param name="CTPFactoryInst"></param> public zCustomTaskPane(Control control, string Title, Office.ICTPFactory CTPFactoryInst) { // create the taskpane control and use the factory to create a new instance of // our base control "CustomTaskPaneControl.BaseControl" _control = control; _title = Title; _taskPane = CTPFactoryInst.CreateCTP("CustomTaskPaneControl.BaseControl", Title); _taskPane.Width = control.Width + 2; _base = (BaseControl)_taskPane.ContentControl; _base.ChildControls.Add(control); _base.Height = control.Height + 2; // when the visibility changes fire an event _taskPane.VisibleStateChange += (Office.CustomTaskPane CustomTaskPaneInst) => { VisibleChanged(this, new EventArgs()); }; } /// <summary> /// Dispose of the control and collect /// </summary> public void Dispose() { try { _taskPane.Visible = false; } catch { } try { _control.Dispose(); _control = null; _base.Dispose(); _base = null; } catch { } GC.Collect(); GC.Collect(); GC.WaitForPendingFinalizers(); } }
Finally, I added hooked up my Shared Add-in to use the ICustomTaskPaneConsumer interface. At that point, everything was hooked up ready to go:
public class Connect : Object, Extensibility.IDTExtensibility2, Office.ICustomTaskPaneConsumer, Office.IRibbonExtensibility { public zCustomTaskPaneCollection CustomTaskPaneCollection; void Office.ICustomTaskPaneConsumer.CTPFactoryAvailable(Office.ICTPFactory CTPFactoryInst) { CustomTaskPaneCollection = new zCustomTaskPaneCollection(CTPFactoryInst); }
Now, I was able to build a fully dynamic control on a Ribbon Button click, like this:
// create a dynamic control on the fly UserControl dynamicControl = new UserControl(); // add a button to it Button btnTest = new Button(); btnTest.Name = "btnTest"; btnTest.Text = "Hello"; // when the user clicks the button on the TaskPane, // say hello... btnTest.Click +=(object sender, EventArgs e) => { MessageBox.Show("Hello World!"); }; // add the button to the control dynamicControl.Controls.Add(btnTest); // now create the taskPane - looks exactly the same // as how you would create one in VSTO zCustomTaskPane myPane = CustomTaskPaneCollection.Add(dynamicControl, "My Pane"); myPane.VisibleChanged += (object sender, EventArgs e) => { // when the taskpane is hidden, invalidate the ribbon // so my toggle button can toggle off ribbon.Invalidate(); }; // dock to the right myPane.DockPosition = Office.MsoCTPDockPosition.msoCTPDockPositionRight; // show it myPane.Visible = true;
Once fully implemented this looks and acts just like the CustomTaskPaneCollection from VSTO and is just as easy to use and allows you to create dynamic, on the fly controls.
原博客URL:https://theofficecontext.com/2013/05/21/customtaskpanes-in-a-net-shared-com-add-in/