DMMTool(仮名) for Windows開発状況その4
いよいよブックマーク機能を実装しました。
今回は、ブックマーク機能を実装するにあたって利用したMVCという設計手法とXBELという規格の話が中心です。
まずはスクリーンショットをご覧下さい。一般的なウェブブラウザに搭載されているような階層構造を持ったブックマークがウィンドウ左側に表示されているのがわかると思います。
このブックマーク機能の仕組みですが、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がどのように働くかを説明します。
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が存在する場合でも
画面に複数のドキュメントウィンドウが開かれている等、複数のViewが存在する状況でも、単にModelはそれぞれのViewに対して同時にModel更新の通知を送ってやれば、各Viewの表示内容が一斉に更新されるわけです。今回ブックマーク機能の実装にあたりMVCを利用した最大の理由が「場合によっては画面に複数のViewが存在する」という理由です。
ドラッグ&ドロップの実装
今回一番時間がかかったのが、ブックマークのドラッグ&ドロップによる編集です。
TreeViewにはドラッグ&ドロップをサポートする機能が標準で搭載されていますが、若干機能不足です。
たとえば標準のドラッグ&ドロップではTreeNodeを他のTreeNodeの上にドラッグし、ドラッグされたTreeNodeの子とすることは簡単にできますが、あるTreeNodeを二つのTreeNodeの間にドラッグして任意の位置にアイテムの挿入を行う、といった機能はありませんから、こういった機能は自前で実装する必要があります。

また、ドラッグ&ドロップにも下記のようなルールを設ける必要があります。
- あるフォルダをそのフォルダ自身が内包しているフォルダには移動できないようにする。Windowsのファイルシステムを例にあげて説明すると、C:hogeというフォルダと、C:hogehageという二つのフォルダがあった場合、前者のフォルダを後者のフォルダの内部に移動させる操作はできない、といった具合です。
- あるフォルダをそのフォルダ自身の子とすることもできない。
- ブックマークの項目をフォルダへ入れて子とすることはできるが、ブックマークの項目をブックマークの項目の子とすることはできない。
- 同様にフォルダをブックマーク項目に入れて子とすることもできない。
これらの機能を一つ一つ検証しながら実装していたのですが、あまりにもややこしすぎて時間がかかってしまいました。
ブックマークの保存形式
ブックマークの保存は、ブックマーク記述言語「XBEL」形式で行います。XBELとはXML Bookmarks Exchange Languageの略で、XMLを利用したブックマークの相互利用のための規格です。XML形式ですので、ブックマークの保存の際にはブックマークのツリー構造を再帰的に辿ってXmlDocumentを構築した後、ファイルに出力しています。XBEL形式に関してはこのページを参考にしました。
この形式を利用する利点は、様々なブラウザのブックマークとの相互変換が可能である事です。自作のアプリとはいえ、独自のデータ形式を使ってしまっては後々困ることになりますから、オープンな規格はどんどん利用すべきだと思います。
さて、次回はWeb版、デスクトップアプリ版両方のDMMアフィリエイトリンク作成ツールに搭載されているキャッシュシステムについて書いてみようかと思います。
DMMTool(仮名) for Windows開発状況その3
DMMTool(仮名) for Windows開発状況その2
今回もまた、プログラミングに関する記事です。大変申し訳ございません。
前回のエントリを書いてから、空き時間を見つけては地道に作業を続けておりました。
前回からの改善点は
- ネットワークアクセスから何から、ありとあらゆる物をマルチスレッド化した。
- エラー処理の強化
徹底したマルチスレッド化
前回のバージョンではURLボックスにURLを入力して情報取得ボタンを押すと、すべての情報の取得が完了するまでウィンドウがフリーズした状態になっていました。画像が多い場合、全情報の取得には10秒以上かかる場合もあり、その間ユーザーは何の操作もできずに待たねばならなかったのです。これはすべての処理をシングルスレッドで行っていたため、情報取得が完了するまでメインのGUIスレッドが止まるからです。
ところが、たくさんある商品の情報を次々と取得したい場合、ある商品のURLを入力し、表示が完了するまで待ち、次の商品のURLを入力し、また待ち、なんてことはかったるくてやってられません。
そこで、今回はHTMLのスクレイピングから画像の取得まで、すべてをスレッド化し、個別の情報が取得でき次第次々とイベントが発生するように改良しました。発生したイベントはメインのUIスレッドに伝えられ、その度に画面の更新が行われます。この状態でもメインのイベントループが動き続けているため、他の操作を行うことができます。
おかげで、下の画像のようにたくさんのウィンドウを開いて次々と情報取得を行っても非常にぬるぬるとなめらかに動くようになりました。
エラー処理の強化
シングルスレッドで処理を行う場合、極端な話必要な処理を関数として書けるので、どこかでエラーが発生すればその時点で関数を抜けて処理を中断し、エラーが発生したことをユーザーに告知するだけのシンプルなプログラムで済みます。
ところがマルチスレッドの場合には話がすこしややこしくなります。
たとえば平行していくつものスレッドが動いている場合、あるスレッドは正常に完了したが、あるスレッドは途中でエラーが起きた、という状況が起きます。具体的には、複数の画像データをマルチスレッドで平行してダウンロードしていたがある画像のダウンロードだけは途中で失敗した、等のケースです。この場合、あるスレッドで発生したエラーをどのようにメインスレッドに伝えるかが問題となります。.NETではあるスレッドで発生した例外を他のスレッドでキャッチすることができないので、例外が発生した場合にはイベントを発生させてメインスレッドに例外を伝えるということをやっています。
また、エラーの告知方法も少し工夫しました。
このツールでは、ユーザーが商品情報の取得を開始して比較的早期に発生したエラー(URLの入力ミスやパッケージ画像が取得できなかった等)はダイアログを表示してその時点で処理が中断されます。
- ユーザーを完了まで待たせるだけ待たせておいてエラーで処理が中断されてはフラストレーションになるから。
- 取得した画像全てがユーザーが必要としている画像となるケースは希であるから、リロードを行うかどうかはユーザーの判断に任せることにした。
- お気に入り、履歴など
- タブを用いて一枚のウィンドウで複数の商品情報を開けるようにしたい
- 複数の商品のアフィリエイトリンクを効率よく作成できる手段の提供
DMMアフィリエイトリンク作成ツールのWindows版の開発を開始しました。
今日は久々にプログラミングの話題です。
当ウェブサイトで公開しております「DMMアフィリエイトのリンク作成支援ツール」はいわゆるWebブラウザを使ってサービスにアクセスするWebアプリケーションの形になっておりますが、Windows版のツールの開発も開始しました。
まだまだ完成にはほど遠く、公開できる状態にはなっておりませんが、DMMサイトからの商品情報を取得して画面に出力する部分まではうまく出来たようです。Webで公開しているWebアプリケーション版と違い、こちらはサンプル画像の取得もできるようになっています。
当初、画像を一覧形式にして表示する部分は自前で実装してしまおうかと思いましたが、Windowsに標準で搭載されているListViewの使い勝手が思いの外良かったのでこちらを利用しました。ご覧の通り、画像をグループ毎にまとめることができ、これはWindows XPから実装された機能のようで、Windows標準のエクスプローラなどにも使用されています。おかげで自前で画像ビューアを作る手間が省けました。
技術的なこと
以前にもこのブログで書いたことがあるのですが、Webアプリケーション版のツールはPerlやPHP等ではなくC#で開発されています。従ってこのようにWindows版を作ることもすぐに出来るわけです。
私はプログラミングは得意な方ですが、Webデザインに関しては全くの素人で、売れる広告デザインの作り方などはまったくわかりません。そこで、このプログラムがある程度完成したら最初にごく少人数の方にベータテストという形で使って頂き、スタイルシートに関する意見や、プログラムに関する要望などを募集しようと思います。また、このブログのコメント欄などでも、搭載して欲しい機能などの要望を受け付けておりますので、よろしくお願いいたします。
I’m Jugglamp EX バージョン2.1.5をリリースしました。
一部の方には「デスクトップGOGOランプ」という愛称で親しまれている拙作フリーソフトウェア「I’m Jugglamp EX」が本日バージョン2.1.5へとバージョンアップしました。
前バージョンからの変更点は、シミュレート対象機種に「アイムジャグラーAPEX」と「ミラクルジャグラー」が追加された事です。その他の機能追加などはありません。

ダウンロードはI’m Jugglamp EXのページから。
今後は、徐々にバージョン2.1.X系統をフェードアウトさせていき、2.2系統をリリースしたいと思っていますが、それに伴う機能追加で、あまりにも大量のコードを一人で書いている状況で、切羽詰まっております。(現在でもソースコードが2万行ほどあります)
実のところ、Jugglampシリーズは元々自分のパチスロの立ち回りの研究のために書き始めたソフトなのですが、現在自分自身がパチスロを打つ時間がほとんど無くなったこともあって、(自分のために作ったソフトにもかかわらず)もう自分にはほとんど必要のないソフトとなってしまいました。しかしながら、要望や意見などは頂いており、自分の時間が空いた時に修正を加えたりしている状況です。
おそらくは、大きな機能追加はバージョン2.2で最後とし、以後は新機種が登場するたびにマイナーバージョンアップ程度に留めていこうと思います。
DMMアフィリエイトのリンク作成ツールをバージョンアップしました。
当サイトで公開しております、「DMMアフィリエイトのリンク作成支援ツール」ですが、本日若干の改良を加えました。
従来はテキストリンクかイメージリンクのいずれかしか作成できませんでしたが今回新たに「イメージ+商品説明テキスト」というスタイルでアフィリエイトリンクを作成する機能が追加されました。
なお生成されるリンクの見た目はスタイルシートでカスタマイズ可能となっております。
外枠の色を変えたい、テキストの大きさや色を変えたい、などの要求にはできるだけ応えられるようになっております。
DMMアフィリエイトのリンクを簡単に作成できるツールを公開しました。
お久しぶりです。
前回のエントリで「面倒な日常作業を自動化するプログラムを書いている」とお伝えしましたが、たった今完成しました。これは自分のためでもあるのですが、知人に「どうしても作ってくれ!」と頼まれた部分もありまして、プログラムを書いておりました。
このツールはDMMで販売されている商品ページのURLを最大20件まで入力でき、一括でアフィリエイトリンクを作成する事ができるツールです。
ツールはこちらのページで公開しております。
早速使い方を説明致します。
アフィリエイトIDを入力
まず、あなたのアフィリエイトIDを入力します。
![]()
商品のURLを入力(複数入力可)
続いて、アフィリエイトのリンクを作成したい商品のURLを入力します。
複数のURLがある場合には改行で区切って入力します。最大20件まで入力できます。
![]()
オプション選択
次に、リンクの作成オプションを選択します。テキストリンクか画像リンクかを選択できるほか、サムネイルイメージのリサイズも行えます。
![]()
結果の確認
最後に、送信ボタンを押しますと、生成されたリンクタグや実際にブラウザでどう見えるかがプレビューに表示されます。
![]()
おまけ
なお、画像リンクを作成した場合、画像の上にマウスを載せると商品の説明がポップアップするように出来ています。
![]()
技術的な事
このツールはCGIとして動作するツールですが、ほぼすべてのプログラムがC#で記述されており、.NET Frameworkの機能を活用して作られています。従って、Windows版を作るのはとても簡単、というより、実は既にWindows版も存在します。Web版のURL20件制限はサーバーの能力が乏しいために存在しますが、Windows版には制限はございません。ただし、Windows版はまだコア部分に適当なGUIをかぶせただけで、とてもじゃないけど公開できる代物ではないためまだ公開はしておりません。
なお、DMMからデータを取得する際、一度取得したデータはこちらにキャッシュされますので、必要以上にDMMのサーバに負荷をかけるという事はありません。
I’m Jugglamp EX 2.1.2を公開しました。
10ヶ月ぶりのバージョンアップです。
追加された機能、修正点は以下の通りです。
- シミュレート可能機種に「ハッピージャグラーV」を追加。
- スランプグラフのサイズを極端に小さくした状態で「拡大率を表示内容に合わせて最適化」を行った場合に例外が発生してアプリケーションが終了してしまうバグを修正。
- スランプグラフのスクロールバーの挙動を微調整。
ダウンロードページはこちら
その他、利用者の方から「ぶどう確率表示する機能を追加して欲しい」という意見も頂いておりますので空き時間を見ながら順次追加していこうと思います。特にぶどう確率はハッピージャグラーVで従来の機種に比べて大きな設定差が設けられたため、立ち回りに必要な情報の一つになるのではないかということで、この機能に関してはできるだけ早く実装します。
また、現在のバージョン2.1系統から2.2系統に移行すべく開発を行っております。
シミュレータとしての機能に大きな変更はないのですが、いままでにない機能を追加する予定です。(すでにソースコード行数は2万行に達していて、徐々に一人で作業するのが大変になってきましたが・・・)
なんじゃこりゃ
限定2本だそうです。
http://page19.auctions.yahoo.co.jp/jp/auction/x106937631?u=;evastore_auc
フェンダー公式:http://www.fender.jp/evangelion/info.html
Process.Start()で起動したプロセスのメインウィンドウのハンドルが取得できない場合の対処法
現在C#にて、以下のような仕様でプロセス間通信を行うアプリケーションを作っているのですが、プロセスAから起動したプロセスBのメインウィンドウのハンドルが取得できないという問題に遭遇しました。
- ユーザーの操作でプロセスAがプロセスBを起動。
- プロセスAはプロセスBのメインウィンドウにWM_COPYDATAを用いてデータを送信。
- プロセスBはプロセスAから受けた指示に従って各種動作を行う。
一番最初に書いたコードは、次のコードです。
このコードを実行すると、プロセスは起動できるのですがWaitForInputIdle()で起動したプロセスのメッセージループ突入まで待機しているにも関わらず、メインウィンドウのハンドルが取得できずに0が返って来ることがあります。
//プロセスを生成する。
Process p = new Process();
p.StartInfo.FileName = appPath;
p.StartInfo.Arguments = args;
p.StartInfo.UseShellExecute = false;
p.Start();
//念のため待つ。
p.WaitForInputIdle();
// ウィンドウのハンドルを取得するのだが、 // IEや.NETアプリを立ち上げるとゼロになってしまう場合がある。
IntPtr hMainWindow = p.MainWindowHandle;
notepad.exeなどのシンプルなアプリケーションの場合には正しくウィンドウハンドルを取得できるのですが、Internet Explorerや自作の.NETアプリケーションの場合には0が返ってきます。
どうやら挙動を詳しく調べてみると、一部のアプリケーションを起動した場合にはWaitForInputIdle()がきちんとWaitしてくれていないようで、メインウィンドウが生成される前にMainWindowHandleを取得してしまいゼロが取得されるようでした。
おそらく一部のアプリケーションでは、ウィンドウ生成後メッセージループ突入という組み方がされていないためこのような現象が起こるのでは、と推測します。
そこで、以下のようなコードを作成し再びテストを行ったところ、InternetExplorerや.NETアプリケーションを起動してもウィンドウハンドルを取得することができるようになりました。
//プロセスを生成する。
Process p = new Process();
p.StartInfo.FileName = appPath;
p.StartInfo.Arguments = args;
p.StartInfo.UseShellExecute = false;
p.Start();
//念のため待つ。
p.WaitForInputIdle();
//ウィンドウハンドルが取得できるか、 //生成したプロセスが終了するまで待ってみる。
while (p.MainWindowHandle == IntPtr.Zero && p.HasExited == false)
{
System.Threading.Thread.Sleep(1);
p.Refresh();
}
// hMainWindow != IntPtr.Zeroの場合にはウィンドウハンドルを //取得できたと思われる。
IntPtr hMainWindow = p.MainWindowHandle;
このコードでは、WaitForInputIdle();で念のためメッセージループの開始を待った後に、さらに、本当にウィンドウハンドルが取得できたかどうかをチェックし、取得できるまでループを回す、という事を行っています。
その際に、起動したプロセスが異常終了した時の事も考慮し、プロセスが終了してしまった場合にはループを抜けるようにしています。
今のところはこのやり方で、メインウィンドウのハンドルは取得できています。
ただしこのコードにはタイムアウト処理が入っていません。もし起動したプロセスがなんらかの原因でウィンドウの生成に失敗した場合、起動元のプロセスがフリーズしてしまいますので、実際にはタイムアウト処理も入れた方が良いでしょう。
さらに.NET Frameworkのドキュメントによりますと、Windows Formで生成したフォームのウィンドウハンドルはフォームの有効期間中に不変であるという保証はないそうです。(ShowInTaskbarプロパティを操作すると変わったりするようです) 従って、ウィンドウハンドルを取得しそこへウィンドウメッセージを送るという方式でプロセス間通信を実現する場合には、取得したメインウィンドウのハンドルを保持しておくのではなく、メッセージ送信前に取得しなおしておいたほうが確実かもしれません。
