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.最后如图进行安装就完成了: