紫だちたる雲の細くたなびきたる blog

春はあけぼの(をかし)

C#でスクリーンセーバーを作ってみた

サンプル画像

Visual C# 2005 Express Editon を使うと、簡単に作れるらしい、というので作ってみた。

  1. 「ファイル」→「新しいプロジェクト」でダイアログを開き、その中から「スクリーンセーバースタートキット」を選択して[OK]クリック
  2. プロジェクトが読み込まれたら[F5]キーを押してビルドすると、できたスクリーンセーバーが立ち上がる。

以上(爆)

 結構洒落たデザインのスクリーンセーバーですこと。何なに?「RSSフィードからのニュース記事を表示する」スクリーンセーバーだって?確かに画面中央左側の白いところにリストがあり、そこで選択されたニュースの概略が右側に表示されている。
 まずは、色々なRSSを表示させるところから始めて見た。

RSS1.0未対応

 Mozilla FirefoxのブラウザにデフォルトでついてくるRSSフィード「最新ニュース」。これはどうだろうか。

  1. 「最新ニュース」で右クリック、「プロパティ」を選択。
  2. RSSフィードURL」の中の文字列をコピー。
  3. VisualC#で、「プロジェクト」→「ScreenSaver1のプロパティ」を選択。
  4. デバッグタグのコマンドライン引数に「/c」と入力
  5. [F5]キーを押下
  6. スクリーンセーバーの設定画面が出てくるので、「RSSフィード」の欄に先ほどコピーしたURLを貼り付け
  7. 設定画面の[検証]ボタンを押下。正しければ「有効なRSSフィードです」と出てくるはずだが・・・

げっ!「有効なRSSフィードではありません」と出やがった。

じゃあ、はてなは?

  1. 日記ページを表示させる
  2. タイトルの横にある[RSS]ボタンで右クリック、「リンク先のURLをコピー」を選択してコピー。

以下先ほどと同じ手順で、自分の日記のRSSフィードを貼り付けて検証してみた。
やっぱり「有効なRSSフィードではありません」。
なんで?

設定画面をよ〜く見てみると[RSS2.0 URI]と書いてある。
そっかRSSのバージョンが違うんだ!

というわけで、第一の改造はRSS1.0対応に決定。

RSS1.0対応

RSS1.0はRDFという形式のXMLで記述されている。(RSS2.0は普通のXMLだ)
rdfという名前空間が定義されているので、それを読み込ませるのに一苦労した。
苦労した点を列挙。

  • XmlNode.SelectSingleNodeにそのまま"rdf:RDF"って渡したら落ちるんだけど?
  • XmlNode.SelectSingleNode(string, XmlNamespaceManager)を使えばいいのはわかったんだけど、XmlNamespaceManagerに名前空間追加してるのにノードを引っ張ってきてくれない!
  • XmlNode.SelectSingleNode("//rdf:RDF", XmlNamespaceManager)でやっと引っ張ってこれた!
  • channelっていう子ノードに対してchannelNode.SelectSingleNode("title")ってやってもタイトルが取れない?
  • 面倒くさくなったから、以下のようにして済ます。

foreach (XmlNode itemNode in channelNode.ChildNodes)
{
  if (itemNode.Name == "title")
  {
      titleNode = itemNode;
  }
}

と、試行錯誤して、できました。

背景画像を「はてなフォトライフ」対応に

 設定画面を見ると、背景画像をフォルダ指定で選べるらしい。何秒か毎に背景画像が切り替わっていくので、それをフォルダごと指定できるということ。
 はてなフォトライフデスクトップだとRSSフィードを指定するとそのRSSフィードに含まれる写真を延々とデスクトップに表示してくれる。これがなかなか面白いので、このスクリーンセーバーでも「はてなフォトライフ」が読めるようにしてやろう。

  • フォトライフRSSフィードXMLファイルを取得。IEで開いて検証。
  • RSS1.0で書かれていたので、先ほどのRSS解析クラスをそのまま流用できた。
  • hatena:imageurlというノードに画像のURLが記述されていたので、これを用いる事にした。

あっという間に実装完了。何て楽ちん。オブジェクト指向万歳。C#万歳。
試しにタグ「しなもん」で検索してみた。タグ「しなもん」に関連する写真、画像 - はてなフォトライフ
RSSのリンク先をコピーして設定画面の背景画像フォルダ欄に貼り付けてみる。
スクリーンセーバーを起動すると画像をはてなにダウンロードしに行くので結構起動に時間が掛かる。が、しなもんが背景で起動した!おぉっ!完璧!オブジェクト指向万歳。C#万歳。
WindowsプログラミングならC#の方がJavaよりずっと楽ちんだわ。感覚的にはJavaで書かれたVisualBasicみたいなイメージ。

画像のタイトルがみたい

 フォトライフデスクトップスクリーンセーバーに持っていた不満。それは画像のタイトルが見えないこと。じゃあ自作スクリーンセーバーにはRSSフィードのタイトルと各画像のタイトルを表示しちゃいましょ(^o^)
先ほどのRSS解析クラスに各画像のタイトルはすでに読み込んでいたのだが、フィード全体のタイトルがない。ので、取得するように修正。画像全体を書き換えるタイミング(OnPaintメソッド)で表示させるようにしてみた。
おぉっ!完璧!オブジェ(以下略)

Subversion導入

 なんか色々手を加えているので、バージョン管理がしたくなってきた。フリーのバージョン管理ツールの代表格といえばCVS。仕事場でもよく使っている(昔はMicrosoft Visual Source Safeだったんだけどなぁ・・・)。しかぁ〜し!仕事場と同じじゃつまらない。一応自己研鑽のつもりもあるので、あえてSubversionを用いることにした。導入についてはbluegate.org - このウェブサイトは販売用です! -&nbspbluegate リソースおよび情報を参考にした。
☆導入で苦労した点☆

  • import時のURL。Windowsだと"C:\Program Files\Subversion"などドライブがあったりパスにスペースが含まれたり全角文字が含まれたりするので、「file://」形式でのURL指定がうまく行かない。上記リンクの下の方に書いてあったのだが「"file:///C:/Program Files/(以下略)"」と指定してやるとうまく行くようだ。
  • checkoutがうまく行かない。上記リンクの「クイックスタート」の項にあるURLではどうしてもうまくいかないのだ。

svn checkout file:///path/to/repos/myproject/trunk myproject

ではなく
svn checkout file:///path/to/repos/ myproject
とする必要がある。ただ、branches, tagsなど他のディレクトリまで取ってきてしまうのが困りものだけど。

RSSのリストを消せるようにしてみる

さて、動かしていると、かわいいしなもんRSSのリストに隠れてしまう(半透明だから一応見えてるんだけどね)のが気になった。ええぃ、設定画面にオプション追加して、表示非表示の設定をできるようにしてやるわっ!
 VBみたいな感覚でチェックボックスを画面に追加。プロパティファイルとのやりとりも他のを参考にして一発でできた。
実行してみる。ちゃんと消えてる。
おぉっ!完璧!オブジェ(以下略)

背景の切替時間・リストの切替時間

 さきほど太字で書いたのだが、このスクリーンセーバー何秒か毎に背景が切り替わる。この何秒っていうのが設定できない。設定できるようにしちゃる!ついでにRSSリストの切替時間も設定できる方がいいよね。
 設定画面にテキストボックスを追加、数字とバックスペース以外は入力できないようにOnKeyPressイベントを書き、テキストボックスの中身を数値にできない状態で[OK]とか[適用]ボタンを押されてもエラーメッセージを出して設定値は更新できないようにした。
いろいろボタンを押してテスト。完璧です。オブ(以下略)

リファクタリング

 さて、気になるのはRSS解析クラス。RSS1.0と2.0で共通してる部分もいっぱいあるし、ていうかtitleとかdescriptionとか同じだし、なんか工夫はできないものか。
 オブジェクト指向ですから!抽象クラスを抽出して継承すればできあがりですから!
とは言え、かなり変更を加えるので、きちんと動作確認がしたい。そこで!テスティングフレームワークの導入!

  1. NUnitをダウンロードしてきてインストールした。
  2. VisualC#で「プロジェクト」→「参照の追加」→「.NET」タブに表示されている[nunit.framework]を選択して[OK]ボタン押下

(これでNUnitがプロジェクトに読み込まれるようになる)

  1. RSS1.0とRSS2.0を読み込んでちゃんとタイトルなどを取得できるテストを書く。
  2. NUnitを実行。バーが緑で全部OKになったことを確認!

おぉっ!完璧!
で、RssFeedクラスとRssChannelクラスを継承させてそれぞれRssFeed1_0,RssFeed2_0クラス、RssChannel1_0,RssFeed2_0クラスを作り、RSSの解析はそれぞれサブクラスに任せるようにした。
その後、再度NUnit実行。バーはグリーンに。
テスト成功!=リファクタリング成功!完璧だね!

断念したこと 〜プレビュー画面に表示 〜

 起動オプションを/cとしたら設定画面、/sとするとスクリーンセーバーが起動する。あと、/pとすると、Windowsスクリーンセーバーの設定画面でプレビュー表示出来るはずなのだが・・・色々ググったりして調べたのですが、C#でのプレビュー画面の作り方がありまっせん!困ったなぁ・・・。
 苦肉の策。VisualBasicスクリーンセーバーを作る方法を紹介しているサイトを見る。あった!!
なになに、コマンドライン引数には

/p 11111

って感じで2番目にプレビュー画面のウィンドウハンドルが渡されてくるので、スクリーンセーバーをWindowsAPI関数でそのプレビュー画面の子ウィンドウにしてやる・・・。む、難しそうだな・・・。


C#でのWindowsAPIの使い方☆

using System.Runtime.InteropServices;

naming space RssScreenSaver
{
  public class Win32API
  {
    // Win32API GetWindowLong
    // LONG GetWindowLong(HWND hWnd, int nIndex)
    [DllImport("User32.Dll")]
    public static extern Int32 GetWindowLong(IntPtr hWnd, int nIndex);

    // Win32API GetWindowRect
    // BOOL GetWindowRect(HWND hWnd, LPRECT lpRect)
    [DllImport("User32.Dll")]
    public static extern Int32 private static extern int GetWindowRect(IntPtr hWnd, out RECT rect);
  }
}

いや、何が苦労したって、GetWindowRect関数。APIではLPRECTというポインタで書かれているから、C#でどう書けばいいのか解らなくて。最初はusing unsafe ブロック使って無理矢理ポインタ使ったりしたんだけどメモリ違反で落ちまくるし。
で、C# GetWindowRectというキーワードでググったら上記のような書き方が出てきました。

で、スクリーンセーバーの設定画面のプレビュー画面の大きさに表示することはできたんだけど、いざ、スクリーンセーバースクリーンセーバーのプレビュー画面の子ウィンドウにしようとすると、表示されない・・・(_ _;)
子ウィンドウにしなかったら、今度はプレビュー画面をドラッグして移動しても元の場所に表示されたままだし、プレビュー画面消しても画面残ってるし・・・・。で、子ウィンドウにしたら表示されないし・・・だめだこりゃ。

というわけで、いい方法をご存じの方いらっしゃったら教えてください。