OneNote是一款很受歡迎的筆記軟件,其分章節的結構特點非常適合記錄課堂筆記,讀書筆記和知識體系,但與Office其他明星產品相比,OneNote的資歷更短,功能也不及Word、Excel強大,還好我們可以通過AddIn來擴展OneNote的功能。
發現國內OneNote插件開發的資料基本沒有,好不容易找到兩篇也是針對2010版的開發,在此附上鏈接:
http://www.malteahrens.com/#/blog/howto-onenote-dev/
現在正式進入開發階段,由於涉及注冊表,所以需要以管理員權限打開VS,不然會無法生成工程的。
第一步:創建工程
首先要完善開發環境,OneNote二次開發不像Word、Excel有現成的VSTO工具,需要創建安裝和部署的工程,已有教程中都是用VS2010自帶的安裝和部署工具來安裝測試,VS2015移除了該功能,需要手動安裝一個部署軟件,這是地址Microsoft Visual Studio 2015 Installer Projects。
接下來創建工程,我們需要創建一個類庫,如圖

工程創建好后設置工程屬性,在程序集信息中勾選使程序集COM可見,

在生成中勾選為COM互操作注冊

第二步:創建Ribbon配置文件
1.添加一個叫ribbon的XML文件

2.將配置文件存入工程資源以便運行時訪問

3.寫ribbon配置文件代碼
此處我們添加一個叫做Custom的Ribbon選項卡
<?xml version="1.0" encoding="utf-8" ?> <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" loadImage="GetImage"> <ribbon> <tabs> <tab id="tabCustom" label="Custom"> <group id="groupHello" label="Hello"> <button id="buttonHello" label="Hello World!" size="large" screentip="Press this for a 'Hello World!' message" onAction="showHello" image="HelloWorld.png" /> </group> </tab> </tabs> </ribbon> </customUI>
如果想在已有選項卡中添加功能,只需要將tab中的值改為idMso="指定Tab頁"即可
第三部:寫功能代碼
1.為工程添加引用,這里主要需要添加三個引用,分別是:Extensibility,office和Microsoft OneNote 15.0 Type Library
2.在Class1中添加如下using
using System.Runtime.InteropServices; using Extensibility; using Microsoft.Office.Core; using OneNote = Microsoft.Office.Interop.OneNote;
3.創建一個新的GUID以標識工程
工具→創建GUID(G)

將創建的GUID粘貼到記事本,之后還要用
4.在Class1上添加標記
[Guid("743A0108-BBE3-4D22-A6A8-3C00ADD2B610"), ProgId("HelloWorld.Class1")] public class Class1 { }
5.實現接口IDTExtensibility2
IDTExtensibility2來自Extensibility名空間,需要實現以下方法:
public void OnAddInsUpdate(ref Array custom) { } public void OnBeginShutdown(ref Array custom) { } public void OnConnection(object Application, ext_ConnectMode ConnectMode, object AddInInst, ref Array custom) { } public void OnDisconnection(ext_DisconnectMode RemoveMode, ref Array custom) { } public void OnStartupComplete(ref Array custom) { }
6.OnConnection()方法在插件加載時調用,傳遞了OneNote的實例,我們創建一個object類型的變量application來接收
private object application; public void OnConnection(object Application, ext_ConnectMode ConnectMode, object AddInInst, ref Array custom) { application = Application; }
7.為了實現Ribbon按鈕事件,需要添加IRibbonExtensibility接口,IRibbonExtensibility來自Microsoft.Office.Core名空間,包含獲取Ribbon界面的方法
public string GetCustomUI(string RibbonID) { return Properties.Resources.ribbon; }
此時返回工程中的ribbon.xml配置信息
8.現在實現與ribbon.xml文件中按鈕的onAction事件調用的函數,注意此函數是公共的並且以IRibbonControl作為參數
public void showHello(IRibbonControl control) { var app = application as OneNote.Application; var win = app.Windows; string id = (application as OneNote.Application).Windows.CurrentWindow.CurrentPageId; string title; app.GetPageContent(id, out title); var doc = XDocument.Parse(title); string pageTitle = doc.Descendants().FirstOrDefault().Attribute("ID").NextAttribute.Value; MessageBox.Show("Current Page = " + pageTitle, "Hello World!"); }
這里實現了輸出當前頁標題的功能。
9.為Ribbon按鈕添加圖片,需要將圖片添加到資源中

還要實現ribbon.xml中loadImage的GetImage方法
public IStream GetImage(string imageName) { MemoryStream mem = new MemoryStream(); Properties.Resources.HelloWorld.Save(mem, ImageFormat.Png); return new CCOMStreamWrapper(mem); }
這里采用將圖片轉為流的方式
class CCOMStreamWrapper: IStream { public CCOMStreamWrapper(System.IO.Stream streamWrap) { m_stream = streamWrap; } public void Clone(out IStream ppstm) { ppstm = new CCOMStreamWrapper(m_stream); } public void Commit(int grfCommitFlags) { m_stream.Flush(); } public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) { } public void LockRegion(long libOffset, long cb, int dwLockType) { throw new System.NotImplementedException(); } public void Read(byte[] pv, int cb, IntPtr pcbRead) { Marshal.WriteInt64(pcbRead, m_stream.Read(pv, 0, cb)); } public void Revert() { throw new System.NotImplementedException(); } public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition) { long posMoveTo = 0; Marshal.WriteInt64(plibNewPosition, m_stream.Position); switch (dwOrigin) { case 0: { /* STREAM_SEEK_SET */ posMoveTo = dlibMove; } break; case 1: { /* STREAM_SEEK_CUR */ posMoveTo = m_stream.Position + dlibMove; } break; case 2: { /* STREAM_SEEK_END */ posMoveTo = m_stream.Length + dlibMove; } break; default: return; } if (posMoveTo >= 0 && posMoveTo < m_stream.Length) { m_stream.Position = posMoveTo; Marshal.WriteInt64(plibNewPosition, m_stream.Position); } } public void SetSize(long libNewSize) { m_stream.SetLength(libNewSize); } public void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag) { pstatstg = new System.Runtime.InteropServices.ComTypes.STATSTG(); pstatstg.cbSize = m_stream.Length; if ((grfStatFlag & 0x0001/* STATFLAG_NONAME */) != 0) return; pstatstg.pwcsName = m_stream.ToString(); } public void UnlockRegion(long libOffset, long cb, int dwLockType) { throw new System.NotImplementedException(); } public void Write(byte[] pv, int cb, IntPtr pcbWritten) { Marshal.WriteInt64(pcbWritten, 0); m_stream.Write(pv, 0, cb); Marshal.WriteInt64(pcbWritten, cb); } private System.IO.Stream m_stream; }
10.當關閉OneNote的時候要確保清空占用的內存
public void OnDisconnection(ext_DisconnectMode RemoveMode, ref Array custom) { application = null; GC.Collect(); GC.WaitForPendingFinalizers(); }
public void OnBeginShutdown(ref Array custom) { if (application != null) { application = null; } }
11.Class1的全部代碼如下:
using System; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Windows.Forms; using System.Xml.Linq; using Extensibility; using Microsoft.Office.Core; using OneNote = Microsoft.Office.Interop.OneNote; namespace HelloWorld { [Guid("743A0108-BBE3-4D22-A6A8-3C00ADD2B610"), ProgId("HelloWorld.Class1")] public class Class1: IDTExtensibility2, IRibbonExtensibility { private OneNote.Application onApp = new OneNote.Application(); private object application; public void OnConnection(object Application, ext_ConnectMode ConnectMode, object AddInInst, ref Array custom) { application = Application; } public void OnDisconnection(ext_DisconnectMode RemoveMode, ref Array custom) { application = null; GC.Collect(); GC.WaitForPendingFinalizers(); } public void OnAddInsUpdate(ref Array custom) { } public void OnStartupComplete(ref Array custom) { } public void OnBeginShutdown(ref Array custom) { if (application != null) { application = null; } } public string GetCustomUI(string RibbonID) { return Properties.Resources.ribbon; } public void showHello(IRibbonControl control) { var app = application as OneNote.Application; var win = app.Windows; string id = (application as OneNote.Application).Windows.CurrentWindow.CurrentPageId; string title; app.GetPageContent(id, out title); var doc = XDocument.Parse(title); string pageTitle = doc.Descendants().FirstOrDefault().Attribute("ID").NextAttribute.Value; MessageBox.Show("Current Page ID = " + pageTitle, "Hello World!"); } public IStream GetImage(string imageName) { MemoryStream mem = new MemoryStream(); Properties.Resources.HelloWorld.Save(mem, ImageFormat.Png); return new CCOMStreamWrapper(mem); } } }
第四步:安裝和部署
1.在解決方案中添加安裝和部署的項目

2.修改注冊表,右擊Setup工程→View→注冊表,將注冊表中的項清空

3.按如下步驟新建鍵
HKEY_CLASSES_ROOT→AppID→{工程的GUID}
右擊→New:
| 類型 | 名稱 | 值 |
| 字符串值 | DllSurrogate |
HKEY_CLASSES_ROOT→CLSID→{工程的GUID}
右擊→New:
| 類型 | 名稱 | 值 |
| 字符串值 | AppID | {工程的GUID} |
HKEY_CURRENT_USER→Software→Microsoft→Office→OneNote→AddIns→工程的ProgId
右擊→New:
| 類型 | 名稱 | 值 |
| 字符串值 | Description | 自定義的工程描述 |
| 字符串值 | FriendlyName | 自定義的工程名稱 |
| DWORD | LoadBehavior | 3 |
HKEY_LOCAL_MACHINE→Software→Classes→AppID→{工程的GUID}
右擊→New:
| 類型 | 名稱 | 值 |
| 字符串值 | DllSurrogate |
HKEY_LOCAL_MACHINE→Software→Classes→CLSID→{工程的GUID}
右擊→New:
| 類型 | 名稱 | 值 |
| 字符串值 | AppID | {工程的GUID} |
完成后的效果如圖所示:

4.此時就可以安裝我們的工程了,首先如圖進入文件系統

然后選擇安裝目錄:Application Folder (一般是 C:\Program Files\ ···) → 右擊 → Add → Project Output… → OK

5.此時就可以生成解決方案了,如果報錯,你可能需要用管理員權限打開VS
6.最后如圖進行安裝就完成了:


