「プログラミング」タグアーカイブ

DMMTool(仮名) for Windows開発状況その4


いよいよブックマーク機能を実装しました。

今回は、ブックマーク機能を実装するにあたって利用したMVCという設計手法とXBELという規格の話が中心です。

まずはスクリーンショットをご覧下さい。一般的なウェブブラウザに搭載されているような階層構造を持ったブックマークがウィンドウ左側に表示されているのがわかると思います。

bookmark.png

このブックマーク機能の仕組みですが、MVC(Model-View-Controller)という設計手法に則って設計されています。なぜこのような手法にしたかというと複数のウィンドウ間でブックマーク表示の同期を取らなければならないからです。

このツールは一般的なウェブブラウザ同様、複数のウィンドウを開いて作業を行えるようになっているため、あるウィンドウでブックマークの変更を行った場合、他に開いているウィンドウにもその内容が即座に反映させなくてはなりません。

MVCを使わない場合・・・

まず、標準のTreeViewだけで上記の要件を満たしたブックマーク機能を実装することを考えてみます。

TreeViewの表示内容を弄るにはNodesプロパティを使います。ここにTreeNodeオブジェクトを追加したり削除したりすることによりTreeViewの表示内容を弄ることができます。TreeNodeは名前の通りツリー構造になっていて一つ以上の子TreeNodeを追加することができ、これによりデータの階層的な表示を実現できます。つまり、TreeViewは表示に関する機能と、表示するデータを管理する機能の二つを両方持っているということになります。従ってここにブックマークのデータを入れてやればTreeViewを使ってブックマークデータの編集や表示が行えるというわけです。

ところが、複数のウィンドウが開かれていて、それぞれのウィンドウが同じブックマークの内容を表示したい場合はどうでしょう。当然それぞれのウィンドウがTreeViewを持つことになりますがこの場合、変更があったTreeViewの内容を全ての他のTreeViewにコピーして回らなければなりません。

また、あるウィンドウでブックマークの追加や削除が行われたことをどうやって他のウィンドウが知れば良いのでしょうか。また、他のウィンドウで行われたブックマークの変更を自分のウィンドウに反映させるにはどうすれば良いでしょうか。さらに、もっと多くのウィンドウが開かれたり閉じられたりをしていた場合のTreeViewの管理はどうなるでしょう。

おそらくはデリゲート等を用いてウィンドウ間の通知機構を実装する方法にたどりつきますが、考えただけで面倒です。

このような動作をより簡単に実現させるためには、まず表示部分とデータの部分を独立させなければなりません。その上でデータの変更を表示部分に通知するメカニズムと、表示部分に対して行われた操作によって、データの変更を要求するメカニズムを実装する必要があります。これがいわゆるModel View Controllerという設計手法です。

前述の通り、標準のTreeViewだけで上記の要件を満たしたブックマーク機能を実装するのは非常に面倒です。
なぜならWindows.FormsのTreeViewはView(TreeView)とModel(Nodes)が一体になっているため、データと表示部分の分離がされていないからです。

MVCを使うと・・・

 そこで、TreeViewには単に表示に専念するだけのViewになってもらい、ブックマークのデータを保持するModelを独自に作ります。

 これでViewとModelの分離は行えました。こうするともはやTreeViewのNodesプロパティはブックマークのデータではなく、単なる表示内容のデータになりますから、ブックマークの編集が行われる度にこのデータを他のTreeViewにコピーして回るという面倒なことはしなくても良くなります。

 ここでは、ユーザーがブックマークアイテムをあるフォルダへドラッグして移動したケースを例に、MVCがどのように働くかを説明します。

MVC.png

ViewとModelの関係

Modelにはブックマークデータを管理するツリー構造の他に、ブックマークの追加や削除、移動、コピー、名前の変更、フォルダの作成などを行うメソッドを作っておきます。.NET Frameworkには(頻繁に利用されるデータ構造であるにも関わらず)データのツリー構造を管理するクラスが用意されていません。そこでまずはツリー構造を管理するクラスの自作から始めました。

対してViewはWindows標準のTreeViewを用いますが、データの表示に専念させ、データの管理は全く行わないようにします。また、ドラッグ&ドロップやクリック、アイテムの削除の操作は全てコントローラに通知されるようにします。

Controllerの働き

ControllerはユーザーがViewで行った操作に従って、Modelに適切なメッセージを送り、データの変更要求を行う役目を持っています。

上の例ではユーザーがブックマークをドラッグして移動しています。するとこの操作はControllerに通知され、Controllerはその通知に従い実際にモデルに対してデータの変更を要求します。ちなみにControllerはModelのデータを直接いじりません。データの操作を行うのはあくまでModelになります。

ModelからViewへの通知

 さて、実際にModelが変更されるとModelからViewへ「Modelの内容が変更されたから表示を更新してください」といった通知が行われます。通知といっても実際にはメソッド呼び出しだったりイベント発生だったりするだけです。ViewはModelの内容を視覚的に認識できる形で画面に表示します。今回の例では、ツリー構造を持ったModelの内容を元に、TreeViewのアイテムの再構築を行っています。

この時大事なことは単一のModelに対してViewは複数存在することができる、ということです。つまり、画面に複数のウィンドウがあり、そこへそれぞれのViewが貼り付けられていたとしても、Modelから各Viewに対して適切に通知が行われれば、あるウィンドウで行ったModelの変更が即座に他のウィンドウのViewにも反映される、ということになります。


複数のViewが存在する場合でも

multipleviews.png

画面に複数のドキュメントウィンドウが開かれている等、複数のViewが存在する状況でも、単にModelはそれぞれのViewに対して同時にModel更新の通知を送ってやれば、各Viewの表示内容が一斉に更新されるわけです。今回ブックマーク機能の実装にあたりMVCを利用した最大の理由が「場合によっては画面に複数のViewが存在する」という理由です。


ドラッグ&ドロップの実装

今回一番時間がかかったのが、ブックマークのドラッグ&ドロップによる編集です。
TreeViewにはドラッグ&ドロップをサポートする機能が標準で搭載されていますが、若干機能不足です。

たとえば標準のドラッグ&ドロップではTreeNodeを他のTreeNodeの上にドラッグし、ドラッグされたTreeNodeの子とすることは簡単にできますが、あるTreeNodeを二つのTreeNodeの間にドラッグして任意の位置にアイテムの挿入を行う、といった機能はありませんから、こういった機能は自前で実装する必要があります。

drag_drop.png

 また、ドラッグ&ドロップにも下記のようなルールを設ける必要があります。

  1. あるフォルダをそのフォルダ自身が内包しているフォルダには移動できないようにする。Windowsのファイルシステムを例にあげて説明すると、C:hogeというフォルダと、C:hogehageという二つのフォルダがあった場合、前者のフォルダを後者のフォルダの内部に移動させる操作はできない、といった具合です。
  2. あるフォルダをそのフォルダ自身の子とすることもできない。
  3. ブックマークの項目をフォルダへ入れて子とすることはできるが、ブックマークの項目をブックマークの項目の子とすることはできない。
  4. 同様にフォルダをブックマーク項目に入れて子とすることもできない。

これらの機能を一つ一つ検証しながら実装していたのですが、あまりにもややこしすぎて時間がかかってしまいました。

ブックマークの保存形式

ブックマークの保存は、ブックマーク記述言語「XBEL」形式で行います。XBELとはXML Bookmarks Exchange Languageの略で、XMLを利用したブックマークの相互利用のための規格です。XML形式ですので、ブックマークの保存の際にはブックマークのツリー構造を再帰的に辿ってXmlDocumentを構築した後、ファイルに出力しています。XBEL形式に関してはこのページを参考にしました。

この形式を利用する利点は、様々なブラウザのブックマークとの相互変換が可能である事です。自作のアプリとはいえ、独自のデータ形式を使ってしまっては後々困ることになりますから、オープンな規格はどんどん利用すべきだと思います。

bookmark_xml.png

さて、次回はWeb版、デスクトップアプリ版両方のDMMアフィリエイトリンク作成ツールに搭載されているキャッシュシステムについて書いてみようかと思います。

DMMTool(仮名) for Windows開発状況その3


 今回のエントリもまたまた開発状況報告になります。
1.履歴機能 & UIの若干の改良
 今回は、履歴を実装してみました。(お気に入りはまだです)
また、ウィンドウのタイトルバーに現在開いている商品のタイトルがちゃんと出るようになりました。 なお、今回履歴を実装するに当たってはじめて「LINQ」という技術を勉強して使ってみたのですが、あまりの便利さにケツが浮きました! これについでは後日別のエントリで改めて書こうと思います。

history.png

2.ドラッグ&ドロップで簡単に画像をダウンロード
 DMMのアフィリエイトは、パッケージ画像の他にも宣伝素材としてサンプル画像の利用が許可されているカテゴリがあります。(カテゴリによっては全く許可されていないものもあるので注意)
 従来、サンプル画像(特に大サイズの物)を利用しようとする場合、まずブラウザで該当の商品ページを開き、次にサンプル画像をクリックし、大きいサイズの画像を表示させてから右クリック、もしくはドラッグ&ドロップで一つ一つ保存、というやり方を行っていた方が殆どだと思います。
 しかし今回ドラッグ&ドロップをサポートしたことにより、画像一覧から画像をエクスプローラにドラッグするだけで一発で保存できます。複数の画像を一気にまとめて保存することも可能です。
dragdrop.png
 保存した画像を開いたところ。ダウンロードとはいえ、実際にはすでに取得済みのキャッシュをエクスプローラへとコピーしているだけなので、複数の画像であっても取得速度は圧倒的に速いです。
samplepic.png
デモ動画

 実際にいくつかの商品情報を取得し、そのサンプル画像をダウンロードする様子を動画でご紹介します。雰囲気だけでも伝われば光栄です。

DMMTool(仮名) for Windows開発状況その2


今回もまた、プログラミングに関する記事です。大変申し訳ございません。

前回のエントリを書いてから、空き時間を見つけては地道に作業を続けておりました。

前回からの改善点は

  • ネットワークアクセスから何から、ありとあらゆる物をマルチスレッド化した。
  • エラー処理の強化

徹底したマルチスレッド化

 前回のバージョンではURLボックスにURLを入力して情報取得ボタンを押すと、すべての情報の取得が完了するまでウィンドウがフリーズした状態になっていました。画像が多い場合、全情報の取得には10秒以上かかる場合もあり、その間ユーザーは何の操作もできずに待たねばならなかったのです。これはすべての処理をシングルスレッドで行っていたため、情報取得が完了するまでメインのGUIスレッドが止まるからです。

 ところが、たくさんある商品の情報を次々と取得したい場合、ある商品のURLを入力し、表示が完了するまで待ち、次の商品のURLを入力し、また待ち、なんてことはかったるくてやってられません。

 そこで、今回はHTMLのスクレイピングから画像の取得まで、すべてをスレッド化し、個別の情報が取得でき次第次々とイベントが発生するように改良しました。発生したイベントはメインのUIスレッドに伝えられ、その度に画面の更新が行われます。この状態でもメインのイベントループが動き続けているため、他の操作を行うことができます。

 おかげで、下の画像のようにたくさんのウィンドウを開いて次々と情報取得を行っても非常にぬるぬるとなめらかに動くようになりました。

dmmtool_overview.png

エラー処理の強化

 シングルスレッドで処理を行う場合、極端な話必要な処理を関数として書けるので、どこかでエラーが発生すればその時点で関数を抜けて処理を中断し、エラーが発生したことをユーザーに告知するだけのシンプルなプログラムで済みます。

 ところがマルチスレッドの場合には話がすこしややこしくなります。

 たとえば平行していくつものスレッドが動いている場合、あるスレッドは正常に完了したが、あるスレッドは途中でエラーが起きた、という状況が起きます。具体的には、複数の画像データをマルチスレッドで平行してダウンロードしていたがある画像のダウンロードだけは途中で失敗した、等のケースです。この場合、あるスレッドで発生したエラーをどのようにメインスレッドに伝えるかが問題となります。.NETではあるスレッドで発生した例外を他のスレッドでキャッチすることができないので、例外が発生した場合にはイベントを発生させてメインスレッドに例外を伝えるということをやっています。

 また、エラーの告知方法も少し工夫しました。

 このツールでは、ユーザーが商品情報の取得を開始して比較的早期に発生したエラー(URLの入力ミスやパッケージ画像が取得できなかった等)はダイアログを表示してその時点で処理が中断されます。

error1.png

 対して、すでに処理を開始してからある程度の時間が経過しており、もうそろそろ処理が終るだろうと思われる頃合いに発生したエラー(サンプル画像を取得中に発生したエラーなど)に関してはダイアログで告知せずにエラーが発生した事を示すアイコンのみを表示し、そのまま続行可能な処理を続けます。これには二つの理由があります。
  • ユーザーを完了まで待たせるだけ待たせておいてエラーで処理が中断されてはフラストレーションになるから。
  • 取得した画像全てがユーザーが必要としている画像となるケースは希であるから、リロードを行うかどうかはユーザーの判断に任せることにした。
error2.png
現時点で搭載している機能

 まだアフィリエイトリンクを作成する機能は実装されていませんが、以下の機能を搭載しています。
1.商品のタイトル、説明、出演者などの情報をテキストとして取得。
2.商品のパッケージ画像(大、小)を取得
feature1.png
3.商品のサンプル画像(大、小、ただしサンプル画像がある場合のみ)を取得
feature2.png今後搭載したい機能
  • お気に入り、履歴など
  • タブを用いて一枚のウィンドウで複数の商品情報を開けるようにしたい
  • 複数の商品のアフィリエイトリンクを効率よく作成できる手段の提供
なお、完成はまだまだ先になると思われますので、今すぐにでもDMMのアフィリエイトリンクを手軽に作成したい!という方は、当サイトで既に公開しておりますWebサービス版をご利用ください。

DMMアフィリエイトリンク作成ツールのWindows版の開発を開始しました。


今日は久々にプログラミングの話題です。

当ウェブサイトで公開しております「DMMアフィリエイトのリンク作成支援ツール」はいわゆるWebブラウザを使ってサービスにアクセスするWebアプリケーションの形になっておりますが、Windows版のツールの開発も開始しました。

まだまだ完成にはほど遠く、公開できる状態にはなっておりませんが、DMMサイトからの商品情報を取得して画面に出力する部分まではうまく出来たようです。Webで公開しているWebアプリケーション版と違い、こちらはサンプル画像の取得もできるようになっています。

dmmtool_win.png

当初、画像を一覧形式にして表示する部分は自前で実装してしまおうかと思いましたが、Windowsに標準で搭載されているListViewの使い勝手が思いの外良かったのでこちらを利用しました。ご覧の通り、画像をグループ毎にまとめることができ、これはWindows XPから実装された機能のようで、Windows標準のエクスプローラなどにも使用されています。おかげで自前で画像ビューアを作る手間が省けました。

explorer.png

技術的なこと

以前にもこのブログで書いたことがあるのですが、Webアプリケーション版のツールはPerlやPHP等ではなくC#で開発されています。従ってこのようにWindows版を作ることもすぐに出来るわけです。

 さらにプログラム内部では
DMMItemInfo item = new DMMItemInfo();
 item.BeginScrape(“DMMの商品URL”);
みたいなことを書くだけで商品情報を取得できるようになっています。
実際に商品情報の取得に成功した後は、
 string hoge = item.Description;
とか、
 DMMPictureInfo hogePicture = item.LargeSamplePictures[3];
みたいな感じで、商品の説明やらサンプル画像が取り出せるようになっています。
さらに、DMM側のサーバにできるだけ負荷を掛けないように独自のキャッシュシステムも構築しています。
ご意見、要望などをお待ちしております。

 私はプログラミングは得意な方ですが、Webデザインに関しては全くの素人で、売れる広告デザインの作り方などはまったくわかりません。そこで、このプログラムがある程度完成したら最初にごく少人数の方にベータテストという形で使って頂き、スタイルシートに関する意見や、プログラムに関する要望などを募集しようと思います。また、このブログのコメント欄などでも、搭載して欲しい機能などの要望を受け付けておりますので、よろしくお願いいたします。