You are on page 1of 29

Developing OneNote 2010 Add-Ins As with other Microsoft Office applications, you can also create add-ins for OneNote. In OneNote 2007 you wrote add-ins by implementing IOneNoteAddin (see Daniel Escapa‟s tutorial – ) and they integrated well with OneNote‟s UI, being toolbar based add-ins in a toolbar based UI. With OneNote 2010′s ribbon, however, these „toolbar based‟ add-ins only show up on a separate Add-Ins tab, and even then only appearing as small icons. Integrating these add-ins with the ribbon / fluent UI is what we want for OneNote 2010, to properly „integrate‟ them with the UI. Like this (a custom button in a custom tab in the ribbon):

This is a tutorial / guide on how to do this.

This has been tested with Visual Studio 2008 Professional and Visual Studio 2010 Professional.
 

Download the Visual Studio 2008 source code Download the Visual Studio 2010 source code

This guide is broken down into five parts:  Part 1 – Creating the project  Part 2 – Creating the ribbon  Part 3 – Writing some code  Part 4 – Fixing the Installer  Part 5 – Tips & Good Sites

Malte Ahrens

Part 1 – Creating the project
Whereas the “common” (not that OneNote isn‟t) office programs, like Word, PowerPoint, Outlook, and Excel, all have VSTO (Visual Studio Tools for Office) templates, making it really easy to create add-ins for those programs. OneNote does not, but it does share a very similar add-in architecture. 1. Open Visual Studio as Administrator (so that Visual Studio can register your add-in with COM Interop) 2. New Project → [Your Language of Choice] → Windows → Class Library

For this tutorial we will choose .NET 3.5 and C#, but you can choose what you want

Developing OneNote 2010 Add-Ins

Page |2

Application tab → Assembly Information Developing OneNote 2010 Add-Ins Page |3 . First up we need to make the application COM-Visible a.3. Right-Click on the Project → Properties b.

Select „Make assembly COM-Visible‟. This makes the class library / . Build tab → Register for COM Interop. Click OK e. d. This gets Visual Studio to automatically register the application for COM Interop when it‟s built Developing OneNote 2010 Add-Ins Page |4 .c.dll generated by the project visible to OneNote.

cs b. To make debugging and building the project easier.*")] Developing OneNote 2010 Add-Ins Page |5 . we will change the AssemblyVersion attribute to automatically increment.0. Open: Properties → AssemblyInfo.0")] to: [assembly: AssemblyVersion("1.4. Change the following: [assembly: AssemblyVersion(" we won‟t have to always update this attribute each time we want to update your application a. This way.*")] [assembly: AssemblyFileVersion("1.0")] [assembly: AssemblyFileVersion("1.0.

Part 2 – Creating the Ribbon The Fluent UI or Ribbon in Microsoft Office (the „new interface‟) was launched with Office 2007 with the „common‟ programs (again. PowerPoint. In 2010 it came to the others. and Excel. extending it (at least visually) is quite easy. Outlook. our second step is to create an XML file that will contain this code to extend the ribbon: 1. Right Click on the Project → Properties → Resources → „Click here to create one‟ Developing OneNote 2010 Add-Ins Page |6 . So. OneNote. To make this XML file readable by our add-in during runtime we will make it a resource (so that it is embedded in the . a.xml 2. not that OneNote isn‟t) first – Word. Call it something like: ribbon. Add an XML file to the Add-In Project (Right Click → Add → Add New Item → Data → XML File).dll created). Since the Fluent UI is controlled by an XML file. etc.

Just add the following attribute to the <CustomUI> element: loadImage="GetImage" so that it looks like this: <customUI xmlns="http://schemas. but we need to set-up the function so that we can do this. MSDN has a great introduction to XML and the Ribbon. Next.Our content goes here --> </ribbon> </customUI>" loadImage="GetImage"> . just Click-and-drag ribbon.b.0" encoding="utf-8" ?> <customUI xmlns="http://schemas. Basically. The XML document to customize the ribbon starts with: <?xml version="1. we need a Tab (our own or an already existing one) to place our Group – a collection of controls – filled with our controls. In order to display images in the ribbon.. See: </customUI> Developing OneNote 2010 Add-Ins Page |7 .com/office/2006/01/customui"> <ribbon> <!-. We will do this later on in this guide. they need to be parsed in a special way.xml into the Resources box

All that a tab needs is an id and a label: <tab id="tabCustom" label="Custom"> </tab> 7. we need to give it the path to the image and we need to give it an onAction function (basically the OnClick() event): <button id="buttonHello" label="Hello World!" size="large" screentip="Press this for a 'Hello World' message" onAction="showHello" image="HelloWorld. and add a tooltip. we will create our own Group. Again. In the ribbon.6. In this group we place our controls – for example Buttons.png"/> Developing OneNote 2010 Add-Ins Page |8 . we will create our own tab. Next. For this demo. check out the Control IDs (www. this can be our own group or an already existing one. Again it needs an id and a label. If you want to extend an already existent tab. Again. etc. Either we can hook into an existing tab or we can create our own. For this Also. Just replace id=”” with the idMso=”[the control id]” (without square brackets). Checkboxes. we need a group in which to place our controls. the first thing that we need is a but we will also change (the next ones are optional) its size to large (for a big button). For this demo. Menus. all it needs is an id and a label: <group id="groupHello" label="Hello"> </group> 8. we will just add a simple button.

9.0" encoding="utf-8" ?> <customUI xmlns="" 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. This is what it should look like now: <?xml version="1.png"/> </group> </tab> </tabs> </ribbon> </customUI> Developing OneNote 2010 Add-Ins Page |9 .

1 For Visual Studio 2008.0 Object Library (for Ribbon Extensibility If you are using . Add a using to the top of Class1.NET System.0 Type Library‟ Developing OneNote 2010 Add-Ins P a g e | 10 .0 Object1 Library (for the OneNote API) COM Microsoft Office 14.cs (you can rename it if you want. you update the ProgId as well) 2.Part 3 – Writing some code With the ribbon done.0 Object Library. and get our add-in to do something.NET Extensibility (note the capital E) (for the COM Add-In code) .Windows.msdn.InteropServices.NET System.NET 4 / VS 2010 be sure to turn off ‘Embed Interop Types’ for Microsoft OneNote 3. 1. just be certain that if you change the class name (the public class [classname]). Go to Class1.Forms (for a MessageBox) .Runtime. See Daniel Escapa’s post on how and why: blogs. Add a reference (Right-Click on References → Add Reference…) to the following: Tab Name . next up we need to hook up the ribbon‟s events. among the other „usings‟: using System. it appears as „Microsoft OneNote 14.cs.Drawing (for putting Images into the ribbon) COM Microsoft OneNote 14.

the Add-In requires a GUID to identify it.e. Tools → Create GUID (if it isn‟t there. or even better OneNote) as they are needed for the setup project (later). Just above: public class Class1 add: [GuidAttribute("[Your app’s Guid]"). To create one: a. copied into Notepad. Next. do a search in the start-menu) b. Choose Registry Format → Copy → Exit 5.ClassName3 Ensure that you have these values on hand (i.Class1 Developing OneNote 2010 Add-Ins P a g e | 11 .4. 2 3 For example: 61139959-A5E4-4261-977A-6262429033EB For example: HelloWorld.ProgId("[Your app’s ProgId]")] [Your app‟s Guid] = <paste> (without curly braces) the one created in Step 42 [Your app‟s ProgId] = Project.

So now there are a whole heap of stub methods with the code: throw new NotImplementedException(). Right-Click on IDTExtensibility2 (in the code (above) that we just added) → Implement Interface → Implement Interface. among the other „usings‟: using Extensibility. This will generate all the method stubs that the Add-In encounters during runtime 9. so that our code now looks like this: public class Class1 : IDTExtensibility2 { } 8. that only the methods remain: public void OnAddInsUpdate(ref Array custom) { } public void OnBeginShutdown(ref Array custom) { } public void OnConnection(object Application. ref Array custom) { } public void OnStartupComplete(ref Array custom) { } Developing OneNote 2010 Add-Ins P a g e | 12 . ext_ConnectMode ConnectMode. This will allow us to implement IDTExtensibility2 (it contains all the COM Add-in functions). This needs to be removed.cs. ref Array custom) { } public void OnDisconnection(ext_DisconnectMode RemoveMode. So delete them. the application will exist because of these exceptions being thrown. 7. object AddInInst. Add a using to the top of Class1. otherwise. when these methods are encountered during runtime.6.

ref Array custom) { onApp = (ApplicationClass)Application. 11.cs. so we set a new class-wide variable. ext_ConnectMode ConnectMode. Here OneNote passes in the current instance of the ApplicationClass. among the other „usings‟: using Microsoft. onApp. Add a using to the top of Class1.Core...10. The OnConnection() function is called when the Add-In is being loaded. object AddInInst. */ } Developing OneNote 2010 Add-Ins P a g e | 13 . IRibbonExtensibility { /* Code here. 13. public void OnConnection(object Application. to equal it. This will allow us to implement IRibbonExtensibility (it contains all the Ribbon functions). } 12. the programmatic interface to OneNote‟s API.Office. To hook the ribbon‟s events to our code we need to add: using Microsoft.OneNote.Office.Interop. so that our code now looks like this: public class Class1 : IDTExtensibility2. ApplicationClass onApp = new ApplicationClass().

} With returning the XML Resource (the ribbon. Now to make the button work we create a function with the same name that we mentioned in the ribbon. Right-Click on IRibbonExtensibility (in the code (above) that we just added) → Implement Interface → Implement Interface.14.Resources. 15.ribbon. Replace the code: public string GetCustomUI(string RibbonID) { throw new NotImplementedException(). This will create a stub of the Ribbon‟s load event for us. It is important that this function is both public and takes an IRibbonControl as a parameter: public void showHello(IRibbonControl control) { } Developing OneNote 2010 Add-Ins P a g e | 14 .xml for our button‟s onAction event. } 16.xml file) that we created in Step 2 public string GetCustomUI(string RibbonID) { return Properties.

c. Call it CCOMStreamWrapper. To get an image to load on the ribbon. click „Add existing file‟. Original: CCOMStreamWrapper. we call the ApplicationClass we created earlier: string id = onApp. For this demo. First.CurrentWindow.xml) i. e. This function will be called when the (in our case) button is pressed. we will just make it show a Messagebox with the current page‟s ID. and select the file (HelloWorld.cs c. This is the part where you can do what you want with your own code. we need to add the image to our project (similarly to how we added ribbon. Paste in the following code written by Nani (Microsoft) (relevant blog link: blogs.Windows. 18. as the image of a button. We start off by creating a new class (Right-Click on the Project → Add → New Class).Forms.Windows.g. ii.CurrentPageId. And we will show it in a MessageBox MessageBox. To get the ID of the current page. First add: using System. b.cs Developing OneNote 2010 Add-Ins P a g e | 15 .png) iii.Show("Current Page ID = " + id. In the Solution Explorer → Resources → right-click the inserted image → Properties → change its „Build Action‟ to „Content‟ b. Go to Resources (Right-Click on the Project → Properties → Resources) Click on the down-arrow next to „Add Resource‟. we need to pass the image as an IStream a.msdn. "Hello World!") ) that converts the image to an IStream Comments have been removed to fit this onto two pages. a.

NotImplementedException(). namespace HelloWorld { class CCOMStreamWrapper : IStream { public CCOMStreamWrapper(System. m_stream. } public void Seek(long dlibMove. switch (dwOrigin) { case 0: { /* STREAM_SEEK_SET */ Developing OneNote 2010 Add-Ins P a g e | 16 .msdn.InteropServices. int dwOrigin.ComTypes. Marshal. cb)).InteropServices. System.Collections.Position). } public void Read(byte[] * */ using using using using using System.WriteInt64(pcbRead.Read(pv. long cb. IntPtr pcbRead) { Marshal. int dwLockType) { throw new System. IntPtr plibNewPosition) { long posMoveTo = 0. long cb.Text.Stream streamWrap) { m_stream = streamWrap. IntPtr pcbRead. } public void Revert() { throw new System. 0. System.IO. System.NotImplementedException().WriteInt64(plibNewPosition.Flush().Runtime. } public void CopyTo(IStream pstm./* * Code from: * TOC Power Toy 2010 by Nani (Microsoft) * See: http://blogs. } public void Clone(out IStream ppstm) { ppstm = new CCOMStreamWrapper(m_stream). m_stream.Runtime. System. int cb. IntPtr pcbWritten) { } public void LockRegion(long libOffset. } public void Commit(int grfCommitFlags) { m_stream.

Length. case 1: { /* STREAM_SEEK_CUR */ posMoveTo = m_stream.Position).ComTypes.WriteInt64(pcbWritten. int cb. pstatstg.cbSize = m_stream. IntPtr pcbWritten) { Marshal.Write(pv. Marshal. } break.Position + dlibMove. } public void Write(byte[] pv.Runtime.pwcsName = m_stream. } public void Stat(out System.WriteInt64(pcbWritten. pstatstg.InteropServices.NotImplementedException().InteropServices. 0).Stream m_stream. case 2: { /* STREAM_SEEK_END */ posMoveTo = m_stream. long cb.WriteInt64(plibNewPosition.Position = posMoveTo. } } Note: You may have to change the namespace ‘HelloWorld’ to your own namespace Developing OneNote 2010 Add-Ins P a g e | 17 . if ((grfStatFlag & 0x0001/* STATFLAG_NONAME */) != 0) return. m_stream. } break. } } public void SetSize(long libNewSize) { m_stream. cb). 0.STATSTG().Length + dlibMove. cb). default: return.SetLength(libNewSize). Marshal.posMoveTo = dlibMove.ToString().ComTypes.IO.Length) { m_stream. } if (posMoveTo >= 0 && posMoveTo < m_stream. } break.Runtime. int dwLockType) { throw new System. int grfStatFlag) { pstatstg = new System. m_stream.STATSTG pstatstg. } private System. } public void UnlockRegion(long libOffset.

that we referenced in the ribbon. Properties. Collect the „Garbage‟ with . return new CCOMStreamWrapper(mem).d.xml load image attribute): public IStream GetImage(string imageName) { } iv. ii. } Developing OneNote 2010 Add-Ins P a g e | 18 . GetImages(). GC. next we need to use the function. To do this. we: a. Next we need to create a memory stream.Array custom) { onApp = null.Resources.WaitForPendingFinalizers(). GetImage() needs an IStream of the image passed back.xml earlier i. using System. It provides one variable. iii.Imaging.Png).HelloWorld. save the image to it (with the correct ImageFormat). So we start off with an empty function (making sure that its name is the same as the one specified in ribbon.Save(mem.Collect().NET‟s GC (GarbageCollector) in the OnDisconnection void public void OnDisconnection(ext_DisconnectMode disconnectMode. ref System. ImageFormat.Runtime. The function called.ComTypes. When the application is closing. GC. } where HelloWorld is the name of the image that you imported 13.xml each time in the image attribute (for example in a button). So first we add (for the IStream and ImageFormat): using System. that is the string specified in ribbon.cs. Going back to Class1. we want to ensure that we clear up the memory used. the image name.InteropServices.Drawing. and return it (after it has been through the CCOMStreamWrapper function): public IStream GetImage(string imageName) { MemoryStream mem = new MemoryStream().

InteropServices.b.Collect().Core. System. System. Extensibility.Windows.Array custom) { onApp = null. System.Runtime. System.cs should look like (with a different GUID & ProgId): (Original: class1. public void OnConnection(object Application.Class1")] public class Class1 : IDTExtensibility2. Microsoft. object AddInInst.Array custom) { if (onApp != null) onApp = null. GC. Make our ApplicationClass variable (onApp) equal to null in the OnBeginShutdown void public void OnBeginShutdown(ref System. System.WaitForPendingFinalizers().IO.Collections. ref Array custom) { onApp = (ApplicationClass)Application. Microsoft.Linq. System. } public void OnStartupComplete(ref Array custom) { } public void OnAddInsUpdate(ref Array custom) { } Developing OneNote 2010 Add-Ins P a g e | 19 .Drawing. } Now Class1. System.Text.Interop.Office. IRibbonExtensibility { #region IDTExtensibility2 Members ApplicationClass onApp = new ApplicationClass().InteropServices. ext_ConnectMode ConnectMode.OneNote.Forms.cs) using using using using using using using using using using using using System. } public void OnDisconnection(Extensibility.ComTypes.Office. System.Imaging. } public void OnBeginShutdown(ref System.ext_DisconnectMode disconnectMode. ref System.Runtime. GC.Array custom) { if (onApp != null) onApp = null. ProgId("HelloWorld.Generic. namespace HelloWorld { [GuidAttribute("61139959-A5E4-4261-977A-6262429033EB").

} #endregion } } Developing OneNote 2010 Add-Ins P a g e | 20 .HelloWorld.CurrentWindow. "Hello World!").Show("Current Page ID = " + id. } public IStream GetImage(string imageName) { MemoryStream mem = new MemoryStream(). return new CCOMStreamWrapper(mem).Resources.Windows.Save(mem.#endregion #region IRibbonExtensibility Members public string GetCustomUI(string RibbonID) { return Properties. } public void showHello(IRibbonControl control) { string id = onApp.Png).Resources. ImageFormat. MessageBox.ribbon. Properties.CurrentPageId.

1. Office Add-ins use the registry to specify settings and link the dll with the Office application. so that only the default hives are left Developing OneNote 2010 Add-Ins P a g e | 21 . The installer needs to create these registry keys. Remove all the keys.Part 4 – Fixing the Installer The last thing to do now is to create an installer. Click on the setup project. and copy the Project Output (the dll created) to the Program Files directory. and choose „Registry Editor‟ 3. Add a Setup Project to the solution: File → Add → New Project → Other Project Types → Setup and Deployment → Visual Studio Installer → Setup Project 2.

com Developing OneNote 2010 Add-Ins P a g e | 22 . it would never load. HKEY_CURRENT_USER\Software\Classes\CLSID\[Your app's GUID] Type string Name AppID Value [Your app's GUID] e. HKEY_CURRENT_USER\Software\Classes\AppID\[Your app's GUID] Type string Name Value DllSurrogate d.4. Create the following keys (Right-Click on a [Key / Hive] → Key) with their respective values (Right-Click → New → <Type>). For more information. HKEY_CLASSES_ROOT\AppID\[Your app's GUID] Type string Name Value DllSurrogate b. Be sure to check their spelling – a common source of frustration when the add-in doesn‟t load in OneNote. but because a tab is not contained in a tab. HKEY_CLASSES_ROOT\CLSID\[Your app's GUID] Type string Name AppID Value [Your app's GUID] c. a. HKEY_LOCAL_MACHINE\Software\Microsoft\Office\OneNote\AddIns \[Your app's ProgID] Type Name string Description string FriendlyName DWORD LoadBehavior Value [Your app's description] [Your app's name] 3 LoadBehavior should be "9" (load the add-in when the tab loads) for editing existing tabs or "3" (load the add-in when the application loads) for newly created tabs. We are using "3" in this tutorial.cs 5. see: because otherwise (if we used "9") our tab would only load when the tab containing it would load. HKEY_LOCAL_MACHINE\Software\Classes\AppID\[Your app's GUID] Type string f. Note your app‟s GUID and ProgId. found in Class1. Name Value DllSurrogate HKEY_LOCAL_MACHINE\Software\Classes\CLSID\[Your app's GUID] Type Name Value string AppID [Your app's GUID] g.

Open notepad and paste in the following code (regkeys.g.00 [HKEY_CLASSES_ROOT\AppID\[Your app’s GUID]] "DllSurrogate"="" [HKEY_CLASSES_ROOT\CLSID\[Your app’s GUID]] "AppID"="[Your app’s GUID]” [HKEY_CURRENT_USER\Software\Classes\AppID\[Your app’s GUID]] "DllSurrogate"="" [HKEY_CURRENT_USER\Software\Classes\CLSID\[Your app’s GUID]] "AppID"="[Your app’s GUID]” [HKEY_LOCAL_MACHINE\Software\Classes\AppID\[Your app’s GUID]] "DllSurrogate"="" [HKEY_LOCAL_MACHINE\Software\Classes\CLSID\[Your app’s GUID]] "AppID"="[Your app’s GUID]” [HKEY_LOCAL_MACHINE\Software\Microsoft\Office\OneNote\AddIns\[ Your app’s ProgId]] "Description"="Press for a Hello World!" "FriendlyName"="Hello World!" "LoadBehavior"=dword:00000003 b. Find the save *. regkeys. <Right-Click> on „Registry on Target Machine‟ → Import… d. Save the file as <filename>.reg (e.If you don‟t want to add all the keys to Visual Studio‟s Registry Editor manually: a.reg) c. sometimes (especially with the DWORD) insert doesn’t quite work) Developing OneNote 2010 Add-Ins P a g e | 23 .reg): Windows Registry Editor Version 5.reg file and open it e. Visual Studio will insert the keys and values for you Be sure to check the imported values.

and your ProgID will probably be different to the one displayed here): 7. and choose „File System Editor‟ Developing OneNote 2010 Add-Ins P a g e | 24 . Click on the setup project. Now it should look like this (your GUID will be different.6.

8. we need to add the project‟s output (the dll created) to the install location: Click on the Application Folder (generally the \Program Files\ directory) → Right-Click → Add → Project Output… → OK 9. Running 64-Bit Office? Be sure to also change the TargetPlatform to x64 in the Installer Properties (Click on the setup project → F4) 10. change the installer setting (Click on the setup project → F4) RemovePreviousVersions to True. now we are done with process in Visual Studio. especially Product Name and Manufacturer. install the setup project that it creates. Last. as the default install location is: [ProgramFilesFolder]\[Manufacturer]\[ProductName] Done! So. You may also want to change some of the other settings. so we just build the solution. Second last. and you should see your add-in in OneNote! Developing OneNote 2010 Add-Ins P a g e | 25 .

and install it. when you want to debug the addin you need to change the Version of the Installer (as it checks for later versions before installing). From the „active solution configuration‟. build the solution. Go to Solution Configurations Selector → Configuration Manager 2. choose <New…> Developing OneNote 2010 Add-Ins P a g e | 26 .Tips & Good Sites Currently. while still easily able to go back to our previous config where Visual Studio built the solution in the bin\debug folder. Speeding up this process (after the installer has run once. First we will create a new Solution Configuration so that we can get Visual Studio to build the solution in the C:\Program Files path. This can become very annoying when compiling frequently and attempting to debug an add-in. and made all the changes to the registry as necessary) you can get Visual Studio to simply build the project at its install location. thus not needing to constantly reinstall the application. To do this: 1.

open OneNote again. Change the Output path (under Output) to the install path of the application (generally C:\Program Files\<Manufacturer>\<ProductName>\) 7. and your addin should have updated. Developing OneNote 2010 Add-Ins P a g e | 27 .3. Create a new Solution Configuration. and Close 5. Now simply build (Build → Build Solution) the project as normal. Click OK. copying settings from „Debug‟ 4. go to the Build tab. In Properties. Right-Click on the Project → Properties 6.

To do this: a.exe and OneNote before Visual Studio will allow you to build the project (files may be in use that would otherwise be overwritten) 2.Note: 1. You may need to close dllhost. File → Options → Add-Ins → (Manage: COM Add-Ins) → Go b. but this time selecting your Add-In again d. Deselect your add-in and click OK (and OK in the OneNote Options window) c. thus (hopefully) loading the newer image for your add-in in the ribbon Developing OneNote 2010 Add-Ins P a g e | 28 . „reload‟ the addin in OneNote. as it gets OneNote to completely reload the addin. Repeat the process (starting at a) again. If your addin has not updated in OneNote. especially if the image in the ribbon has not. This generally works.

aspx Office Schema Reference (useful for trying to understand OneNote‟s XML elements and Debugging OneNote API access through the log: http://blogs.xsd. but it‟s still relevant): Editing content in OneNote through OneNote‟s API: http://msdn.12) but again it‟s still relevant (and it provides it in a HTML format and *. adding a button on the Home tab.Useful links  Debugging a OneNote Add-in with Visual Studio (for OneNote 2007 add-ins. rather than a new tab): and notebooks in OneNote: http://blogs.aspx Customizing the Ribbon / Fluent UI (for Office 2007. and Buttons in OneNote‟s ribbon (useful for e. It‟s the 2007 All the OneNote blogs (especially John Guin and Daniel Escapa)          Developing OneNote 2010 Add-Ins P a g e | 29 .msdn. but it‟s still relevant): http://msdn. sections (and section groups). OMSpy – taking a look at the XML structure of pages. whereas the 2010 one only has the *.aspx OneNote Error Codes / COM Exceptions: http://msdn.xsd)): http://www.aspx Getting the Control IDs for Tabs.aspx?FamilyId=15805380F2C0-4B80-9AD1-2CB0C300AEF9&displaylang=en Source Code from the Table Sorting Powertoy (by Nani – Microsoft):