Plugins for Windows Live Writer are simple to create and can provide many extra features.

This article will show you how to start writing plugins for Windows Live Writer, including how to get started, how to debug, and how to package the final plugin.

Before you can begin writing a plugin for Windows Live Writer (http://writer.live.com/), you need to understand what it is and what it actually does. Windows Live Writer is a free desktop blog authoring tool from Microsoft that allows you to write an entry for your blog without having to be online and do it right from your desktop. Live Writer doesn’t just support Microsoft’s own blogging site (Windows Live Spaces), it supports a multitude of different blog engines: basically any that support the MetaWeblog API or Atom Publishing Protocol. This list includes Wordpress, Community Server, LiveJournal, and Blogger. You can write your blog entry in the style of your own blog as well because Live Writer downloads your blog style and uses it as a template.

Settings, like Forms, can be an integral part of your plugin if you would like the user to be able to set defaults for the plugin.

Whether you’re someone just starting out with their first blog, or a seasoned pro, Windows Live Writer makes the blogging experience that much better. And the features that don’t come with the program can come in the form of plugins that add extra extensibility and improve the blogging experience even more.

Your Development Environment Pre-requisites

Live Writer plugins are written in .NET, but while you can use .NET 1.1, it is recommended to use .NET 2.0. You will need Visual Studio 2005 or above installed-this can be any version of Visual Studio 2005 or 2008 (http://msdn.microsoft.com/vstudio), including the free Express Editions of Visual Studio (http://www.microsoft.com/express/). You will also need to download Windows Live Writer (http://writer.live.com/) as this has the required API DLL on which the plugins are based.

As plugins are developed in .NET, you can use any language that uses it, but for my examples, I will be using C#.

Writing Your Plugin

Open up Visual Studio and create a new Class Library project, giving it a meaningful name for your plugin. Rename the default class1.cs to plugin.cs (this isn’t essential, but will allow you to quickly see the main plugin class).

Next you need to add the Windows Live Writer API reference: bring up the Add Reference window, point to your Windows Live Writer install folder (typically C:\Program Files\Windows Live\Writer), and then select WindowsLive.Writer.API.dll. As a best practice for this reference, set the property of Copy Local to be false for this reference (this will stop older versions of the API DLL being copied into the Live Writer Plugins folder). Open plugin.cs and include the following using statement:

using WindowsLive.Writer.Api;

You also need to add a reference to the Windows Forms namespace, so add System.Windows.Forms as a reference and also include the following:

using System.Windows.Forms;

Now you can add the Plugin’s attributes: these tell Live Writer about who made the plugin, the link to the developer’s Web site, the description, whether the plugin has options, and the location of the image to be used as the plugin’s icon within the Live Writer UI. These details go in just after the namespace (before the main class).

The code for this is as follows:

[WriterPlugin ("d39bba2b-9522-49b1-8731-61030ccd6c95",
    "My First Plugin",
    Description = "This is my first plugin",
    HasEditableOptions = false,
    Name = "My First Plugin",
    PublisherUrl = "<a href="http://www.liveside.net">http://www.liveside.net</a>")]
[InsertableContentSource ("Bold Text")]

Please note: The GUID is unique for each plugin and you shouldn’t reuse it in other plugins as this can cause problems for Writer and may cause your plugin not to be loaded.

To add an icon for your plugin, you should include the code:

ImagePath = "icon.gif",

The value of this attribute must be the file name of the icon you wish to use. Its location is relative to where it is stored in your project. The image itself must be 20 x 18 pixels in size.

After this, you need to create your plugin class. The plugin class has to inherit from the Live Writer API and there are two choices to inherit from: ContentSource and SmartContentSource. ContentSource plugins are very basic plugins and will only insert text into the blog entry. The plugin cannot change that text again. SmartContentSource plugins can do a lot more and will be covered later on in the article. For this example, I am going to inherit just from the ContentSource interface:

public class LiveWriterExamplePlugin : ContentSource

Then you need to include how the plugin will insert the text. There are three different types:

  •     CreateContent (from the Insert menu).
    
  •     CreateContentFromUrl (when pasting a URL into the blog entry area or from a Blog This action).
    
  •     CreateContentFromLiveClipboard (using the LiveClipboard, although this method isn’t widely used as there isn’t much LiveClipboard content out there due to documentation).
    

CreateContent is the most commonly used method, although there are examples where the FromUrl method has been used. The plugin code overrides all of these methods and they are not mutually exclusive; you can have more than one type in a plugin.

Override CreateContent Method

You can see that there is a string reference of content. There are two things of note about this reference:

Remove the base code that is put in, this is just a placeholder. To start off with, for this first example, just return DialogResult.OK. This first part will just be a simple plugin to make the highlighted text bold, so the code for the CreateContent method is simply:

public override DialogResult CreateContent
            (IWin32Window dialogOwner, 
 ref string content)
{
       if (!string.IsNullOrEmpty(content))
           content = 
string.Format("&lt;b&gt;{0}&lt;/b&gt;", content);
       return DialogResult.OK;
}

Using this code, the plugin will take in whatever text was highlighted and the bold HTML tags to either end of that content. Listing 1 shows just how simple the code can be for a Live Writer plugin.

Compiling and Debugging

There are several little tricks to know about compiling and debugging your plugins for Live Writer as they are DLL files and so can’t be run on their own. The two main tricks are for copying the built DLL to Live Writer’s plugin directory and how to start Live Writer so that you can debug your plugin.

ContentSource plugins are very basic plugins and will only insert text into the blog entry. You can edit SmartContentSource plugins’ content again at a later time.


Figure 1 shows the command needed for copying the built plugin to the Live Writer plugin directory, the command being:

![Figure 1: What you need to get the plugin to copy to the plugins folder.](https://codemag.com/Article/Image/0804092/Lovegrove_Figure 1.tif)

XCOPY /D /Y /R "$(TargetPath)" "C:\Program Files\Windows Live\Writer\Plugins\"

Note: If your Program Files folder is in a different location to the Windows default, you will need to change this for the way in which your system is configured.

Alternatively, you can simply set the output folder of the project to point to your Live Writer Plugins folder.

Figure 2 shows you the setting needed to be able to debug your plugin. You must start Windows Live Writer as an external application in order to debug your plugin. This will only work if you have done what is in Figure 1, otherwise the plugin won’t have been copied to the plugin directory and Writer won’t pick it up.

![Figure 2: What you need to debug your plugin.](https://codemag.com/Article/Image/0804092/Lovegrove_Figure 2.tif)

Once you have these two settings configured in Visual Studio, you can press F5 as you would normally, which will then build your project, copy it to the plugin directory, and then load Windows Live Writer with your plugin in debug mode.

Now you are in debug mode, Figure 3, Figure 4, and Figure 5 will show the stages of using the plugin. In Figure 3 you have your text as it was typed, in normal font style. In Figure 4 you see the text is highlighted so that the plugin will pick up that text and use it. Figure 5 shows the results of clicking the plugin name from the Insert section of Writer.

![Figure 3: Some sample text to be made bold.](https://codemag.com/Article/Image/0804092/Lovegrove_Figure 3.tif)

![Figure 4: The highlighted text.](https://codemag.com/Article/Image/0804092/Lovegrove_Figure 4.tif)

![Figure 5: Having used the plugin the highlighted text is now bold.](https://codemag.com/Article/Image/0804092/Lovegrove_Figure 5.tif)

Using Forms

Windows Forms can be an integral part of your plugin, so it’s important to know how to call them correctly from your plugin code. Forms can control exactly what you want the user to put into their blog entry. This can range from putting in video URLs, selecting an emoticon from a range of different emoticons, or even searching for a book from Amazon; you can use Forms in a multitude of different ways.

You call the Form from within the CreateContent method like so:

public override DialogResult CreateContent
        (IWin32Window dialogOwner, 
         ref string content)
{
     using (frmMain form = new frmMain())
     {
         DialogResult result = form.ShowDialog();
         if (result == DialogResult.OK)
             content = form.NewContent;

         return result;
     }
}

In this snippet of code I have a simple Form with a property called NewContent, which will give me the new content value I want to have inserted into the blog entry. Usually the final code to be put in the plugin will be a property of the Form.

It’s important to note that in the code for your Accept button in your Form, you have to put this.DialogResult = DialogResult.OK; so that when the Form closes the If condition checking for that result is actually met.

Using Settings

Settings, like Forms, can be an integral part of your plugin if you would like the user to be able to set defaults for the plugin. Typically this would be done by the user going to the Options area of Live Writer, selecting the plugin, and then choosing the Options button as shown in Figure 6.

![Figure 6: How your plugin’s options are accessed in Live Writer.](https://codemag.com/Article/Image/0804092/Lovegrove_Figure 6.tif)

To start with, Live Writer has to be told that your plugin has an options Form, which is done by making sure you set HasEditableOptions=true in the Plugin Attributes. Next you have to create a Settings class, as this makes it easier to access the settings.

The Settings class also has to use WindowsLive.Writer.Api, as you need to pass it an IProperties object to read/write the settings to. The constructor of your settings class should look like the following:

IProperties m_properties;
public PluginSettings(IProperties properties)
{
       m_properties = properties;
}

Note that I have a global variable for this class of m_properties, this is for calling that IProperties in the class’ Properties.

The Live Writer APIs understands five types of setting types: String, Int, Float, Bool, and Decimal. They all have their own Get and Set methods (GetString(), SetString(), etc.) and these are part of the IProperties class. So a simple property for a setting that gets saved using the Live Writer APIs will look like this:

public bool DefaultBoldOption
{
        get { return m_properties.GetBoolean
            ("DEFAULTBOLD", true); }
        set { m_properties.SetBoolean
            ("DEFAULTBOLD", value); }
}

Each Get method requires an attribute name (in this case “DEFAULTBOLD”) and a default value, so if it’s the plugin’s first time being used it has something to fall back on. Each Set method requires the attribute name, like the Get method, and that then just sets the attribute using the value passed. You will need to do this for all settings you wish your plugin to have.

Once you are happy with your Settings class, you need to initialize the settings from within the plugin code. Back in the original plugin class, add a global variable:

PluginSettings m_defaultsettings;

You then need to assign some PluginSettings to those default settings, so you need to override the Initialize() method:

public override void Initialize
        (IProperties pluginOptions)
{
        base.Initialize(pluginOptions);
        m_defaultsettings = 
            new PluginSettings(pluginOptions);
}

Having the base.Initialize(pluginOptions) tells Writer to get the plugin settings from the registry for this particular plugin and put them into an IProperties class, which you then pass to the PluginSettings class that you created so you can read/write to them.

Now you have that done, you need to create your Options Form. For this example, I created a Form that looks like Figure 7. You will need to pass the PluginSettings object to the Form itself:

![Figure 7: How the options Form looks for this sample plugin.](https://codemag.com/Article/Image/0804092/Lovegrove_Figure 7.tif)

PluginSettings m_settings;
public frmOptions(PluginSettings settings)
{
        InitializeComponent();
        m_settings = settings;
        // This sets the check box to be 
        // whatever the default option was
        chBoldOption.Checked = 
            m_settings.DefaultBoldOption;
}

The Save button will have the following action for its Click event:

private void btnSave_Click
        (object sender, EventArgs e)
{
        // Sets the option to whatever
        // the check box is currently at
        m_settings.DefaultBoldOption = 
            chBoldOption.Checked;
        this.Close();
}

This example only has one setting, but the principle is the same for however many settings your plugin has.

Back to the original plugin class, you now need to override the EditOptions method to call your Options Form:

public override void EditOptions
   (IWin32Window dialogOwner)
{
         PluginSettings settings =
             new PluginSettings(this.Options);
         frmOptions op =
             new frmOptions(settings);
         op.ShowDialog();
}

All that’s left to do now is apply that setting to what the plugin actually does, so in the CreateContent code, you need to change it to the following:

using (frmMain form = new frmMain())
{
    DialogResult result = form.ShowDialog();
    if (result == DialogResult.OK)
    {                    
         content = form.NewContent;
         if (m_defaultsettings.DefaultBoldOption)
         {
               content =
string.Format("&lt;b&gt;{0}&lt;/b&gt;",    content);
         }
    }
    return result;
}

SmartContentSource Plugins

SmartContentSource plugins have a lot more about them than basic ContentSource plugins. The biggest difference about the two types is that ContentSource plugins insert HTML code into the blog entry and this cannot be changed or edited using the plugin at a later time. SmartContentSource plugins store as much information about what code it’s inserted as the developer wants it to, which allows the user to open that blog entry in Writer at a later time and edit that bit of their blog entry using the plugin without having to go through the whole process again.

Planning What’s Needed

With SmartContentSource plugins, it’s often best to plan out what you want internally for your plugin, like settings and which bits of the content you would like the end user to be able to edit if required. Knowing what parts you want them to edit at a later date will help you determine what settings you need for the plugin. These settings are held within the IProperties property of the ISmartContent object, which gets passed to the plugin from Live Writer.

While the examples given in this article are C#, the Live Writer API is .NET, so you’re not limited to just C#.

You will also want to work out where you would like the end user to edit the content, whether it’s a Form similar to the original Form they used or it’s done from the sidebar of Writer.

Listing 2 shows the code used in the example plugin’s PluginSettings class.

To write a SmartContentSource plugin, you must inherit from SmartContentSource, rather than ContentSource:

public class HiddenText : SmartContentSource

Override CreateContent Method

Like ContentSource plugins, you need to override the CreateContent method, but this time it’s slightly different:

public override DialogResult CreateContent
        (IWin32Window dialogOwner, 
        ISmartContent newContent)
{
     PluginSettings settings = 
        new PluginSettings(newContent.Properties);
     using (frmMain main = new frmMain(settings))
     {
          DialogResult result = main.ShowDialog();
          return result;
      }
}

This time, Writer does not pass you a string reference, which means whatever is highlighted in the editor gets ignored. Instead you’re given an ISmartContent object. ISmartContent objects contain everything about an instance of a plugin’s usage, which includes an IProperties property (as used when including settings). However, those properties are for that ISmartContent object and only that object-they are not global like you saw before.

From the code snippet, you can see that I have created a PluginSettings class, which takes in the IProperties property of the ISmartContent object; this then gets passed to the Form, which makes it easier for the Form to write the required settings to the PluginSettings class.

GeneratePublishHTML Method

In the main plugin class, there are three new methods that you can override: two of these are compulsory, the third is optional. The first of these is the GeneratePublishHTML method, which is the method where the HTML is what will be published to your actual blog. This method is compulsory; you have to have it there. This code snippet is a very small example of what is required:

public override string GeneratePublishHtml
       (ISmartContent content, 
       IPublishingContext publishingContext)
{
       PluginSettings settings = 
           new PluginSettings(content.Properties);
       return settings.FinalText;
}

In this snippet, the text that would actually be published comes from a setting that you would have written to using the CreateContent method.

The IPublishingContext object that gets passed holds information about the type of blog you’re writing to, which includes a GUID for the individual blog (this comes from Live Writer itself) and the Service Name, which is whatever the current publishing service is (e.g., “Windows Live Spaces”, “LiveJournal”, “WordPress.com”, etc.).

CreateEditor Method

This is the second compulsory method that you need to override. This method creates the sidebar that appears in Live Writer when a SmartContentSource object is selected in the Live Writer editor:

public override SmartContentEditor CreateEditor
            (ISmartContentEditorSite editorSite)
{
       return new ContextEditor();
}

This method is usually just as simple as that. The ContextEditor class that I call in this snippet will be explained later in the article.

GenerateEditorHTML

Sometimes in a plugin, what you want to display in the blog entry isn’t always something that you want to appear in the editor of Live Writer, JavaScript functions for example. So sometimes you might need to have something else appear in the actual Live Writer editing area. For this you need to override the GenerateEditorHTML method:

public override string GenerateEditorHtml
       (ISmartContent content, 
       IPublishingContext publishingContext)
{
       PluginSettings settings = 
           new PluginSettings(content.Properties);
       return settings.PlaceHolder;
}

As you can see, you get passed the same objects as the GeneratePublishHTML method, so you can use the same information for putting your HTML code into the editor.

This method is the optional method; your plugin does not require it. If this isn’t overridden, Live Writer will simply use what is in the GeneratePublishHTML method.

Listing 3 shows the full code for the example plugin’s main class.

Using Forms

Using Forms is mostly the same as when doing a basic ContentSource plugin, the main difference being that instead of creating the final HTML and passing that back, you just save the settings into the PluginSettings class, which will then be used in a later method in the plugin’s main class.

Listing 4 shows the full code for the main Form used in this example plugin.

The Sidebar (ContextEditor)

When you select a SmartContentSource within the Live Writer editor, the editor activates a sidebar on the right-hand side of the Live Writer window, as shown in Figure 8.

![Figure 8: The sidebar in Live Writer appears on the right-hand side when a SmartContentSource is selected.](https://codemag.com/Article/Image/0804092/Lovegrove_Figure 8.tif)

For this, you need to create a new User Control to your plugin project. I call mine ContextEditor. Now, rather than inherit the UserControl interface, it needs to inherit from the SmartContentEditor interface (make sure you’re using WindowsLive.Writer.API):

public partial class ContextEditor : SmartContentEditor

The constructor for the editor has to be like the following or your plugin could end up with some strange behavior:

PluginSettings m_settings;
ISmartContent m_content;
public ContextEditor()
{
       InitializeComponent();
       this.SelectedContentChanged += 
           new EventHandler(
               SelectedContentNowChanged);
}

Notice the EventHandler you have had to listen out for. This is important as this is what detects that you have selected a different instance of a SmartContentSource object (don’t forget, your user could have used your plugin to insert two separate objects in their blog entry).

The code for the EventHandler method is also important to get right:

void SelectedContentNowChanged
      (object sender, EventArgs e)
{
      m_content = SelectedContent;
      m_settings = 
         new PluginSettings(m_content.Properties);
      textBox1.Text = m_settings.PlaceHolder;
      textBox2.Text = m_settings.FinalText;
}

Important: You must not use SelectedContent.Properties if you wish to get the IProperties property of the ISmartContent-this will not work. This is why I assign SelectedContent to a local variable, and then use that local variable when passing the IProperties object to the PluginSettings class.

As a best practice, you should also use this EventHandler method to either set the current settings to what you have in your sidebar or call the method that applies these settings.

If the user makes a change to the content using the sidebar, nothing will change in the editor until you call the OnContentEdited() method. You can call this from a single button after all changes or each time anything is changed; it is up to you as the developer to decide when to update the editor.

Listing 5 shows you the code needed for the sample plugin’s ContextEditor.

Much Much More…

If you look through the Live Writer APIs, you will find so much more that you can do with your plugins, including an excellent screen-scraping method that will take a screenshot of a given URL and return an image. I would highly recommend taking a good look at the rest of the API documentation on MSDN (http://msdn2.microsoft.com/en-us/library/aa738852.aspx).

Listing 1: Full code for a very basic Live Writer plugin.

using System;
using System.Collections.Generic;
using System.Text;
using WindowsLive.Writer.Api;
using System.Windows.Forms;

namespace LiveWriterExample
{
    [WriterPlugin("d2c99304-8648-4696-9ef1-6a82a2d070c9", 
        "LiveWriterExamplePlugin",
        Description = "Makes highlighted text bold.",
        HasEditableOptions = true,
        ImagePath = "icon.gif",
        Name = "Bold Text Plugin",
        PublisherUrl = "<a href="http://www.liveside.net">http://www.liveside.net</a>")]
    [InsertableContentSource("Bold Text")]

    public class LiveWriterExamplePlugin : ContentSource
    {
        public override DialogResult CreateContent
            (IWin32Window dialogOwner, ref string content)
        {
            // If nothing is highlighted, content will be empty.
            // If this is the case, the plugin does nothing.
            if (!string.IsNullOrEmpty(content))
                content = string.Format("&lt;b&gt;{0}&lt;/b&gt;", content);
            
            return DialogResult.OK;
        }

    }
}

Listing 2: This is the code for the PluginSettings class in the SmartContentSource plugin.

using System;
using System.Collections.Generic;
using System.Text;
using WindowsLive.Writer.Api;

namespace EditPublish
{
    public class PluginSettings
    {
        IProperties m_properties;
        private const string PLACEHOLDER = "PLACEHOLDER";
        private const string ACTUALCODE = "ACTUALCODE";

        public PluginSettings(IProperties properties)
        {
            m_properties = properties;
        }

        public string PlaceHolder
        {
            get
            {
                return m_properties.GetString(PLACEHOLDER, "");
            }
            set
            {
                m_properties.SetString(PLACEHOLDER, value);
            }
        }

        public string FinalText
        {
            get
            {
                return m_properties.GetString(ACTUALCODE, "");
            }
            set
            {
                m_properties.SetString(ACTUALCODE, value);
            }
        }
    }
}

Listing 3: This is the code for the plugin class for the SmartContentSource plugin.

using System.Windows.Forms;
using WindowsLive.Writer.Api;

namespace EditPublish
{
    [WriterPlugin("18d43e01-4549-4fde-8ca6-c7b4b7385fac", 
        "Insert Placeholder",
        PublisherUrl = "<a href="http://scottisafooldev.spaces.live.com">http://scottisafooldev.spaces.live.com</a>",
        Description = 
        "Lets you put in a placeholder for the editor, "+
        "to be replaced with other text when published.", 
        ImagePath = "writer.png", 
        HasEditableOptions = false)]
    [InsertableContentSource("Placeholder")]

    public class HiddenText : SmartContentSource
    {
        public override DialogResult CreateContent
            (IWin32Window dialogOwner, 
            ISmartContent newContent)
        {
            PluginSettings settings = 
                new PluginSettings(newContent.Properties);
            using (frmMain main = new frmMain(settings))
            {
                DialogResult result = main.ShowDialog();
                return result;
            }
        }

        public override SmartContentEditor CreateEditor
            (ISmartContentEditorSite editorSite)
        {
            return new ContextEditor();
        }

        public override string GeneratePublishHtml
            (ISmartContent content, 
            IPublishingContext publishingContext)
        {
            PluginSettings settings = 
                new PluginSettings(content.Properties);
            return settings.FinalText;
        }

        public override string GenerateEditorHtml
            (ISmartContent content, 
            IPublishingContext publishingContext)
        {
            PluginSettings settings = 
                new PluginSettings(content.Properties);
            return settings.PlaceHolder;
        }
    }
}

Listing 4: The code for the main form in the SmartContentSource plugin.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace EditPublish
{
    public class frmMain : Form
    {
        PluginSettings m_settings;

        public frmMain(PluginSettings settings)
        {
            m_settings = settings;
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (textBox1.Text != "")
            {
                m_settings.PlaceHolder = textBox1.Text;
            }
            else
            {
                MessageBox.Show("You need to set a placeholder", 
                    "Big Fat Hairy Error", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
                return;
            }
            if (textBox2.Text != "")
            {
                m_settings.FinalText = textBox2.Text;
            }
            else
            {
                MessageBox.Show("Enter some text", 
                    "Big Fat Hairy Error", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
                return;
            }

            this.DialogResult = DialogResult.OK;
            this.Close();
        }

        /// &lt;summary&gt;
        /// Required designer variable.
        /// &lt;/summary&gt;
        private System.ComponentModel.IContainer components = null;

        /// &lt;summary&gt;
        /// Clean up any resources being used.
        /// &lt;/summary&gt;
        /// &lt;param name="disposing"&gt;true if managed resources should be disposed; otherwise, false.&lt;/param&gt;
        protected override void Dispose
            (bool disposing)
        {
            if(disposing &amp;&amp; (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// &lt;summary&gt;
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// &lt;/summary&gt;
        private void InitializeComponent()
        {
            System.ComponentModel.ComponentResourceManager resources = 
                new System.ComponentModel.ComponentResourceManager(typeof(frmMain));
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.textBox2 = new System.Windows.Forms.TextBox();
            this.label1 = new System.Windows.Forms.Label();
            this.label2 = new System.Windows.Forms.Label();
            this.label3 = new System.Windows.Forms.Label();
            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(13, 124);
            this.textBox1.Multiline = true;
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(407, 48);
            this.textBox1.TabIndex = 0;
            // 
            // textBox2
            // 
            this.textBox2.Location = new System.Drawing.Point(13, 203);
            this.textBox2.Multiline = true;
            this.textBox2.Name = "textBox2";
            this.textBox2.Size = new System.Drawing.Size(407, 56);
            this.textBox2.TabIndex = 1;
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(13, 105);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(168, 13);
            this.label1.TabIndex = 2;
            this.label1.Text = "Text to appear in the Writer editor:";
            // 
            // label2
            // 
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(13, 185);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(189, 13);
            this.label2.TabIndex = 3;
            this.label2.Text = "Text to appear in the actual blog entry:";
            // 
            // label3
            // 
            this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)
                (((System.Windows.Forms.AnchorStyles.Top | 
                System.Windows.Forms.AnchorStyles.Left) |
                System.Windows.Forms.AnchorStyles.Right)));
            this.label3.Location = new System.Drawing.Point(13, 9);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(407, 96);
            this.label3.TabIndex = 4;
            this.label3.Text = resources.GetString("label3.Text");
            // 
            // button1
            // 
            this.button1.DialogResult = System.Windows.Forms.DialogResult.Cancel;
            this.button1.Location = new System.Drawing.Point(264, 272);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 5;
            this.button1.Text = "Insert";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // button2
            // 
            this.button2.DialogResult = System.Windows.Forms.DialogResult.Cancel;
            this.button2.Location = new System.Drawing.Point(345, 272);
            this.button2.Name = "button2";
            this.button2.Size = new System.Drawing.Size(75, 23);
            this.button2.TabIndex = 6;
            this.button2.Text = "Cancel";
            this.button2.UseVisualStyleBackColor = true;
            // 
            // frmMain
            // 
            this.AcceptButton = this.button1;
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.CancelButton = this.button2;
            this.ClientSize = new System.Drawing.Size(432, 307);
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.label3);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.textBox2);
            this.Controls.Add(this.textBox1);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.MaximizeBox = false;
            this.MinimizeBox = false;
            this.Name = "frmMain";
            this.ShowIcon = false;
            this.ShowInTaskbar = false;
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
            this.Text = "Insert Placeholder Plugin";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.TextBox textBox2;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
    }
}

Listing 5: The code for the ContextEditor in the SmartContentSource plugin.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using WindowsLive.Writer.Api;

namespace EditPublish
{
    public class ContextEditor : SmartContentEditor
    {
        PluginSettings m_settings;
        ISmartContent m_content;
        public ContextEditor()
        {
            InitializeComponent();
            this.SelectedContentChanged += 
                new EventHandler(
                    SelectedContentNowChanged);
        }

        void SelectedContentNowChanged
            (object sender, EventArgs e)
        {
            m_content = SelectedContent;
            m_settings = 
                new PluginSettings(m_content.Properties);
            textBox1.Text = m_settings.PlaceHolder;
            textBox2.Text = m_settings.FinalText;
        }

        private void button1_Click
            (object sender, EventArgs e)
        {
            if (textBox1.Text != "")
            {
                m_settings.PlaceHolder = textBox1.Text;
            }
            else
            {
                MessageBox.Show("No placeholder", 
                    "Big Fat Hairy Error", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
                return;
            }
            if (textBox2.Text != "")
            {
                m_settings.FinalText = textBox2.Text;
            }
            else
            {
                MessageBox.Show("No text", 
                    "Big Fat Hairy Error", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Error);
                return;
            }
            OnContentEdited();
        }

        /// &lt;summary&gt; 
        /// Required designer variable.
        /// &lt;/summary&gt;
        private System.ComponentModel.IContainer components = null;

        /// &lt;summary&gt; 
        /// Clean up any resources being used.
        /// &lt;/summary&gt;
        /// &lt;param name="disposing"&gt;true if managed resources should be disposed; otherwise, false.&lt;/param&gt;
        protected override void Dispose(bool disposing)
        {
            if (disposing &amp;&amp; (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Component Designer generated code

        /// &lt;summary&gt; 
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// &lt;/summary&gt;
        private void InitializeComponent()
        {
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.label1 = new System.Windows.Forms.Label();
            this.textBox2 = new System.Windows.Forms.TextBox();
            this.label2 = new System.Windows.Forms.Label();
            this.button1 = new System.Windows.Forms.Button();
            this.label3 = new System.Windows.Forms.Label();
            this.SuspendLayout();
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(3, 91);
            this.textBox1.Multiline = true;
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(178, 58);
            this.textBox1.TabIndex = 0;
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(0, 73);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(168, 13);
            this.label1.TabIndex = 3;
            this.label1.Text = "Text to appear in the Writer editor:";
            // 
            // textBox2
            // 
            this.textBox2.Location = new System.Drawing.Point(3, 182);
            this.textBox2.Multiline = true;
            this.textBox2.Name = "textBox2";
            this.textBox2.Size = new System.Drawing.Size(178, 58);
            this.textBox2.TabIndex = 4;
            // 
            // label2
            // 
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(0, 164);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(171, 13);
            this.label2.TabIndex = 5;
            this.label2.Text = "Text to appear in actual blog entry:";
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(3, 255);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 6;
            this.button1.Text = "Apply";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // label3
            // 
            this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
                        | System.Windows.Forms.AnchorStyles.Right)));
            this.label3.BackColor = System.Drawing.Color.White;
            this.label3.Font = new System.Drawing.Font("Microsoft Sans Serif", 14F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.label3.Location = new System.Drawing.Point(0, 0);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(190, 32);
            this.label3.TabIndex = 7;
            this.label3.Text = "Insert Placeholder";
            // 
            // ContextEditor
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Controls.Add(this.label3);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.textBox2);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.textBox1);
            this.Name = "ContextEditor";
            this.Size = new System.Drawing.Size(190, 482);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.TextBox textBox2;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Label label3;
    }
}