Win2000でPaint.NET ― 2007年09月01日 15時49分13秒
やっと見つかりましたがな
以前「Paint.NETがインストールできない」というエントリを書いたが、なんとかインストールできるものが入手できた。それまでに若干の紆余曲折があったのでメモっておこう。
2.5をインストールしてみる
会社のPCには2.5のインストーラが残っていたのでそれを持ち帰ってインストールを試みたが、いきなり「Paint.NET requires the .NET Framework 1.1.」と拒絶された。実は今回のクリーンインストールでは、.NET関連は2.0のみインストールして1.1は入れていなかったのだ。
2.5は.NET 1.1系対応の最終版で、これ以降は.NET 2.0に移行していたのは知っていたが、サイドバイサイドで動くと思ってそのまま試してみたがインストールすらできなかった。むぅ。
インターネット図書館
となると、2.5以上3.0未満のバージョンをなんとか入手したくなるわけで、まずはWeb Archiveだのみとなる。
以前のバージョンは現在の公式ページに移転する前に入手していたのでそちらの古いサイトのアーカイブを探って、いくつかページをたどってみた。
後半のほうはすでに移転先へのリダイレクトになっていたが、2006/07/15以前はかろうじて元ページが残っていたのでそこからダウンロードページへ移動、ダウンロードリンクをクリックしてみたが、リンクされているダウンロードサイトでも現在のバージョンしかダウンロードできない。むぅぅ。
ファイル名がわかれば、ひょっとして...
しかし、上記ダウンロードページにたどり着いたことで、もともとのダウンロードファイル名が判明(PaintDotNet_2_xx.exe)したので、これで検索してみることにした。
ぐーぐる先生に「PaintDotNet_2_63.exe」を尋ねて返ってきたのをいくつかたどると、なぜかゲーム系のサイトのダウンロードページへ。いってみたらダウンロードできた。いやぁ、探せばなんとかあるもんだ。
さらに日本語化
ということで、なんとか2.63をインストールでき、さらに「Paint.NET - 日本語化WEB」さんから2.5向けの非公式言語パックをダウンロード、だめ元で適用してみたらうまくいってメニュー周りも日本語化され、快適な状態で使えるようになった。まぁ、リソースファイルだけなのでランタイムバージョンには依存しないか。これでやっと、ひと段落。
ただ、アプリケーションとしての使い勝手は3.0系には及ばないんだけど、まあちょっとした画像加工とかなら間に合うからこれでいいか。
蛇足:バージョンに関する訂正
以前のエントリでは2.5系と2.8系(?たぶん3.0前の最終がこれだと思った)
とか書いていたが、2.x系の最終はどうやら2.72が最終らしく、このバージョンですでにWin2Kのサポートが切られているみたい。
まとめると、
- 2.5以前 → .NET 1.1ベース。対応OSはWin2K/WinXP(Server2003も入るかも。間違いなく動きそうだけど)
- 2.6系 → .NET2.0ベース。対応OSは変わらず。最終は2.64みたい。
- 2.7系 → .NET2.0ベース。Win2Kサポート打ち切り。最終は2.72?
- 3.x系 → .NET2.0ベース。対応はWinXP/Vista/Server2003。2007.9.1現在の最新は3.10
SpinelzのDatePickerのパフォーマンスが改善されている件 ― 2007年09月01日 17時02分49秒
ものすごい勢いでパフォーマンス改善してますが。
先日のエントリで、prototype + script.aculo.usベースのUIライブラリ「Spinelz」に対して無責任に文句たれてたら、なんと開発者ご本人よりコメントをいただいてしまった。びっくり!
詳細はコメント欄を参照していただくとして、
現在開発段階ではありますが、全体的にかなりパフォーマンス改善されております。 表示内容の変更や、複数Datepickerをバインドしてもストレスなくご利用いただけると思います。という情報をいただき、さらに正式リリース前にもかかわらず、現在の最新版のsvnリポジトリを教えていただいたので試してみた。
試してみると、年・月の切り替え時のもたつきがまったくなく、IEでも快適に使用できるように、劇的ともいえる改善がなされていた。すごい!
ざっとソースを覗いてみたら、以前はscript.aculo.usのBuilderを使ってDOM構築をしていた部分をソース文字列生成 → innerHTML書き換えに変更しているみたいなのだが、それだけ(というには修正ボリューム大きいけど)でここまで改善されるとはちょっと予想外だった。
dara-jのアプローチ
文句たれてるエントリでも書いたが、現在はとりあえず自分でスクラッチしてみている。自作するにあたり、以下のような拙い工夫をしてみている。
- テーブルそのものはnew時に生成しておき、表示更新はセルの値の書き換えのみを行う
- インスタンスが使用する要素一式のテンプレートをクラスオブジェクト(コンストラクタになるFunctionね)で1回だけ生成して、インスタンスはそれをcloneNode()したものを自己の要素とする
- 最後に選択した日付を記憶
- 選択日付と表示月の別管理、表示の記憶は表示月にする
- 日付表示として連動する要素の真下に自動的に表示
- 複数インスタンス設置時の、ポップアップの排他(DatePicker1表示中にDatePicker2を表示させたら自動的にDatePicker1が閉じる、といった具合)
テンプレートのcloneNode()が本当に有効かどうかはちと微妙なのだが、表示更新時にテーブルを再構築するのではなく表示内容のみを書き換える方法は結構自信があったのだが、どうも体感上はSpinelzの最新版のほうがパフォーマンスがいいような気がする。俺の2日間っていったい...orz
自作版については、急にSafariでNGになったりとちょっと不安定なところがありそうなので、もう少し検証してよく安定しているようであればまたSpinelzに戻すかも。ますます俺の2日間って...orz
改めて御礼
Spinelzを開発されている木下さん、無責任な悪口とも言えるエントリに貴重なコメントを寄せていただき、本当にありがとうございました。 ご教示いただいたとおりDatePickerのパフォーマンスに関して劇的ともいえる改善が見られ、今後の開発の進捗に大変期待しております。がんばってください。
おまけの愚痴: IEがむかつく
Spinelzに「IECover」というクラスがあり、DatePickerがそれを利用しているのはソースを見てしっていた。IE対策なのもコメントから伺える。さらにこれが「IEのselect要素が常に最前面に表示される」ことへの対策なんだろうってこともわかる。
が、いざ自分で作ってみると「IEのselect要素が常に最前面に表示される」ことが以下に腹立たしく、なるほどIECoverのような工夫がライブラリレベルで必要なのはそういったことかと感心させられた。IECoverが行っている、iframeを用いた回避方法なんてdara-jには自力で思いつくはずもなく、それだけでもUIライブラリに必要なスキルが多岐にわたり深いものが必要なのだなぁと思い知らされる。もっとも、IECoverなんてIE以外に必要ないんだけども。
IEのバカっぷりについては他にも
- ボックスモデルがヘン
- data:スキームに対応してない(これがあればUIライブラリに画像添付しなくてもいいのに)
- ベクターグラフィック周りが孤立無援のvml
- JScriptのString#split(RegEx, String)の結果が他の処理系と違う
- 動的に作成したiframeのバカっぷり(via: Enjoy*Study)
まぁ、根本的な部分でサードパーティのブラウザと事情が違うのはわかる(実態として、OSを構成するコンポーネント上にアプリケーションを構築しているので。mshtmlもmsxmlもWindowsスクリプティングも捨てられないのはわかる)んだけども、それが足枷になって他のモダンブラウザの足を引っ張るようでは存在意義が問われてしまうと思うのだが。
ZendFramework入門・その2 アクションメソッドの追加とリンクの扱い方 ― 2007年09月03日 02時08分59秒
のっけからお詫び
すみません、このエントリ書くまで気づきませんでしたが、前回示したフォルダツリーで、ビュースクリプト関連で1階層抜けてました。具体的には「htdocs/zf1/application/views/scripts/index/」の「scripts」部分が抜けてました。どうもすみませんでした。
2つ目のアクションメソッド
気を取り直して、本題です。
少し期間が空いてしまいましたが、前回の続きでアクションコントローラを扱います。前回はデフォルトのアクションメソッド(indexAction)のみの実装でしたが、まずは2つ目のアクションメソッドを実装するところからはじめましょう。
アプリケーションは前回のものをそのまま流用しますので、これからやる作業は
- IndexController.phpにアクションメソッド「secondAction」を追加する
- secondActionに対応するビュースクリプト「second.phtml」を/views/scriptsに作成する
/application/controllers/IndexController.phpを以下のようにします。
<?php require_once 'Zend/Controller/Action.php'; // IndexController class IndexController extends Zend_Controller_Action { // indexアクション public function indexAction() { $this->view->assign( 'message', 'ようこそ Zend Frameworkへ' ); } // 今回追加したsecondアクション public function secondAction() { // indexActionと同様にメッセージをassignする $this->view->assign( 'message', '2つ目のアクション' ); } }
対応するsecond.phtmlは、とりあえずindex.phtmlをそのままコピーしてリネームしてください。
ここまでの作業で、ファイル一式は以下のようになります。
- htdocs/
- zf1/
- application/
- controllers/
- IndexController.php → アクションメソッド「secondAction」を追加
- views/
- scripts/
- index/
- index.phtml
- second.phtml → secondActionに対応するビュースクリプト
- index/
- scripts/
- controllers/
- index.php
- .htaccess → Apacheを使用している方はこれを忘れずに
- application/
- zf1/
以降、基本的には、「アクションメソッドを増やしたら、対応するビュースクリプトを追加する」というような拡張方法になります。
相互にリンクを張ってみる
さて、せっかくアクセスできるURLが2つに増えたのに、それぞれいちいちURLを直接打ち込んでいたら面倒この上ないので、それぞれのアクションメソッド(のビュー)からお互いにリンクを張ってみることにします。
ビュースクリプト中に直接記述してもいいのですが、リンクアドレスの情報はコントローラ側で管理し、ビュースクリプトはそれをレンダリングするだけに分離するのが筋だと思いますので、そのようにしてみます。
まずは2つのアクションメソッド内で「$this->view」にリンクさせるアドレスをassignさせます。こんな感じでしょうか。
<?php require_once 'Zend/Controller/Action.php'; // IndexController class IndexController extends Zend_Controller_Action { // indexアクション public function indexAction() { $this->view->assign( 'message', 'ようこそ Zend Frameworkへ' ); // secondActionへのリンクをビューへ追加 $this->view->assign( 'link', 'index/second' ); } // secondアクション public function secondAction() { $this->view->assign( 'message', '2つ目のアクション' ); // indexActionへのリンクをビューへ追加 $this->view->assign( 'link', 'index/index' ); } }
そしてビュースクリプトです。index.phtml、second.phtmlとも以下の内容に修正します。
<html> <head> <title>zf1</title> </head> <body> <h3> <?php echo $this->message; ?> </h3> <hr> <!-- プロパティ「link」の内容をhrefに使用する --> <a href="<?php echo $this->link; ?>"><?php echo $this->link; ?></a> </body> </html>
さて、まずは「http://<host名>/zf1/」にアクセスしてみてください。以下のようになるはずです。
ようこそ Zend Frameworkへ
index/second
次にページ中のリンク「index/second」をクリックしてみてください。以下のようになります。
2つ目のアクション
index/index
リンク先がなんだかヘン
さて、ここまで特に問題はないはずなのですが、どこかおかしい所はないでしょうか?
index/secondに表示されているリンクをよく確認してください。未訪問の色のままになっていませんか?
リンク「index/index」にカーソルを合わせてみるとわかると思いますが、実は現在のサンプルのままでは、リンク先が「http://<host名>/zf1/index/index/index」になってしまいます。
よくよく考えてみると(よくよく、でなくてもわかりますが)、Zend Frameworkのコントローラモデルでは、すべてのアクセスがディレクトリパスに展開されるため、相対でリンクを示すと常に現在のパスに対して階層が追加されてゆくことになってしまいます。
これが、アプリケーションがホストのトップであれば、「/index/second」などと絶対パスが使用できるので問題ありませんが、アプリケーションごとにホストを用意したりバーチャルホストを用意したりするのはあまり現実的ではありません。
また、ドキュメント中に出現するURLはリンク先だけではありません。JavaScriptやCSSなどのリソース、画像、さらにはformのpost先など、1つのWebアプリケーション内でアプリケーション内のほかのURLを指定する必要があるところはたくさんありますので、このままではちょっと困ったことになってしまいます。
さて、どうやって解決したものでしょうか...
base要素を活用
これはdara-j的なアプローチですが、リンクの解決にbase要素を使用してみましょう。そう、すべてのページのhead内で「<base href="http://<host名>/zf1/"></base>」が出力されてさえいれば、前述のように「コントローラ名/アクション名」形式でリンクを指定することができます。
base要素に指定するアプリケーションのルートURLですが、直接ビュースクリプト中に指定してもよいのですが、他のアプリケーションを構築する際にもそのまま流用できるように関数を作成することにします。
起動スクリプト「index.php」に以下のような関数を追加します。
function getApplicationUrl($request) { if( $request == null ) return false; return ( preg_match('/^HTTPS/i', $request->getServer('SERVER_PROTOCOL')) ? 'https' : 'http' ) . '://' . $request->getServer('HTTP_HOST') . $request->getBaseUrl(); }引数として受け取る「$request」は、Zend_Controller_Request_Httpというオブジェクトになります。このオブジェクト(クラス)は従来$_GETや$_POSTなどのスーパーグローバル変数から取得していたリクエスト情報を一元的に集約したバリューオブジェクトです。当然Zend Framework上でも従来のスーパーグローバル変数へアクセスはできますが、せっかくのオブジェクト指向プログラミングでもあり、なるべくこちらを使うようにしたほうがよいと思います。
アクションコントローラの初期化機構
では、このgeApplicationUrl関数を使用して、実際にbase要素を出力するようにしてみましょう。引数にZend_Controller_Request_Httpが必要ということもあり、
- アクションコントローラからgetApplicationUrlを呼び出して値を取得し、
- ビュースクリプトへassignする
これまでと同じように、各アクションメソッド内から$this->view->assignとしてもよいのですが、これはコントローラ内での共通処理とするような内容なので、どこか一箇所に集約しましょう。
アクションコントローラは固有の初期化処理用として、「init」メソッドを用意しています。これはZend_Controller_Actionのコンストラクタから始まる一連の初期化処理の中でサブクラス固有の初期化処理用として用意しているもので、これをオーバーライドすることになります。
initメソッドをオーバーライドし、base要素向けのベースURLをビューへassignするコードを追加したIndexController.phpは以下のようになります。
<?php require_once 'Zend/Controller/Action.php'; // IndexController class IndexController extends Zend_Controller_Action { // initメソッド。初期化処理用のフックメソッド // アクセスレベルが「public」な点に注意 public function init() { // リクエストオブジェクト(Zend_Controller_Request_Abstract)を取得 $request = $this->getRequest(); // ベースURLを取得 $baseUrl = getApplicationUrl( $request ); // ビューへ割り当てる $this->view->assign( 'baseUrl', $baseUrl ); } // indexアクション public function indexAction() { $this->view->assign( 'message', 'ようこそ Zend Frameworkへ' ); // secondActionへのリンクをビューへ追加 $this->view->assign( 'link', 'index/second' ); } // secondアクション public function secondAction() { $this->view->assign( 'message', '2つ目のアクション' ); // indexActionへのリンクをビューへ追加 $this->view->assign( 'link', 'index/index' ); // indexAction用のビュースクリプトをレンダリング $this->_helper->viewRenderer( 'index' ); } }
もう一点補足です。secondActionメソッドの最後に「$this->_helper->viewRenderer('index')」という見慣れないコードを追加していますが、これはまったく同じ内容の2つのビュースクリプトを定義しているのが気持ち悪いため、2つのアクションメソッドで1つのビュースクリプトを共用するためのコードです。
_helperやviewRendererがいったい何なのかはこの連載でもいずれ取り上げますが、「viewRendererヘルパーの(暗黙のメソッドである)setRenderメソッドでレンダリングするビュースクリプトを変更できる」ということができる、ということだけ理解してくだされば問題ないと思います。詳細を知りたい場合はリファレンスガイドの「7.8. アクションヘルパー」、および同ページ中の「例 7.8. 別のビュースクリプトの選択」をご覧ください。
他のソースの改変
init内で「baseUrl」として割り当てられたURLをbase要素として出力するよう改変したindex.phtmlは以下のようになります(second.phtmlは不要になったので削除してください)。
<html> <head> <title>zf1</title> <!-- base要素の出力。末尾の「/」に注意 --> <base href="<?php echo $this->baseUrl; ?>/"></base> </head> <body> <h3> <?php echo $this->message; ?> </h3> <hr> <!-- プロパティ「link」の内容をhrefに使用する --> <a href="<?php echo $this->link; ?>"><?php echo $this->link; ?></a> </body> </html>
そしてgetApplicationUrl関数を追加した、最終的なindex.phpは以下のようになります。
<?php // エラーを表示するようにしておく ini_set( 'display_errors', 1 ); // フロントコントローラをrequire require_once 'Zend/Controller/Front.php'; // ルーティング&ディスパッチ開始 Zend_Controller_Front::run( './application/controllers' ); // アプリケーションのベースURLをリクエスト情報から生成 function getApplicationUrl($request) { if( $request == null ) return false; return ( preg_match('/^HTTPS/i', $request->getServer('SERVER_PROTOCOL')) ? 'https' : 'http' ) . '://' . $request->getServer('HTTP_HOST') . $request->getBaseUrl(); }
今回の最終的なファイルツリーは以下のようになります。
- htdocs/
- zf1/
- application/
- controllers/
- IndexController.php → init()、secondAction()を追加
- views/
- scripts/
- index/
- index.phtml → ベースURLとリンクの出力を追加
- index/
- scripts/
- controllers/
- index.php → getApplicationUrl()関数を追加
- .htaccess
- application/
- zf1/
今回のまとめ
今回のポイントは以下のようになります。
- アクションメソッドを増やしたら、対応するビュースクリプトを追加する
- 相対URLを正常に使用するにはbase要素でアプリケーションのベースURLを指定する
- リクエスト情報の取得には、スーパーグローバルの代わりにZend_Controller_Request_Httpを使用する
- アクションコントローラの初期化はinit()メソッドをオーバーライドする
- ビュースクリプトの重複・分散を避けるために、$this->_helper->viewRenderer()を使用して他のアクションメソッドに関連付けられたビュースクリプトを共用することもできる
次回の予定
次回は(今回やる予定だった)エラーの処理方法の概要と、関連して複数のアクションコントローラを使用する例を扱ってみたいと思います。
連載記事って大変だわ。 ― 2007年09月03日 03時34分51秒
いやはや、思ったより大変だ...
なにを思ったのか、先日より「ZendFramework入門」なる連載記事をはじめてみたのだが、これが思ったよりも大変。先ほどあげた記事なんかは、もともと漠然とした構成は頭の中にあったので「1時間くらいでできるかな?」などと考えていたのだが、結局3時間ほどかかってしまった。
書いているうちに「あれも必要では?」「これはどこに差し込もう?」なんて迷いがいろいろでるというのもあるし、なによりテキストエディタでHTMLを直接書いているという前時代的な作業が遅くなる原因なんだろうけど。
よく間違えるし
もともとHTML書くのにツールを使う習慣がないので、通常の記事であればエディタでしこしこHTML書くのはそんなに苦にならないんだけど(量を書かないからね)、今回ネックになるのは
- HTMLのソース
- フォルダツリー
うれしいこともある
もちろん大変なことだけではなく、うれしいこともある。まだはじめたばかりで海のものとも山のものともつかないしょぼい連載なのだが、いくつかのサイトさんでこの連載をご紹介くださっているのだ。
紹介してくだすったお礼も兼ねて(兼ねられるのか?)、リンクを示しておこう。
あるSEのつぶやきさん、PHPの種さん、ご紹介ありがとうございます。へこむこともある
ただ、上記の「PHPの種」さんのところで別の入門記事(というかクイックスタート)を紹介されているのだが、こんなの見ると「俺やってる意味あんのかな」とかへこんだりへこまなかったり。
- PHPの種 ブログ ≫ zend_controller クイックスタートが紹介されています
- Zend Framework 特集:実験:zend_controller:クイックスタート | LaboRat [ラボラット]
こっちなんか2回(準備編入れて3回か)も費やしてるのに内容的にはラボラットさんとこより薄いもんなぁ。
今後はもう少し内容を濃くするようにがんばろう。
HttpWebRequestでちとハマってた ― 2007年09月04日 01時28分16秒
HttpWebRequestでCookieを使う
先週までのLL漬けからうって変わって、今日から久しぶりに.NETプログラミング。いや~、半年振りくらいのC#だもんで、結構コードかけなくなってるし。明日くらいまではちょっとリハビリ状態かしらん。
で、本題。これ知らずに今日は結構時間使ったのでメモ。でも.NET1.1だからいまやあまり役にたたん情報かも。
.NETアプリでWebアプリからちょっとしたデータを取得しようとしてたんだけど、まず手抜きでWebClient使って 認証リクエスト → データ取得処理 てな手順でコード書いてみたらうまくいかない。
Webアプリ側では認証とおってないリクエストはすべてログインフォームがあるページに飛ばすようにしてるんだけど認証リクエストの戻りからすでにログインフォームのコンテンツが返ってきてる状態。
おんなじ処理をJScriptでXHR使ってやってみたらこっちは認証とおって目的のデータがちゃんと取れてる。ふむ。
1.1のWebClientはかなり割り切った使い方をするものらしく(いままであんまり使ったことなかった)、URL直接食わせてコンテンツをダウンロードするくらいのインターフェイスしかないみたいなので、不精せずにHttpWebRequestに切り替えてみたんだけどこれも結果変わらず。
Webサーバのログを見てみると、一応希望のURLへのアクセスで200を返しているみたいだけどしつこくログインフォームのコンテンツだけが返ってくる。「ひょっとしてヘンなキャッシュ引いてるんじゃね?」とか思ったんだけどどうもそうでもない。
で、レスポンスヘッダ調べてるうちにPHPが返すセッションの値がリクエストごとに違っている。なんだ、そんな話かよ。
要するにサーバがCookieセットしようとしてるけどWebRequest側でそれを受け付けてないので、当然認証していないことになってたわけだ。
CookieContainer
と、ここまでわかったけどはて、どうやってCookieサポートするんだ?
でMSDNとにらめっこしてたらHttpWebRequestにCookieContainerやらいうプロパティがあることを発見。
解説の項を見てみると
CookieContainer プロパティは、要求に関連付けられている Cookie を格納する CookieContainer クラスのインスタンスを提供します。 CookieContainer の既定値は null 参照 (Visual Basic では Nothing) です。GetResponse メソッドが返す HttpWebResponse の Cookies プロパティで Cookie を返すには、CookieContainer オブジェクトをそのプロパティに割り当てる必要があります。などと、わかるようなわからんような記述がある。まぁHttpWebRequestでCookieの文言があるメンバも他に見当たらんし、これを使ってみるのかな。
で、このCookieContainerプロパティは型がまんま「System.Net.CookieContainer」クラスで、これがパラメータなしコンストラクタをサポートしてるので、こんな感じのコードを書いてみた(注:コード未検証。抜けとか間違いあるかも)。
CookieContainer cookie = new CookieContainer(); HttpWebRequest req = (HttpWebRequest)(WebRequest.Create( authUrl )); req.CookieContainer = cookie; using( HttpWebResponse res = (HttpWebResponse)(req.GetResponse()) ) { // 認証ページのコンテンツを取得 } // 同じCookieContainerを使いまわしてデータ取得URLへアクセス req = (HttpWebRequest)(WebRequest.Create( url ) ); req.CookieContainer = cookie; using( HttpWebResponse res = (HttpWebResponse)(req.GetResponse()) ) { // データを取得 }で試してみると、これで正解だったみたい。いやー、わからんかった。
つか、定番処理っぽい
ちと時間くったけど、まぁ最悪「MSXMLをtlbimpするかぁ?」とか考えてたワケで、それに比べればマシなのでほっとしてたんだけど、あとでなんとなくぐぐってみたらいっぱい引っかかるじゃん。探し方悪すぎ。orz
最近のコメント