japan.internet.comThe Internet & IT Network
RSS
  • ニュース
  • コラム
  • リサーチ
  • ヘッドライン
  • 特集
  • ブログ
  • プレスリリース
  • 専門チャンネル
  • イベント
  • ランキング
  • ニュースメール
2008年10月14日
文字サイズ文字サイズ小文字サイズ中文字サイズ大
デベロッパー コラム2008年8月8日 10:00
DevX
DevX japan.internet.com 編集部メールホームrss
米国 Jupitermedia が運営する、
企業向けアプリケーションの開発者向けの技術情報/サービスサイト。

Windows Live Writer用のプラグインを開発する

海外海外internet.com発の記事

はじめに

 Windows Live WriterはMicrosoftが無償で提供しているデスクトップブログオーサリングツールです。Live Writerを使用すると、オフライン状態でもデスクトップからブログエントリを書くことができます。Live WriterはMicrosoftの専用ブログサイト(Windows Live Spaces)をサポートするだけでなく、MetaWeblog APIまたはAtom Publishing Protocolに対応する任意のブログエンジンをサポートしており、たとえばWordpress、Community Server、LiveJournal、Bloggerなどのブログエンジンで使用できます。Live Writerではユーザーのブログスタイルをダウンロードし、それをテンプレートとして使用できるので、自分のブログのスタイルでブログエントリを書くことができます。

 ブログを始めたばかりの初心者でも、ブログ歴が長い人でも、Live Writerを使用すればブログの使い勝手は格段に向上します。しかも、Live Writerに含まれていない機能をプラグインとして追加することができます。

編集者注
 この記事はもともとCoDe Focus Magazineの「Windows Live」エディション(2008, Vol. 5, Issue 2)に掲載されたものですが、許可を得てここに転載しました。

必要な開発環境

 Live Writerのプラグインは.NETで開発します。.NET 1.1を使用することもできますが、Microsoftでは.NET 2.0以上を使用するように推奨しています。Visual Studio 2005以上をインストールしておく必要があります。Visual Studio 2005または2008のバージョンは問いません(Express Editionsも可)。また、Windows Live Writerをダウンロードすることも必要です。Windows Live Writerには、プラグインの基礎となる必須のAPI DLLが含まれています。

著者注
 プラグインの開発にはどの.NET言語を使用してもかまいませんが、本稿の例ではC#を使用しています。

プラグインを書く

 Visual Studioを開き、新しいクラスライブラリプロジェクトを作成して、プラグインとして意味のある名前を付けます。既定名の「class1.cs」を「plugin.cs」に変えましょう(これは必須ではありませんが、こうするとメインのプラグインクラスが一目でわかります)。

 次にWindows Live Writer APIの参照を追加する必要があります。[Add Reference]ウィンドウを開き、Windows Live Writerのインストールフォルダ(通常はC:\Program Files\Windows Live\Writer\)にあるWindowsLive.Writer.API.dllを選択します。この参照については、Copy Localプロパティをfalseに設定することをお勧めします(これにより、以前のバージョンのAPI DLLがLive WriterのPluginsフォルダにコピーされなくなります)。plugin.csを開き、次のusingステートメントを含めます。

using WindowsLive.Writer.Api;
 Windows Forms名前空間への参照も追加する必要があるので、System.Windows.Formsを参照として追加し、次のステートメントも含めます。

using System.Windows.Forms;
 次は、プラグインの属性を追加します。これらの属性は、プラグインの作成者、開発者のWebサイトへのリンク、説明、プラグインのオプションの有無、およびLive Writer UI内でプラグインのアイコンとして使用するイメージの場所をLive Writerに知らせるためのものです。この属性は名前空間の直後(メインクラスの前)に記述します。

 具体的なコードを次に示します。

[WriterPlugin ("d39bba2b-9522-49b1-8731-61030ccd6c95",
   "My First Plugin",
   Description = "This is my first plugin",
   HasEditableOptions = false,
   Name = "My First Plugin",
   PublisherUrl = "http://www.liveside.net")]
[InsertableContentSource ("Bold Text")]
 なお、GUIDはプラグインごとに一意のものであり、これを他のプラグインで再使用してはなりません。そのようなことをすると、Writerで問題が起こり、目的のプラグインをロードできない可能性があります。

 プラグインのアイコンを追加するには、次のコードを含めます。

ImagePath = "icon.gif",
 この属性の値に、使用したいアイコンのファイル名を指定します。ファイルの場所はプロジェクト内での相対パスで指定します。イメージ自体のサイズは20×18ピクセルでなければなりません。

 この後、プラグインクラスを作成する必要があります。このプラグインクラスはLive Writer APIから継承する必要があります。継承元として、ContentSourceとSmartContentSourceという2つの選択肢があります。ContentSource系プラグインは非常に基本的なものであり、テキストをブログエントリに挿入するだけで、そのテキストを再度変更することはできません。一方、SmartContentSource系プラグインでは、これよりもずっと多くのことができます。この最初のサンプルではContentSourceインタフェースを継承します。

public class LiveWriterExamplePlugin : ContentSource
 プラグインでテキストをどのように挿入するかを指定する必要があります。次の3つの方法があります。

  • CreateContent ― [Insert]メニューから挿入
  • CreateContentFromUrl ― URLをブログエントリ領域に貼り付けたとき、または[Blog This]アクションから挿入
  • CreateContentFromLiveClipboard ― LiveClipboardを使用(ただし、ドキュメントの問題からLiveClipboardのコンテンツがあまりないので、このメソッドは一部でしか使用されていない)
 最もよく使われている方法はCreateContentです。プラグインコードでは、これらのメソッドをすべてオーバーライドします。また、これらは相互排他的な関係ではなく、1つのプラグインで2種類以上を使用できます。

CreateContentメソッドをオーバーライドする

 下記のコードの、コンテンツが含まれる文字列参照(content)に注目してください。この参照については、次の2点に注意する必要があります。

  1. 編集ウィンドウ内で選択されているものがある場合は、それがこのコンテンツの初期値となります。選択されているテキストがない場合、contentは空になります。テキストが選択されている場合、contentは選択されているテキストのフルHTMLコードになります。
  2. プラグインは、このメソッドが終了する時点でcontentに入っているものをブログエントリに戻します。つまり、contentにはプラグインの機能に必要なHTMLフォーマットが含まれていなければなりません。
 挿入された基本コードは単なるプレースホルダなので削除します。この最初のサンプルでは、単にDialogResult.OKを返します。このサンプルは、選択されているテキストをボールドにする単純なプラグインとなります。したがって、CreateContentメソッドのコードもごくシンプルです。

public override DialogResult CreateContent
   (IWin32Window dialogOwner, 
   ref string content)
{
   if (!string.IsNullOrEmpty(content))
      content = string.Format(
      "<b>{0}</b>", content);
   return DialogResult.OK;
}
 このコードを使用すると、プラグインは選択されているテキストを受け取り、そのコンテンツをHTMLボールドタグで囲みます。リスト1を見ると、このLive Writerプラグインのコードがどれだけ簡単かがわかります。

リスト1 プラグインのコード
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 = "http://www.liveside.net")]

   [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("<b>{0}</b>", content);
         return DialogResult.OK;
      }
   }
}

コンパイルとデバッグ

 Live Writer用のプラグインのコンパイルとデバッグについては、いくつか知っておくべきコツがあります。なぜなら、これらのプラグインはDLLファイルなので、単独では実行できないからです。重要なコツは2つあり、ビルドされたDLLをLive Writerのプラグインディレクトリにコピーするときと、プラグインをデバッグする目的でLive Writerを起動するときに必要になります。

 図1は、完成したプラグインをLive Writerのプラグインディレクトリにコピーするために必要なコマンドを示しています。

XCOPY /D /Y /R "$(TargetPath)" "C:\Program Files\Windows 
   Live\Writer\Plugins\"
著者注
 Program FilesフォルダがWindowsの既定と違う場所にある場合は、システムの実際の構成に合わせて、これを変更する必要があります。あるいは、プロジェクトのoutputフォルダの設定をLive WriterのPluginsフォルダにするという方法もあります。
図1 プラグインをpluginsフォルダにコピーするために必要なコマンドライン
図1 プラグインをpluginsフォルダにコピーするために必要なコマンドライン
 図2は、プラグインをデバッグするために必要な設定を示しています。プラグインをデバッグするには、Windows Live Writerを外部アプリケーションとして起動する必要があります。これが機能するのは、設定を図1のように変更してある場合だけです。このようにしないと、プラグインはプラグインディレクトリにコピーされないので、Writerによって認識されません。

図2 プラグインのデバッグに必要な設定
図2 プラグインのデバッグに必要な設定
 Visual Studioでこの2つの設定を構成した後は、F5キーを押すとプロジェクトがビルドされ、プラグインがプラグインディレクトリにコピーされ、さらにデバッグモードでWindows Live Writerにロードされます。

 このプラグインをデバッグモードで使用したときのテキストの状態の変化を図3、図4、および図5に示します。図3は、通常のフォントスタイルで入力されたテキストです。図4はテキストを選択した状態であり、プラグインはこのテキストを受け取って使用することができます。図5は、Writerの[Insert]セクションでプラグイン名をクリックした結果を示しています。

図3 プラグイン実行前のテキスト(選択していない状態)
図3 プラグイン実行前のテキスト(選択していない状態)
図4 テキストを選択した状態
図4 テキストを選択した状態
図5 プラグインを実行すると選択テキストがボールドになる
図5 プラグインを実行すると選択テキストがボールドになる

フォームの使用

 Windows Formsをプラグインで使用することはよくあるので、プラグインコードから正しく呼び出す方法を知ることが大切です。フォームを使用することで、ユーザーがブログエントリに挿入するものを正確に制御できます。ビデオURLを挿入する、リストから顔文字を選択する、Amazonから書籍を検索するなど、フォームはさまざまな方法で使用できます。

 フォームはCreateContentメソッドから呼び出すことができます。

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;
   }
}
 上記のコードは、NewContentというプロパティを持つ単純なフォームを表示します。このプロパティはブログエントリに挿入する新しいコンテンツ値を返します。通常、プラグインが最終的に挿入するコードは、フォームのプロパティの値に基づきます。

 フォーム内の[Accept]ボタンのコードには次の行が必要です。

this.DialogResult = DialogResult.OK; 
 これにより、フォームが閉じたときに、その結果をチェックするif条件が真になります。

設定の使用

 プラグインの既定値をユーザーが設定できるようにしたい場合は、フォームと同様に、設定オブジェクトをプラグインに組み込みます。通常、ユーザーはLive Writerの[Options]領域でプラグインを選択し、[Options]ボタンを選択することで、プラグインの既定値を設定できます(図6を参照)。

図6 Live Writerプラグインのオプションへのアクセス方法
図6 Live Writerプラグインのオプションへのアクセス方法
 まず、プラグインにオプションフォームがあることをLive Writerに知らせなければなりません。これを行うには、プラグイン属性でHasEditableOptions=trueを設定する必要があります。

 設定クラスではWindowsLive.Writer.Apiも使用する必要があります。なぜなら、このオブジェクトには、プラグインが設定を読み書きするための媒体となるIPropertiesオブジェクトを渡す必要があるからです。設定クラスのコンストラクタは次のようになります。

IProperties m_properties;
public PluginSettings(IProperties properties)
{
   m_properties = properties;
}
 グローバル変数m_propertiesに注目してください。これはクラスのプロパティの中でIPropertiesを呼び出すために使われます。

 Live Writer APIは、String、Int、Float、Bool、Decimalという5種類の設定型を認識します。これらはすべて独自のGetメソッドとSetメソッドを持っており(GetString()SetString()など)、これらはIPropertiesクラスの一部として公開されます。そのため、Live Writer APIを使って保存される設定の単純なプロパティは次のようになります。

public bool DefaultBoldOption
{
   get { return m_properties.GetBoolean
      ("DEFAULTBOLD", true); }
   set { m_properties.SetBoolean
      ("DEFAULTBOLD", value); }
}
 それぞれのGetメソッドには属性名(この場合はDEFAULTBOLD)と既定値が必要です(プラグインの初回使用時にはこの既定値が使われます)。それぞれのSetメソッドは、Getメソッドと同じように属性名を必要とし、渡された値を使って単純に属性を設定します。プラグインに持たせたいすべての設定について、これを行う必要があります。

 設定クラス(PluginSettings)の用意ができたら、次はプラグインコード内から設定を初期化する必要があります。元のプラグインクラスに戻り、グローバル変数を追加します。

PluginSettings m_defaultsettings;
 その後、プラグイン設定を初期設定に割り当てるために、Initialize()メソッドをオーバーライドします。

public override void Initialize
   (IProperties pluginOptions)
{
base.Initialize(pluginOptions);
   m_defaultsettings = 
       new PluginSettings(pluginOptions);
}
 base.Initialize(pluginOptions)により、このプラグインのレジストリからプラグイン設定が取得され、IPropertiesクラスに格納されます。このクラスをPluginSettingsオブジェクトに引き渡すことで、プラグイン設定の読み書きが可能になります。

 さらに、オプションフォームを作成する必要があります。この例では、図7のようなフォームを作成しました。このフォームにPluginSettingsオブジェクトを渡す必要があります。

図7 サンプルプラグインのオプションフォーム。
図7 サンプルプラグインのオプションフォーム。
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;
}
 [Save]ボタンのClickイベントでは次のコードを実行します。

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();
}
 この例には設定が1つしかありませんが、プラグインの設定がどれだけ多くても原理は同じです。

 次はプラグインクラスに戻り、EditOptionsメソッドをオーバーライドして、オプションフォームを呼び出すようにする必要があります。

public override void EditOptions
   (IWin32Window dialogOwner)
{
   PluginSettings settings =
      new PluginSettings(this.Options);
   frmOptions op =
      new frmOptions(settings);
   op.ShowDialog();
}
 この後やるべきことは、その設定をプラグインアクションに反映させることだけです。CreateContentメソッドのコードを次のように変更します。

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

SmartContentSourceプラグイン

 SmartContentSourceプラグインは基本的なContentSourceプラグインよりも複雑です。最も大きな違いは、ContentSourceプラグインはブログエントリにHTMLコードを挿入するだけで、後からプラグインを変更または編集することはできないという点です。それに対し、SmartContentSourceプラグインは、挿入されるコードに関連する詳細情報を格納できるため、ユーザーはWriter内の作成済みブログエントリを開き、プラグインを使っていつでも編集できます。

必要なものを計画する

 SmartContentSourceプラグインを作成するときは、まずプラグインで内部的に必要になるもの、つまりプラグインの設定や、エンドユーザーによる編集を可能にしたいコンテンツ部分などについての計画を立てることをお勧めします。ユーザーによる再編集を可能にする部分をあらかじめ決めておけば、プラグインでどんな設定が必要になるか判断しやすくなります。これらの設定はISmartContentオブジェクトのIPropertiesプロパティ内に保持され、そのISmartContentオブジェクトがLive Writerからプラグインに渡されます。

 さらに、エンドユーザーにコンテンツの編集をどこで行わせるか、編集時にもコンテンツ作成時と同様のフォームを使用させるか、Writerサイドバーから編集させるかという点についても決める必要があります。

 リスト2は、この例のプラグインのPluginSettingsクラスの中で使われているコードを示しています。

 SmartContentSourceプラグインを書くには、ContentSourceではなく、SmartContentSourceから継承する必要があります。

public class HiddenText : SmartContentSource
リスト2 SmartContentSourceプラグインのPluginSettingsクラスのコード
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);
         }
      }
   }
}

CreateContentメソッドをオーバーライドする

 ContentSourceプラグインと同様、CreateContentメソッドをオーバーライドする必要がありますが、この場合のコードは少し異なります。

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;
   }
}
 この場合、Writerから文字列参照が渡されることはありません。つまり、エディタ内で何が選択されていようと無視されるということです。その代わり、ISmartContentオブジェクトが与えられます。ISmartContentオブジェクトには、プラグインを使用したときのインスタンスに関するあらゆるものが含まれます。たとえば設定を含めるときに使用するIPropertiesプロパティ値などもその1つです。しかし、このプロパティは、そのISmartContentオブジェクトのみに関するものであり、前の例のようにグローバルなものではありません。

 このコードでは、ISmartContentオブジェクトのIPropertiesプロパティを受け取るPluginSettingsオブジェクトを作成し、このオブジェクトをフォームに渡しています。これにより、必要な設定をフォームからPluginSettingsクラスに書き込むのが容易になります。

GeneratePublishHTMLメソッド

 メインプラグインクラスには、オーバーライドできる3つの新しいメソッドがあります(うちの2つは必須で、残り1つはオプション)。最初のメソッドはGeneratePublishHTMLで、ここで実際のブログに発行されるHTMLを生成します。このメソッドは必須です。GeneratePublishHTMLメソッドのごく簡単な例を次に示します。

public override string GeneratePublishHtml
   (ISmartContent content, 
   IPublishingContext publishingContext)
{
   PluginSettings settings = 
      new PluginSettings(content.Properties);
   return settings.FinalText;
}
 このコードでは、CreateContentメソッドを使って書き込んだ設定に基づくテキストを実際に発行します。

 IPublishingContextオブジェクト引数には、書き込み先のブログのタイプに関する情報が格納されます。これには各ブログのGUID(Live Writer自身が提供)と、現在の発行サービスの名前(Windows Live Spaces、LiveJournal、WordPress.comなど)が含まれます。

CreateEditorメソッド

 これは2番目の必須メソッドで、オーバーライドする必要があります。このメソッドは、Live Writerエディタ内でSmartContentSourceオブジェクトが選択されたときにLive Writerに表示されるサイドバーを作成します。

public override SmartContentEditor CreateEditor
   (ISmartContentEditorSite editorSite)
{
   return new ContextEditor();
}
 通常、このメソッドはご覧のとおりの単純なものです。このコードで呼び出しているContextEditorクラスについては、後ほど説明します。

GenerateEditorHTML

 プラグインによっては、ブログエントリ内に表示するものと、Live Writerのエディタ内に表示するものとが必ずしも一致しないことがあります(たとえばJavaScript関数を扱うプラグインの場合など)。このような場合、実際のLive Writer編集領域には何か別のものを表示しなければなりません。そのためには、GenerateEditorHTMLメソッドをオーバーライドする必要があります。

public override string GenerateEditorHtml
   (ISmartContent content, 
   IPublishingContext publishingContext)
{
   PluginSettings settings = 
      new PluginSettings(content.Properties);
   return settings.PlaceHolder;
}
 ご覧のように、GeneratePublishHTMLメソッドと同じオブジェクトが渡されるので、同じ情報を使用してHTMLコードをエディタに入れることができます。

 このメソッドはオプションのメソッドであり、プラグインに必須のものではありません。これをオーバーライドしなければ、Live WriterはGeneratePublishHTMLメソッド内にあるものを単純に使用します。

 リスト3は、この例のプラグインのメインクラスのフルコードを示しています。

リスト3 SmartContentSourceプラグインのプラグインクラスのコード
using System.Windows.Forms;
using WindowsLive.Writer.Api;
namespace EditPublish
{
   [WriterPlugin("18d43e01-4549-4fde-8ca6-c7b4b7385fac", 
   "Insert Placeholder",
   PublisherUrl = "http://scottisafooldev.spaces.live.com",
   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;
      }
   }
}

SmartContentSourceプラグインでフォームを使用する

 フォームを使用するのは、基本的なContentSourceプラグインを作成したときに説明したプロセスに似ています。主な違いは、最終的なHTMLを作成して返す代わりに、設定をPluginSettingsクラスに保存し、このクラスを後からプラグインのメインクラスのメソッドの中で使用するという点です。

 リスト4は、この例のプラグインで使用するメインフォームのフルコードを示しています。

リスト4 SmartContentSourceプラグインのメインフォームのコード
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();
      }

      /// <summary>
      /// Required designer variable.
      /// </summary>
      private System.ComponentModel.IContainer components = null;

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

#region Windows Form Designer generated code
      /// <summary>
      /// Required method for Designer support - do not modify
      /// the contents of this method with the code editor.
      /// </summary>
      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;
   }
}

サイドバー(ContextEditor)

 Live Writerのエディタ内でSmartContentSourceを選択すると、Live Writerウィンドウの右側にあるサイドバーがアクティブになります(図8を参照)。

図8 ユーザーがSmartContentSourceを選択すると右側にLive Writerサイドバー表示される
図8 ユーザーがSmartContentSourceを選択すると右側にLive Writerサイドバー表示される
 このようなサイドバーを表示するためには、プラグインプロジェクト内に新しいユーザーコントロールを作成する必要があります。この例ではContextEditorと名付けています。UserControlインタフェースを継承するのではなく、SmartContentEditorインタフェースから継承する必要があります(WindowsLive.Writer.APIを使用することを忘れないでください)。

public partial class ContextEditor : 
   SmartContentEditor
 エディタのコンストラクタは次のようなものでなければなりません。そうでないと、プラグインがおかしな動作をする可能性があります。

PluginSettings m_settings;
ISmartContent m_content;
public ContextEditor()
{
   InitializeComponent();
   this.SelectedContentChanged += 
      new EventHandler(
      SelectedContentNowChanged);
}
 このコードでEventHandlerをどのようにリスンしているかに注目してください。これにより、別のSmartContentSourceオブジェクトインスタンスが選択されたことを検出することができます(ユーザーがこのプラグインを使用して2つの異なるオブジェクトをブログエントリに挿入する可能性があることを忘れないでください)。

 イベントハンドラメソッドのコードも重要です。

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;
}
重要
 ISmartContentのIPropertiesプロパティを取得したい場合は、SelectedContent.Propertiesを使用しないでください(正しく機能しません)。代わりに、SelectedContentをローカル変数に割り当て、IPropertiesオブジェクトをPluginSettingsクラスに渡すときに、そのローカル変数を使用してください。
 ベストプラクティスとしては、このイベントハンドラメソッドを使用して、現在の設定をサイドバー内の設定に反映させるか、これらの設定を反映させるメソッドを呼び出すようにします。

 ユーザーがサイドバーを使用してコンテンツを変更しても、OnContentEdited()メソッドを呼び出すまでエディタ内では何も変化しません。このメソッドは、すべての変更が行われた後に1つのボタンから呼び出すことも、変更が行われるたびに呼び出すこともできます。エディタを更新するタイミングは開発者に任されています。

 リスト5に、サンプルプラグインのContextEditorに必要なコードを示します。

リスト5 SmartContentSourceプラグインのContextEditorのコード
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();
   }

   /// <summary> 
   /// Required designer variable.
   /// </summary>
   private System.ComponentModel.IContainer components = null;
   /// <summary> 
   /// Clean up any resources being used.
   /// </summary>
   /// <param name="disposing">true if managed resources should be 
   /// disposed; otherwise, false.</param>
   protected override void Dispose(bool disposing)
   {
      if (disposing && (components != null))
      {
         components.Dispose();
      }
      base.Dispose(disposing);
   }

#region Component Designer generated code
   /// <summary> 
   /// Required method for Designer support - do not modify 
   /// the contents of this method with the code editor.
   /// </summary>

   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;
   }
}

開発のヒント

 Live Writer APIをよく調べると、プラグインではもっと多くの、高度な機能を実現できることに気づくでしょう。たとえば、指定したURLのスクリーンショットを撮ってイメージを返す、優れたスクリーンスクレーピングメソッドなどがあります。MSDNのAPIドキュメントにひととおり目を通すことを強くお勧めします。

著者紹介

Scott Lovegrove(Scott Lovegrove)
Windows Live MVP for Microsoftであり、Windows Live Writerだけでなく、Windows Live全般に深く関係している。Windows Live専用のコミュニティWebサイト「LiveSide.net」の主要な寄稿者の1人であり、Windows Live上での開発について広範囲にわたって記事を執筆。人気のあるWindows Live Writerプラグインをいくつも書いており、Live Writerサポートフォーラムでは一般サポートと開発者サポートの両方で頻繁に名前が見られる。
海外のインターネットコムアメリカ韓国ドイツトルコ
Copyright 2008 Jupitermedia Corporation All Rights Reserved.http://www.internet.com/