[Office開發系列] Office 的自訂工作台 (Custom Task Pane)

[Office開發系列] Office 的自訂工作台 (Custom Task Pane)

自訂工作台是 Office 2003 開始加入的功能,當時被稱為智慧文件 (smart document),這個名詞是繼承自在 Office XP 中所加入的智慧標籤 (smart tags),它是在 Office 應用程式中開放一個介面 (ISmartDocument),由開發人員自行實作相對應的方法,告訴 Office 應用程式在工作台中要放哪些控制項,以及它相對應的處理常式,而在 Office 物件模型中也包裝了一個 SmartDocument 屬性,讓文件可以和自訂工作台溝通。

The Smart Document sample

 

雖然這個點子很棒,因為可以由文件的區段 (document segements) 來動態決定工作台中要顯示的使用者介面為何,例如在訂單主檔中可以查詢主檔的資料,而在明細表格中顯示查詢產品的介面,讓訂單管理人員可以將資料加到文件中,這個技術基本上是基於 Smart Tags 的文件區段感應的技術,在不同的文件區段中可以顯示不同的 Smart Tags 功能表,但它的開發複雜度卻是相當的大,開發人員必須要在文件中放 XML schema,定義部署工作台的 XML manifest,以及依 ISmartDocument 介面開放的方法來實作以供應 Office 應用程式必要的資料,它才可以正常運作,而當時 Visual Studio Tools for Office 2003 暫時無法支援工作台的開發。

但到了 Visual Studio 2005 Tools for Office 開始,開發工作台的工作變得很簡單,因為開發人員可以利用自訂 Windows Forms 使用者控制項的方式來設計介面,這對於長久以來只能用原始方式開發的流程來說真是個天堂,而且它還支援 .NET Framework (C#, VB.NET) 的開發,同時也不再受限於 XML schema,Manifest 也由 VSTO 自行控制,開發人員不再需要為了 XML Expansion Pack 傷透腦筋。

到了 Office 2007,Ribbon 介面出現後,這個原因被認為 “智慧”的介面被改名為自訂工作台 (custom task pane) 而退到第二線,但它仍然是個好用的介面開發方式,配合不同的應用程式以及 add-ins,開發人員仍可以利用自訂工作台來開發適合的使用者介面,尤其是需要加入資料的 Office Business Application (OBA) 解決方案。本例的自訂工作台可以將檔案系統中的資料夾清單加到 Word 文件中 (若是 Excel 則是加到工作表中)。

image

 

若要開發具有自訂工作台的應用程式,必須要使用 Office 應用程式的增益集 (add-ins) 專案,增益集專案才有 CustomTaskPanes 屬性,這個屬性才會允許開發人員在 Office 應用程式中加入自訂工作台的設定,而自訂工作台都是 Windows Forms 的使用者控制項,在本例中,我們使用 Visual Studio 2010 的 Word 2010 Add-in 專案範本,並且在專案中加入一個 CustomTaskPaneView 的 Windows Forms 使用者控制項:

image

 

然後在控制項中使用下列程式碼:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Office.Interop.Word;

namespace WordCustomTaskPaneDemo
{
    public partial class CustomTaskPanelView : UserControl
    {
        public CustomTaskPanelView()
        {
            InitializeComponent();
        }

        private void CustomTaskPanelView_Load(object sender, EventArgs e)
        {
            DriveInfo[] drives = DriveInfo.GetDrives();

            this.cboDriveList.DisplayMember = "Name";

            foreach (DriveInfo drive in drives)
                this.cboDriveList.Items.Add(drive);
        }

        private void cmdEnumerateDirectories_Click(object sender, EventArgs e)
        {
            this.tvDirList.Nodes.Clear();

            // load directories.
            TreeNode node = new TreeNode();
            DirectoryInfo directoryInfo = (this.cboDriveList.SelectedItem as DriveInfo).RootDirectory;
            node.Text = this.cboDriveList.SelectedItem.ToString().Split('(')[0].Trim();
            node.Tag = directoryInfo;

            DirectoryInfo[] childDirectories = directoryInfo.GetDirectories();

            if (childDirectories.Length > 0)
            {
                try
                {
                    foreach (DirectoryInfo childDirectory in childDirectories)
                        this.LoadDirectories(node, childDirectory);
                }
                catch (Exception)
                {
                }
            }

            this.tvDirList.Nodes.Add(node);
        }
        private void LoadDirectories(TreeNode ParentNode, DirectoryInfo Directory)
        {
            DirectoryInfo directoryInfo = Directory;
            TreeNode node = new TreeNode(directoryInfo.Name);

            node.Tag = directoryInfo;

            DirectoryInfo[] childDirectories = directoryInfo.GetDirectories();

            if (childDirectories.Length > 0)
            {
                try
                {
                    foreach (DirectoryInfo childDirectory in childDirectories)
                        this.LoadDirectories(node, childDirectory);
                }
                catch (Exception)
                {
                }
            }

            ParentNode.Nodes.Add(node);
        }

        private void cmdAddList_Click(object sender, EventArgs e)
        {
            Range contentRange = Globals.ThisAddIn.Application.ActiveDocument.Content;

            TreeNodeCollection nodes = this.tvDirList.SelectedNode.Nodes;

            Table table = contentRange.Tables.Add(contentRange, nodes.Count + 1, 3);
            table.Cell(1, 1).Range.Text = "Directory Name";
            table.Cell(1, 2).Range.Text = "Path";
            table.Cell(1, 3).Range.Text = "File Count";

            int rowIndex = 2;

            foreach (TreeNode node in nodes)
            {
                table.Cell(rowIndex, 1).Range.Text = (node.Tag as DirectoryInfo).Name;
                table.Cell(rowIndex, 2).Range.Text = (node.Tag as DirectoryInfo).FullName;
                table.Cell(rowIndex, 3).Range.Text = (node.Tag as DirectoryInfo).GetFiles().Length.ToString("###,###,##0");

                rowIndex++;
            }
        }
    }
}

 

接著在 Add-in 的 Startup 事件中加入下列程式碼:

public partial class ThisAddIn
{
    private CustomTaskPanelView _customTaskPane = null;

    private void ThisAddIn_Startup(object sender, System.EventArgs e)
    {
        this._customTaskPane = new CustomTaskPanelView();

        this.CustomTaskPanes.Add(_customTaskPane, "檔案系統資訊");
        this.CustomTaskPanes[0].DockPosition = Office.MsoCTPDockPosition.msoCTPDockPositionRight;
        this.CustomTaskPanes[0].Width = 400;
        this.CustomTaskPanes[0].Visible = true;
    }

    private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
    {
        this._customTaskPane.Dispose();
    }

    ..
}

 

接著按 F5 編譯並執行,你就可以看到自訂的工作台出現在畫面右邊 (使用下拉式方塊選取磁碟機,按列舉資料夾後在資料夾會顯示所有的資料夾樹狀目錄,選擇其中一個按 “加入清單到表格中”,就會將選取目錄的子目錄以及其檔案個數生成表格並加到文件中)。

image 

這個功能目前只能由外部程式 (VB6, VSTO 等) 來開發,VBA 無法開發這樣的功能,它只能和工作台互動,但無法建立工作台。