July 2010

Volume 25 Number 07

OneNote 2010 - Creating OneNote 2010 Extensions with the OneNote Object Model

By Andy Gray | July 2010

Microsoft Office OneNote is a powerful digital notebook for collecting, organizing, searching and sharing information. With the recent release of Microsoft Office 2010, not only is the OneNote user experience improved, but OneNote notebooks are now more universally available. Users can synchronize content among computers via Windows Live; search, edit and share notes from any Web browser; and access full notebooks from Windows Mobile (and, soon, Windows Phone 7). Further, OneNote was previously included only in some Office editions, but it’s now in every edition of Office 2010. All of these factors create a more compelling opportunity than ever before to integrate OneNote into information management solutions.

In this article, I’ll provide an overview of developing applications that interoperate with data from Microsoft OneNote 2010 and 2007. In the process, I’ll introduce the OneNote Object Model project that is freely available on CodePlex and demonstrate how this library makes it easy to integrate information from OneNote notebooks, sections and pages into client applications.

The Evolution of OneNote Development

The initial release of OneNote 2003 didn’t provide an API to external applications. Shortly thereafter, however, OneNote 2003 SP 1 added a COM library, called the OneNote 1.1 Type Library, which enabled programmatic import of images, ink and HTML into OneNote via a simple class called CSimpleImporter. Notably, however, this class only provided data import capabilities; you could use it to push data into OneNote notebooks, but there was no way to get content back out programmatically.

The release of OneNote 2007 brought much more powerful development capabilities with a new COM API that provides the ability to import, export and modify OneNote 2007 content programmatically. The OneNote Application class in that library provides a rich collection of methods for working with:

  • Notebook structure: discovering, opening, modifying, closing and deleting notebooks, section groups and sections
  • Page content: discovering, opening, modifying, saving and deleting page content
  • Navigation: finding, linking to and navigating to pages and objects

Most of these methods return or accept XML documents that represent both notebook structure and page content. Saul Candib wrote a two-part series, “What’s New for Developers in OneNote 2007,” that documents this API at msdn.microsoft.com/library/ms788684(v=office.12), and the XML schema is detailed at msdn.microsoft.com/library/aa286798(office.12).

The XML schema for OneNote 2010 is substantially similar to that in OneNote 2007. OneNote 2010 introduces a file format change to support some of its new features (such as linked note-taking, versioning, Web sharing, multilevel subpages and equation support). However, OneNote 2010 can continue to work on OneNote 2007 notebooks without changing the file format. In OneNote 2010, retrieving data from sections stored in the OneNote 2007 file format will yield XML documents similar to those in OneNote 2007. The primary differences in the XML schema for OneNote 2010 sections are additive changes to support the new features listed earlier. A new XMLSchema enumeration is available to represent the OneNote schema version; many of the OneNote methods have new overloads that take an XMLSchema parameter to indicate the schema version desired.

Note that the CSimpleImporter class, introduced in OneNote 2003 and still available in OneNote 2007, has been removed from OneNote 2010, so applications that use this class need to be rewritten to use the new interfaces in order to work with OneNote 2010.

Accessing OneNote Data Using the COM API

It’s fairly straightforward to start using the OneNote COM API to access live data from OneNote notebooks. Start by creating a new console application in Visual Studio and then add a reference to the Microsoft OneNote 14.0 Type Library COM component (for OneNote 2010) or the Microsoft OneNote 12.0 Type Library COM component (for OneNote 2007).

If you’re using Visual Studio 2010 to develop OneNote 2010 applications, take note of a couple minor compatibility issues. First, due to a mismatch of the OneNote interop assembly that shipped with Visual Studio 2010, you should not directly reference the
Microsoft.Office.Interop.OneNote component on the .NET tab of the Add Reference dialog, but instead reference the Microsoft OneNote 14.0 Type Library component on the COM tab. This still results in the addition of a OneNote interop assembly to your project’s references.

Second, the OneNote 14.0 Type Library is not compatible with the Visual Studio 2010 “NOPIA” feature (in which primary interop assemblies are not embedded in the application by default). Therefore, make sure to set the Embed Interop Types property to False for the OneNote interop assembly reference. (Both of these issues are described in more detail on OneNote Program Manager Daniel Escapa’s blog at blogs.msdn.com/descapa/archive/2010/04/27/onenote-2010-and-visual-studio-2010-compatibility-issues.aspx.) With the OneNote library reference in place, you’re ready to make calls to the OneNote API. The code in Figure 1uses the GetHierarchy method to retrieve an XML document containing a list of OneNote notebooks, then uses LINQ to XML to extract and print the notebook names to the console.

Figure 1 Enumerating Notebooks

using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Office.Interop.OneNote;

class Program
{
  static void Main(string[] args)
  {
    var onenoteApp = new Application();

    string notebookXml;
    onenoteApp.GetHierarchy(null, HierarchyScope.hsNotebooks, out notebookXml);
    
    var doc = XDocument.Parse(notebookXml);
    var ns = doc.Root.Name.Namespace;
    foreach (var notebookNode in 
      from node in doc.Descendants(ns + "Notebook") select node)
    {
      Console.WriteLine(notebookNode.Attribute("name").Value);
    }
  }
}

The HierarchyScope enumeration, passed as the second parameter to the GetHierarchy method, specifies the depth of the notebook structure to retrieve. To retrieve sections in addition to the notebooks, simply update this enumeration value to HierarchyScope.hsSections and process the additional XML child nodes, as demonstrated in Figure 2.

Figure 2 Enumerating Sections

using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Office.Interop.OneNote;

class Program
{
  static void Main(string[] args)
  {
    var onenoteApp = new Application();

    string notebookXml;
    onenoteApp.GetHierarchy(null, HierarchyScope.hsSections, out notebookXml);
    
    var doc = XDocument.Parse(notebookXml);
    var ns = doc.Root.Name.Namespace;
    foreach (var notebookNode in from node in doc.Descendants(ns + 
      "Notebook") select node)
    {
      Console.WriteLine(notebookNode.Attribute("name").Value);
      foreach (var sectionNode in from node in 
        notebookNode.Descendants(ns + "Section") select node)
      {
        Console.WriteLine("  " + sectionNode.Attribute("name").Value);
      }
    }
  }
}

Retrieving and Updating Page Content

The GetPageContent method will return an XML document containing all of the content on a specified page. The page to retrieve is specified using a OneNote object ID, a string-based unique identifier for each object in the OneNote notebook hierarchy. This object ID is included as an attribute on the XML nodes returned by the GetHierarchy method.

Figure 3builds on the previous examples by using the GetHierarchy method to retrieve the OneNote notebook hierarchy down to page scope. It then uses LINQ to XML to select the node for the page named “Test page” and pass that page’s object ID to the GetPageContent method. The XML document representing the page content is then printed to the console.

Figure 3 Getting Page Content

using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Office.Interop.OneNote;

class Program
{
  static void Main(string[] args)
  {
    var onenoteApp = new Application();

    string notebookXml;
    onenoteApp.GetHierarchy(null, HierarchyScope.hsPages, out notebookXml);

    var doc = XDocument.Parse(notebookXml);
    var ns = doc.Root.Name.Namespace;
    var pageNode = doc.Descendants(ns + "Page").Where(n => 
      n.Attribute("name").Value == "Test page").FirstOrDefault();
    if (pageNode != null)
    {
      string pageXml;
      onenoteApp.GetPageContent(pageNode.Attribute("ID").Value, out pageXml);
      Console.WriteLine(XDocument.Parse(pageXml));
    }
  }
}

The UpdatePageContent method can be used to make changes to a page. The page content is specified by the same XML document schema that the code in Figure 3 retrieved; it can contain various content elements that define text outlines, inserted files, images, ink, and audio or video files.

The UpdatePageContent method treats the elements in the provided XML document as a collection of content that may have changed, matching specified content to existing content via its OneNote object ID. You can therefore make changes to existing content by calling the GetPageContent method, making the desired changes to the XML returned, then passing that XML back to the UpdatePageContent method. You can also specify new content elements to be added to the page.

To illustrate this, Figure 4 adds a date stamp to the bottom of our test page. It uses the approach shown in Figure 3to determine the OneNote object ID of the page, and then uses the XDocument and XElement classes in System.Xml.Linq to construct an XML document containing the new content. Because the Page object ID specified in the document matches the object ID of an existing page, the UpdatePageContent method will append the new content to the existing page.

Figure 4 Updating Page Content

using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Office.Interop.OneNote;

class Program
{
  static void Main(string[] args)
  {
    var onenoteApp = new Application();

    string notebookXml;
    onenoteApp.GetHierarchy(null, HierarchyScope.hsPages, out notebookXml);

    var doc = XDocument.Parse(notebookXml);
    var ns = doc.Root.Name.Namespace;
    var pageNode = doc.Descendants(ns + "Page").Where(n => 
      n.Attribute("name").Value == "Test page").FirstOrDefault();
    var existingPageId = pageNode.Attribute("ID").Value;

    if (pageNode != null)
    {
      var page = new XDocument(new XElement(ns + "Page", 
                                 new XElement(ns + "Outline", 
                                   new XElement(ns + "OEChildren", 
                                     new XElement(ns + "OE", 
                                       new XElement(ns + "T", 
                                         new XCData("Current date: " +
                                           DateTime.Now.
                                             ToLongDateString())))))));
       page.Root.SetAttributeValue("ID", existingPageId);
       onenoteApp.UpdatePageContent(page.ToString(), DateTime.MinValue);
    }
  }
}

The OneNote Object Model Library

It isn’t particularly difficult to interact with OneNote data in this way, but it’s a bit awkward to parse and construct XML documents just to perform basic data operations. That’s where the OneNote Object Model comes in. It’s a managed code library that provides object-oriented abstractions over the COM-based OneNote API. The library is open source and licensed under the Microsoft Public License (Ms-PL).

The OneNote Object Model is available for download on CodePlex at onom.codeplex.com. The library was designed for OneNote 2007, and by the time you read this, the release downloads should be updated to provide compatibility with OneNote 2010. If not, you can still use it with OneNote 2007 sections in OneNote 2010 by downloading the source code, removing the existing Microsoft.Office.Interop.OneNote assembly reference in the OneNoteCore project and adding a reference to the Microsoft OneNote 14.0 Type Library as shown previously.

In addition to some unit test projects and sample code, the solution contains two class library projects: OneNoteCore and OneNoteFramework. The OneNoteCore library is the low-level bridge between the OneNote COM API and familiar Microsoft .NET Framework metaphors; it exposes real return values instead of COM out parameters, converts COM error codes into .NET exceptions, exposes a OneNoteObjectId struct and XDocument instances instead of raw strings, and more. Studying this code can help you understand how the OneNote API works, but in most cases you won’t need to interact with the OneNoteCore library directly.

The OneNoteFramework library provides higher-level abstractions of OneNote concepts. Here you’ll find classes with intuitive names like OneNoteNotebook, OneNoteSection and OneNotePage. The primary entry point for interacting with the OneNote hierarchy structure is a class called OneNoteHierarchy, which contains a static member called Current. By adding an assembly reference to the OneNoteFramework library, we can rewrite to our program to enumerate the notebook names (Figure 1) much more concisely as follows:

using Microsoft.Office.OneNote;

class Program
{
  static void Main(string[] args)
  {
    foreach (var notebook in OneNoteHierarchy.Current.Notebooks)
      System.Console.WriteLine(notebook.Name);
  }
}

As you might expect, the OneNoteNotebook class has a property called Sections. Therefore, you can enumerate the section names (Figure 2) simply as follows:

using Microsoft.Office.OneNote;

class Program
{
  static void Main(string[] args)
  {
    foreach (var notebook in OneNoteHierarchy.Current.Notebooks)
    {
      System.Console.WriteLine(notebook.Name);
      foreach (var section in notebook.Sections)
      {
        System.Console.WriteLine("  " + section.Name);
      }
    }
  }
}

Collections exposed by OneNote Object Model properties are managed with a specialized generic collection class called OneNoteObjectCollection<T>. Because OneNoteObjectCollection<T> implements IList<T>, as well as IEnumerable<T>, these collections  can be queried using LINQ.

For example, given a reference to a OneNoteSection instance in the section variable, we could determine all of the pages that had been modified today with a simple LINQ expression like this:

var pagesModifiedToday = from page in section.Pages 
                           where page.LastModifiedTime >= DateTime.Today 
                           select page;

Data Binding with OneNote Object Model Library

The fact that the OneNote Object Model exposes IEnumerable collections also enables XAML-based data binding with Windows Presentation Foundation (WPF). Figure 5 d demonstrates the use of data binding to display a WPF TreeView of the OneNote notebook hierarchy purely in XAML markup—without requiring the use of code behind.

Figure 5 Data Binding with Windows Presentation Foundation

<Window x:Class="NotebookTree.MainWindow"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:onf="clr-namespace:Microsoft.Office.OneNote;assembly=
          OneNoteFramework"
        Title="OneNote Notebook Hierarchy" >
  <Grid>
    <Grid.Resources>
      <DataTemplate x:Key="PageTemplate">
        <StackPanel Orientation="Horizontal">
          <Image Source="Images\Page16.png" Margin="0,0,2,0"/>
          <TextBlock Text="{Binding Name}" />
        </StackPanel>
      </DataTemplate>
            
      <HierarchicalDataTemplate x:Key="SectionTemplate" 
        ItemsSource="{Binding Pages}"
        ItemTemplate="{StaticResource PageTemplate}">
        <StackPanel Orientation="Horizontal">
          <Image Source="Images\Section16.png" Margin="0,0,2,0"/>
          <TextBlock Text="{Binding Name}" />
        </StackPanel>
      </HierarchicalDataTemplate>
            
      <HierarchicalDataTemplate x:Key="NotebookTemplate" 
        ItemsSource="{Binding Sections}"
        ItemTemplate="{StaticResource SectionTemplate}">
        <StackPanel Orientation="Horizontal">
          <Image Source="Images\Book16.png" Margin="0,0,2,0"/>
          <TextBlock Text="{Binding Name}" />
        </StackPanel>
      </HierarchicalDataTemplate>
    </Grid.Resources>
        
    <TreeView Name="NotebookTree" BorderThickness="0"
              HorizontalAlignment="Left" VerticalAlignment="Top"
              ItemsSource="{Binding Notebooks}" 
              ItemTemplate="{StaticResource NotebookTemplate}" 
              DataContext="{Binding Source={x:Static 
                onf:OneNoteHierarchy.Current}}" />
  </Grid>
</Window>

This XAML first references the OneNoteFramework assembly, giving it the XML namespace prefix onf. With this reference in place, the DataContext for the TreeView can then be set to the static Current property of the OneNoteHierarchy class, providing the control with the root of the OneNote hierarchy structure. HierarchicalDataTemplates are then used to data bind each level of the tree with the corresponding collection exposed by the OneNote Object Model (see Figure 6).

image: Data Binding the Hierarchy to a Tree View

Figure 6 Data Binding the Hierarchy to a Tree View

Simplified Data Access

Wrapping up, the OneNote Object Model library substantially simplifies access to data in Microsoft OneNote notebooks, exposing rich object collections that can be queried and manipulated with LINQ expressions and WPF data binding. A follow-up article will extend these concepts to explore working with OneNote notebooks in Silverlight and Windows Phone applications, and accessing OneNote data in the cloud.


Andy Gray is a partner and technology director of Five Talent Software, helping nonprofit organizations operate more effectively through strategic technology solutions.

Thanks to the following technical experts for reviewing this article: Michael Gerfen and John Guin.