ページ

2010年12月20日月曜日

[お知らせ] 過去に他のブログに書いた記事をすべてこのブログに持ってきました

今まで使ってきたブログ(技術系)

2003年7月 BlogX を立ち上げてブログ開始。
(BlogX っていうのは C# で作られたブログエンジンです。2004年くらいで更新はストップしてます)


.Text 時代

2004年1月 .Text を立ち上げてブログ開始。
(.Text も C# で作られたブログエンジンです。こちらもずいぶん前に開発は終了してるようです)

BlogX のデータをすべて移行して旧ブログは停止。
2008年4月ごろ 技術系以外の趣味のことを書いていた FC2 のブログに技術系のことも書くようになってきた。
(使い分けが面倒になった)

FC2 時代
2009年1月 .Text のブログの更新を停止して FC2 の方に一本化。
2010年7月 やっぱり技術系と趣味系を分離。
blogspot に技術系のためのブログを作成。それがここ。

(最初は独自ドメインを使った URL で開始したけど 2010/12/14 に普通の blogspot の URL に変更)

blogspot 時代
現在  

と、こんな感じで今までブログを移り変わってきたんですが、特にデータを移行するってことはしてきませんでした。(BlogX → .Text はしましたが)
それを今回、過去に書いた技術系の記事をすべてこのブログに持ってきました。

.Text に書いた記事はすべてここに移行しました。ちょっと面倒なところもありましたが、コメントも含めて無事すべてのデータを移行できました。移行手順については別の記事にまとめるつもりです。
で、この .Text のブログは停止しました。
.Text は開発時期が古いですし、コメントやトラックバックのスパム防止機能がありません。自分でソースをいじってスパムを弾いたり、CAPTCHA を導入したりしてましたが、それでも今でもスパムがやってきます。なので、たまにスパムを消してやったりしないといけないんですが、さすがに面倒になりました。
今回、過去の記事をここに移行してきたのは、この 「.Text にやってくるスパムがうざいから .Text を停止したい」 というのが一番の理由です。

FC2 では技術系のことと趣味系のことがまぜこぜになってますが、今回、技術系の記事をすべてここに移行しました。こちらもコメントも含めて移行できました。この手順についても別の記事にまとめるつもりです。
FC2 の方を消したりはしていないので、技術系の記事に関してはここと FC2 とに同じものが 2つあります。消すのもなんなので、これはこのままにしておくつもりです。

なお、単純に記事を移行しただけで修正などはしていません。なので、リンクが切れたり画像が表示されていないようなところがあるかもしれません。また、スタイルが違うせいで見た目がおかしいところもあるかもしれません。これらはとりあえずそのままにしておくつもりです。あまりにひどいところがあったら修正するかもしれませんけど。

というわけで、

という使い分けで今後もやっていきます。
(最近、どちらもあまり更新してませんが)

2010年12月14日火曜日

[お知らせ] このブログの URL が変わりました

下で書いたとおり、このブログの URL が変わりました。新しい URL は http://shinichiaoyagi.blogspot.com/ です。
RSS の URL も変わりました。 http://shinichiaoyagi.blogspot.com/feeds/posts/default です。

すでに以前の URL ではアクセスできません。
あらためて検索してみたら、あまり多くはありませんが、はてブ、.NET Clips、MSDN フォーラムなどからリンクされていました。これらは完全にリンク切れになってしまいます。申し訳ないです。
なお、URL が変わっただけでブログ内の記事自体は以前とまったく同じです。(各記事の URL もドメイン名部分を “shinichiaoyagi.blogspot.com” に置き換えてもらえばそれ以外は同じままでアクセスできるはず)

2010年12月13日月曜日

[お知らせ] このブログの URL が変わります

このブログの URL が http://shinichiaoyagi.blogspot.com/ に変わります。
あわせて RSS の URL も変わります。( http://shinichiaoyagi.blogspot.com/feeds/posts/default )
URL の変更は 2010年12月14日の午前中を予定しています。(って、もう明日ですが)

なお、変更後は今の URL でのアクセスはできなくなります。リダイレクトなどもされません。
ですので、このブログへのリンクはみんな切れちゃうことになります。すみません。

2010年10月28日木曜日

[VS2010] Help Viewer 1.1 (Visual Studio 2010 SP1 といっしょにリリース予定)

Help Viewer 1.1 Preview Video より。
Visual Studio 2010 SP1 といっしょに Help Viewer 1.1 がリリース予定だそうです。
紹介ビデオはこちら Help Viewer Updates in Visual Studio 2010 SP1

VS2010 では、ローカルのヘルプを見るのにもブラウザ(IE)が使われるようになってました。それが SP1 で専用のヘルプビューワーアプリケーションが追加されるようです。
すでに GrapeCity ヘルプビューワ とかいくつかヘルプビューワーがありますから、とってもイマサラ感がしないでもないですが。

ちなみに、VS2010 の「ヘルプ」-「ヘルプ設定の管理」メニューでオンライン・ローカルのどちらのヘルプを参照するかや、オンライン上のヘルプをローカルにインストールしたりできます。

ところで、”Help Viewer 1.0” というのは、ヘルプシステム全体の名称として使われていたと思います。Microsoft Help 2 の次のバージョンの名前が Help Viewer 1.0 だったはずです。
ということは、今回のアプリは「Help Viewer 1.1 という名前の Help Viewer 1.0 用ビューワーアプリ」ってこと?
上記の記事にも「Q: 既存のヘルプコンテンツに変更が必要なの?」 「A: 必要ない」とか何とかあるので、やっぱりそういうことだよなぁ。
うーむ

2010年10月27日水曜日

[.NET] C# でミニダンプを書き出す方法

Writing Minidumps in C# より。
C# でミニダンプを書き出すコードが紹介されてたので覚え書きとして。

ミニダンプを書き出しておけばあとでそのときの状態(呼び出し履歴とか変数の内容とか)を確認することができます。
特に Visual Studio 2010 ではマネージコードのダンプに対応しているのでちゃんと C# や VB での行番号とかがわかります。(VS2008 とかだと JIT 後のアセンブラレベルのものしか見れないんじゃないかと思います。確認してませんが)

[Silverlight][WP7] PhysicsHelper 4.0 Alpha

PhysicsHelper の 4.0 Alpha がリリースされてました。
http://physicshelper.codeplex.com/

PhysicsHelper とは、C# で実装された物理演算エンジンである Farseer Physics Engine をビヘイビアーで包みこんだものです。おかげでとってもお手軽に XAML の要素に対して物理演算をすることができます。すでに Windows Phone 7 にも対応してるそうです。
とりあえず http://www.andybeaulieu.com/video/PhysicsHelper4Intro.wmv このビデオを見ればどういうものかはわかるんじゃないかと。
(英語ですが、Blend を使ったデモなので見てるだけでもやってることはわかりました)

ただ、まだ Alpha なのでバグもあるし、未実装の部分もあるとのこと。

2010年9月28日火曜日

[Silverlight] Silverlight は Web のためのものでは無くなった

Silverlight…not just for the Web… より。

おぉ、ほんとだ! http://msdn.microsoft.com/en-us/library/default.aspx を見ると

msdnsilverlight_en_us.jpg

このように Silverlight が 「.NET Development」 の下に移動してます。
今までは 「Web Development」 の下でした。
上記記事にあるとおり、out of browser もあるし、Windows Phone 7 もあるんだから Web の下よりは .NET の下の方がいいんじゃないか、っていうことですね。

けど、日本語版 http://msdn.microsoft.com/ja-jp/library/default.aspx の方を見ると

msdnsilverlight_ja_jp.jpg

まだ 「Web 開発」 の下ですね。
きっとそのうち英語版と同じように移動するんでしょう。

2010年9月22日水曜日

[Silverlight] Effect がパフォーマンスに与える影響

Silverlight Performance Tip: Understanding the impact of Effects on performance より。
なるほどぉ。
正確なことは上記記事を見てもらうとして要点のみ。

<Grid x:Name="LayoutRoot" Background="White">
    <Border Width="200" Height="100" Background="LightGray">
        <Border.Effect>
            <DropShadowEffect/>
        </Border.Effect>
        <Grid Width="200" Height="100">
            <TextBox Width="100" Height="30"></TextBox>
        </Grid>
    </Border>
</Grid>

こんな XAML があるとします。表示させると以下のような感じです。

DropShadowEffectPerformance.jpg

DropShadowEffect を使って影を落としていて、真ん中にテキストボックスがあるというごく単純なものです。
で、DropShadowEffect などのエフェクトがかかっていると、その内側の要素に再描画が必要になった場合に DropShadowEffect の部分ごと再描画されるそうです。
この例では中にテキストボックスがあるわけですが、テキストボックスの点滅するキャレットも再描画によって描かれてます。なので、キャレットが点滅するたびに回りの Border ごと再描画されることになります。もちろん、キャレットだけでなく、マウスホバーで色を変えたりするようなボタンとか、進捗にあわせて動くプログレスバーとか、再描画が発生するものはみんな同じことになります。
この例では Border の下にはテキストボックスがあるだけなので特に問題にはなりませんが、コントロールがたくさんあるような場合にはそれらがみんな再描画されることになるので CPU 負荷が高くなったりといった問題が発生するかもしれません。
で、どうすればいいかというと

<Grid x:Name="LayoutRoot" Background="White">
    <Border Width="200" Height="100" Background="LightGray">
        <Border.Effect>
            <DropShadowEffect/>
        </Border.Effect>
    </Border>
    <Border>
        <Grid Width="200" Height="100">
            <TextBox Width="100" Height="30"></TextBox>
        </Grid>
    </Border>
</Grid>

こんな風に Border を 2つに分けてしまえばいいそうです。
実際やってみると確かに見た目は同じになります。
そして、テキストボックスが含まれている方の Border には何のエフェクトも無いことになるので無駄な再描画は発生しないということですね。
なるほどなぁ。

なお、再描画が発生する範囲は Silverlight を貼り付けている HTML の方に

<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
  <param name="enableRedrawRegions" value="true"/>
:
:

enableRedrawRegions を指定すれば可視化することができます。

2010年9月14日火曜日

[Silverlight][Twitter] Seesmic Desktop 2 って MEF で機能追加できるようになってるのか

Twitter クライアントの一つ、Seesmic Desktop 2 ですが、これって Silverlight 4 なんですね。Silverlight 4 ですが、ブラウザ上で動かすのではなくクライアント PC にインストールして実行する形態になっています。(out-of-browser です)

そして、
Writing plugins for Seesmic Desktop
(Microsoft の Silverlight チームのプログラムマネージャーの Tim Heuer 氏のブログ)
によると Seesmic Desktop 2 は Managed Extensibility Framework(MEF)によって拡張可能になっているそうです。Visual Studio 2010 用のテンプレート Seesmic Desktop Platform Developer Templates なんてのもありますし、Seesmic Desktop Platform Plugins には Tim 氏が作ったプラグインが紹介されています。
結構拡張性も高そうで、なかなかおもしろそうです。

# といいつつ、インストールしてちょっと使ってみたけど、イマイチ自分には合わず。
# 結局、TweetDeck に戻っちゃいました。
# 一時期は Seesmic Web を使ってたけど、最近 TL が更新されない病が発生してて使い物にならず。

2010年9月13日月曜日

[.NET] MTA では OpenFileDialog が動かない?

たまたま見かけてちょっと気になったので覚え書きとして。。。

Current thread must be set to single thread apartment (STA) mode before OLE calls can be made」 より。
Visual Studio セットアッププロジェクトでインストーラーを作るときにインストーラークラスのカスタムアクションで System.Windows.Forms.OpenFileDialog を使うとエラーが出ちゃうそうです。Win XP や 2003 ではちゃんと動くけど、Vista やそれ以降の Windows で発生するとのこと。
デバッガーにアタッチしておくと、"Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it. This exception is only raised if a debugger is attached to the process." というエラーメッセージが出るそうです。原因はこのメッセージのまんまで、MSI が MTA で動いてるからで、OpenFileDialog なんかは STA じゃないと動かないかららしい。

へぇ、OpenFileDialog とかって MTA じゃ動かなかったんだ。知らんかった。

上記の記事では対策方法も紹介されています。
別のスレッドを作って、そいつを Thread.SetApartmentState(ApartmentState.STA) してから OpenFileDialog を ShowDialog() してやればいいようです。

2010年9月10日金曜日

[C#] yield return はスレッドセーフなのか? 追記

昨日の 「[C#] yield return はスレッドセーフなのか?」 に匿名さんからコメントを頂きました。
昨日の記事の最後のところにまとめとして 「lock { ~ } の中で yield return を使うのはやめておいた方がよさげ」 と書きましたが、ちょっと乱暴なまとめだと思ったので追記します。
(最初は昨日の記事に追記するつもりだったんですが、長くなったので別記事にしました)

えーと、もともと 「lock { ~ } の中で yield return を使うのはやめておいた方がよさげ」 と私が思ったのは 「ロックが解除されるタイミングが呼び出し元に依存するから」 というのが理由です。
お仕事で lock { ~ } の中で yield return を使うコードを書いていたときに、ふと、「これってちゃんとロックかかるの?」 と疑問に思ったのが発端で今回のことを調べたんです。このとき書いていたコードは 「なるべく早くロックを解除して他のスレッドが動けるようにしたい」 というものだったので、自然とロック解除のタイミングが呼び出し元に依存するのは避けたいと思ってました。
そういう前提があったので 「lock { ~ } の中で yield return を使うのはやめておいた方がよさげ」 と、まとめのところに書きました。けど、そういう前提であることをあまり明確にせずにこうまとめちゃうのはちょっとまずかったですね。

-- 9/10 13時追記 ここから
前の記事にもちょこっと追記しましたが、指摘を頂いたのでこちらにも追記しておきます。
すっかりロックが解除されるのは Enumerator の Dispose() が呼び出されたときだけかのように書いちゃってますが、実際には MoveNext() が false を返すときにも Dispose() と同じ処理が行われるようになっています。なので、Enumerator の Dispose() メソッドを呼び出したときか、MoveNext() が false を返して列挙が終わるときにロックが解除されることになります。
もちろん、以下に書いたファイルの例でも同じです。
-- ここまで追記

ということで、また他の例を。

コメントで頂いたように using { ~ } や try ~ finally でも lock { ~ } と同じことが起こります。
たとえば、以下のようなコード。

private IEnumerable<string> GetLines()
{
    using (var stream = new FileStream("test.txt", FileMode.Open, FileAccess.Read, FileShare.None))
    using (var reader = new StreamReader(stream))
    {
        var text = reader.ReadToEnd();
        var lines = text.Split('\n');
        foreach (var line in lines)
        {
            yield return line;
        }
    }
}

これはファイルをオープンするときに FileShare.None を指定してファイルをアクセス禁止にしています。
yield return が無ければ using のスコープを抜けるときに stream.Dispose() と reader.Dispose() が呼び出されてファイルがクローズされます。もちろん、このときにファイルのアクセス禁止も解除されます。
しかし、yield return によって出来上がるコードは lock { ~ } のときに見たのと同じようなものですから、stream.Dispose() と reader.Dispose() が呼び出されるのは Enumerator の Dispose() メソッドからになります。
呼び出し元が foreach を使っているときは、以下のような感じになるわけです。

foreach (var line in GetLines())
{

    ... line を使って何か処理 ...

}
// foreach を抜けたときに Enumerator の Dispose() が呼ばれて test.txt のアクセス禁止が解除される

このように GetLines() から返ってきた Enumerator での列挙が終わったあとに、foreach によって作られた Enumerator.Dispose() の呼び出しが行われて、ファイルクローズおよびアクセス禁止解除ということになります。

では、yield return を使わない場合はどうなるでしょうか?
以下のようなコードです。

private IEnumerable<string> GetLines()
{
    using (var stream = new FileStream("test.txt", FileMode.Open, FileAccess.Read, FileShare.None))
    using (var reader = new StreamReader(stream))
    {
        var text = reader.ReadToEnd();
        var lines = text.Split('\n');
        return lines;
    }
}

この場合は、GetLines() メソッドから return する時点で using のスコープを抜け、stream.Dispose() と reader.Dispose() が呼び出されてファイルがクローズされ、アクセス禁止が解除されます。
ですから、呼び出し元が上記と同じ foreach を使ったものであっても、foreach の列挙が始まるときにはすでにファイルがクローズされている状態になっています。

で、これはどちらが正しいとか言えるようなたぐいのものではありません。
前者は 「列挙が終わって Enumerator の Dispose() メソッドを呼び出すまでファイルがアクセス禁止になる」、後者は 「Enumerator を作成する間だけファイルがアクセス禁止になる」 ということになるわけですが、どちらにしたいかなんてことはケースバイケースです。
ただ、今回取り上げたような yield return の動作を知らないと 「yield return のところで using のスコープを抜けてファイルはクローズされるんじゃないの?」 と勘違いしやすいとは言えると思います。

というわけで、結局のところ、lock { ~ }、using { ~ }、try ~ finally などスコープに重要な意味があるものの中に yield return を書くときはちょっと注意が必要ではあると思うけど、yield return でいいかどうかなんてことはケースバイケース、という感じですね。

それにしても、yield return についてちゃんと考えたのなんて今回が初めてなんですが、なかなか興味深かったです。

2010年9月9日木曜日

[C#] yield return はスレッドセーフなのか?

スレッドセーフと言ってもいろいろ意味がありますが、ここでは以下のようなコードのことを指してます。

class YieldTest
{
    private List<Hoge> list = new List<Hoge>();

    public IEnumerable<Hoge> GetHoges1(string s)
    {
        lock (this.list)
        {
            var result = new List<Hoge>();
            foreach (var x in this.list)
            {
                if (x.Value == s)
                {
                    result.Add(x);
                }
            }
            return result;
        }
    }

    以下略

見てもらったまんまですが、何かの List があって、この List は別のスレッドからも操作されるからアクセス時には必ず lock すること、というような場合です。
で、上記の GetHoges1() メソッドは以下のように yield return を使って書いてもほとんど同じ意味です。

    public IEnumerable<Hoge> GetHoges2(string s)
    {
        lock (this.list)
        {
            foreach (var x in this.list)
            {
                if (x.Value == s)
                {
                    yield return x;
                }
            }
        }
    }

さて、この yield return を使った GetHoges2() メソッドはスレッドセーフなんでしょうか?

yield return っていうのは、魔法の力で動いているわけではなく、C# コンパイラが自動的に IEnumerator を実装したクラスを作成してくれて動いてるわけです。どんなコードが作成されるのかを言葉で説明するのは難しいですが、yield return や yield break のところでバラバラに分割してうまいこと MoveNext() に収め直すというような感じです。ですから、普通に考えると lock のスコープを抜けてしまいきちんと排他されないんじゃないか?と思えるわけです。
C# 言語仕様」 の 「8.14 yield ステートメント」 を見ると try ~ catch の中に yield は書けない、というようなことが書いてあります。しかし lock に関することは何も書いてありません。

というわけで、実際に生成されたコードを Reflector で見てみました。
すると以下のようになってました。

private class Enamerator : IEnumerator<Hoge>, IDisposable
{
    private List<Hoge> list = (this.list がセットされる)
    private IEnumerator<Hoge> enumertor;
    private bool lockToken = false;

    public Hoge Current { get; set; }

    public bool MoveNext()
    {
        if (最初の実行)
        {
            Monitor.Enter(this.list, ref this.lockToken);
            this.enumertor = this.list.GetEnumerator();
        }
        while (this.enumertor.MoveNext())
        {
            if (条件判定)
            {
                this.Current = this.enumertor.Current;
                return true;
            }
        }

        // 9/10 13時追記
        // コードは省略しますが、ここで下の Dispose() メソッドと同じ処理(Monitor.Exit の呼び出し)が行われます。

        return false;
    }

    public void Dispose()
    {
        if (this.lockToken)
        {
             Monitor.Exit(this.list);
        }
    }
}

上記のコードは意味的にはこんな感じになっているという例であって、実際の自動生成されたコードとはかなり違うんですが、まぁ、こんな感じです。
どうでしょう?ちゃんと、初めて MoveNext() メソッドが呼ばれたときに Monitor.Enter で list に対してロックをかけ、Dispose() メソッドで Monitor.Exit でロックを解除しています。

-- 9/10 13時追記 ここから
上にも書き足しましたが、MoveNext() が false を返すときにも Dispose() と同じ処理が行われるようになっています。なので、Enumerator の Dispose() メソッドを呼び出したときか、MoveNext() が false を返して列挙が終わるときにロックが解除されることになります。
-- ここまで追記

すげぇ!C# コンパイラは lock のことまで考慮して yield return の処理をしてるのか!

。。。と、思いましたが、実は違いますね。
まず、lock ステートメントは C# のシンタックスシュガーで、コンパイルすると Monitor を使ったコードが出来上がります。yield return と違ってそんなにややこしくなく、以下のような try ~ finally で Monitor.Enter と Monitor.Exit を囲んだコードに変換されるだけです。全体を try { ~ } で囲み、finally で Monitor.Exit() を呼び出すことによって途中で例外が発生しようが何しようがスコープを抜けるときには確実にロックが解除されるようになっているわけですね。

lock (this.list)
{
    ...
}

---- 上記をコンパイルすると下記のコードができあがる ----

bool lockToken = false;
try
{
    Monitor.Enter(this.list, ref lockToken);

    // ここに lock { ... } の ... 部分が入る
}
finally
{
    if (lockToken)
    {
        Monitor.Exit(this.list);
    }
}

また、「C# 言語仕様」 の 「8.14 yield ステートメント」 に try ~ catch の中に yield は書けないとありますが、try ~ finally の中には書けるとあります。
で、try ~ finally の中に yield が書いてある場合は、finally の部分が自動生成された Enumerator の Dispose() メソッドの中で実行されるわけですね。
だから、C# コンパイラはいつもどおりに lock ステートメントを解釈し、いつもどおりに yield return を解釈すると、自動的に正しく lock が働くコードが生成されるわけです。

というわけで、まとめ。

lock { ~ } の中で yield return を使ってもきちんとロックされる。

ただし、重要な注意点があります。
上記の自動生成されるコードを見れば明らかですが

lock { ~ } の中で yield return を使った場合は、返された Enumerator の Dispose() メソッドを呼ぶまでロックが解除されない。

なお、多くの場合、yield return で返された Enumerator は foreach で使われるんじゃないかと思います。
foreach ステートメントは Enumerator が IDisposable を実装している場合は自動的に Dispose() メソッドを呼び出すようになっています。(「C# 言語仕様」 の 「8.8.4 foreach ステートメント」。これは C# の最初のバージョンからそういう仕様だったと思います) だから foreach で使う分にはきちんと Dispose() が呼ばれますから何も問題ありません。
というか、Enumerator は多くの場合に IDisposable を実装していますから、それをチェックしてきちんと Dispose() メソッドを呼び出してやらないとまずいことになる可能性大です。foreach を使わずに Enumerator を受け取るときは using を使うなりしてちゃんと Dispose() を呼び出してやりましょう。

そして、ロックが解除されるタイミングが違うというのも大きな違いですね。
最初の例の GetHoges1() では lock のスコープを外れた時点でロックが解除されます。
それに対して GetHoges2() では Enumerator を受け取った側で Dispose() を呼び出した時点でロックが解除されます。ということは、ロックが解除されるタイミングが呼び出し元の処理に依存してしまうわけですね。
なるべく早くにロック解除してやって他のスレッドが動けるようにしてやりたいというのが普通でしょうから、呼び出し元に依存しちゃうのは避けたい場合が多いんじゃないかと思います。

というわけで、本当のまとめ。

lock { ~ } の中で yield return を使ってもきちんとロックされる。しかし、Enumerator の Dispose() が呼ばれるまでロックが解除されないし、いつ Dispose() が呼ばれるかも呼び出し元に依存することになる。
だから lock { ~ } の中で yield return を使うのはやめておいた方がよさげ。

9/10 追記を書きました。
[C#] yield return はスレッドセーフなのか? 追記

2010年9月8日水曜日

[VS] Visual Studio の色設定

へぇ、こんなサイトがあったんですね。
http://studiostyles.info/

Visual Studio 用の色設定がたくさん投稿されています。
背景が黒いやつの方が人気あるみたいですねぇ。私はデフォルトの白背景のまま使ってるから、黒背景は違和感あるなぁ。

ちなみに、使い方は Visual Studio の「ツール」メニューの「設定のインポートとエクスポート」でインポートするだけです。インポートする前に今の設定をエクスポートしておけばあとで元に戻せます。(VS2010 ではインポートするときに今の設定を保存しておくか聞いてくれます)

おまけ
これは有名だと思いますが、VS2010 では Visual Studio Color Theme Editor を入れてやるとメニュー、ツールバー、タブ、タイトルバー、などなどの色を変更することができます。(メニューに「Theme」が追加されます)

2010年9月2日木曜日

[Silverlight] XAP の最適化ツール (と、VS2010 アドインの作り方)

Xaps Minifier. A Step Forward. New Options and New Features」 より。
おぉ、こりゃなるほどだ。

Xaps Minifier という VS2010 のアドインが紹介されています。(私はまだ試してませんが)
どういうものかは part 1 の記事で解説されていますが、ここでも簡単に概要を書いときます。
part 1 の記事の最初がやろうとしてることの解説、残りの大部分は Visual Studio 2010 アドインの作り方の解説になってます)

Silverlight のアプリでも規模が大きくなってくると複数のプロジェクトにわけたりします。Visual Studio では XAP ファイルはプロジェクトごとにできるようになってますから、プロジェクトの数だけ XAP ファイルが出来上がります。
で、XAP ファイルの中にはそのプロジェクトが使っているアセンブリ (DLL) がすべて入っています。なので、他のプロジェクトを参照していたり、共通で使うアセンブリがあったりした場合は、同じアセンブリがあちこちの XAP ファイルの中に入っていることになります。
というか、複数プロジェクトの場合、プロジェクト参照しながらの方が普通だと思うので、ほとんどのアセンブリがダブって入っていると思ったほうがいいくらいじゃないかと思います。
(XAP ファイルは ZIP の拡張子を変えたものですから、拡張子を ZIP にして解凍してみれば簡単に確認できます)

しかし、実行時には同じアセンブリが重複して読み込まれることはありません。
なので、複数の XAP ファイルの中に同じアセンブリを入れておく必要はありません。最初に読み込まれる XAP ファイルにだけ入れておけば十分です。
けど、XAP ファイルが読み込まれる順番はプログラムの作り方次第なのでどれが最初かなんてわかりません。
ただ、スタートアップに指定されている XAP は必ず最初に読み込まれるわけですから、これに含まれているアセンブリは他の XAP に入れておく必要がないことは明らかです。

というわけで、この 「スタートアッププロジェクトに含まれているアセンブリを他の XAP に入らないようにする」 という設定をしてくれるのが Xaps Minifier です。
ほんと、そりゃそうだって感じです。
(もちろん、こういうことが出来るのは他に配布したりする予定がない XAP ファイルだけですが)

あと、複数のプロジェクトで使われているアセンブリをスタートアップの XAP に含めるようにしてもくれるようです。こうすればトータルサイズは当然小さくなりますね。
ただ、これは 「[Silverlight] Silverlight アプリケーションの起動時に関するベストプラクティス」 にある 「起動時間を早くするにはスタートアップの XAP ファイルを小さくしろ」 っていうのの真逆になっちゃいますが。
この辺はケースバイケースで考えるしかないでしょうね。

2010年9月1日水曜日

[IE9] コンテンツ領域が 2 ピクセルくらいサイズが変わるらしい

Making Sites Look Their Best in Standards Mode」 より。
今までまったく気づいてませんでしたが、IE8 まではコンテンツ領域の回りに 2 ピクセルのボーダーがあったんですね。言われてみると確かにあるw

で、どうやら IE9 ではこの 2 ピクセルが無くなるらしい。
ただし、無くなるのはスタンダードモードのときだけで、レガシーモードのときは今までと同じに 2 ピクセルのボーダーがあるとのこと。
スタンダードモードかレガシーモードかはどうやら doctype が HTML5 になってるかどうかで判断するみたい。

いやぁ、やっぱり IE くらいのアプリケーションになると、ここまで後方互換性を気にしてるんだなぁ、と素で感心。というか、こっそり変えちゃっても誰も気づかないんじゃない?ww

9/6 追記
ちゅきさんに頂いたコメントでタイトルが間違ってることに気づきました。
「2ピクセル小さくなる」ってタイトルでしたが、ボーダーが無くなるんだから小さくなるんじゃないですね。けど、右のスクロールバーとか下のステータスバーとかのところにボーダーがあるのかよくわからなかったので、とりあえず「2ピクセルくらいサイズが変わるらしい」というタイトルに変更しました。

[Silverlight][3DCG] SilverMotion (Silverlight 用の 3DCG 描画エンジン)

SilverMotion
Silverlight 用の 3DCG 描画エンジンだそうです。
PostVision という会社の商品みたいですね。(フリー版もあるようです)

Silverlight 自体には 3D 描画機能はありませんから全部自前で(おそらくは C# で)実装したんですね。
デモページ で動いているところが見れます。
私の環境だと、複数ライト + テクスチャマッピング + ノーマルマッピング + 環境マッピングで総ポリゴン数 9万くらいのカエルさんが 45fps くらいで描画できてました。
(オープンソースの Silverlight 用 3DCG 描画エンジンなんかもあったと思いますが、私はそれらのことを知らないので、それらに比べてどうなのかとかはわかりません)

ちなみに Blender, 3DS Max, Maya, MilkShape などからエクスポートした .3DS ファイルに対応しているそうです。


[Silverlight][3DCG] SilverMotion (Silverlight 用の 3DCG 描画エンジン)

SilverMotion
Silverlight 用の 3DCG 描画エンジンだそうです。
PostVision という会社の商品みたいですね。(フリー版もあるようです)

Silverlight 自体には 3D 描画機能はありませんから全部自前で(おそらくは C# で)実装したんですね。
デモページ で動いているところが見れます。
私の環境だと、複数ライト + テクスチャマッピング + ノーマルマッピング + 環境マッピングで総ポリゴン数 9万くらいのカエルさんが 45fps くらいで描画できてました。
(オープンソースの Silverlight 用 3DCG 描画エンジンなんかもあったと思いますが、私はそれらのことを知らないので、それらに比べてどうなのかとかはわかりません)

ちなみに Blender, 3DS Max, Maya, MilkShape などからエクスポートした .3DS ファイルに対応しているそうです。

2010年8月26日木曜日

[.NET] MSDN ライブラリのクラスリファレンスがバージョン選択できるようになってる!けど、、

Improved Topic Versions Now In MSDN Library
を見て知りました。

MSDN ライブラリのクラスリファレンスのところで .NET Framework のバージョンが選択できるようになっています。上記記事によると 8/16 の週の前半に機能追加されたみたいですね。

たとえば、 http://msdn.microsoft.com/ja-jp/library/system.aspx だと以下みたいな感じ

MSDN.jpg

ただし、対応してるのはライトウエイト表示のときだけみたいです。

クラシック表示のときにもできて欲しいなぁ。
確かにクラシック表示の時には左側にツリーがあってそこでバージョンを選べますから、最初からみたいバージョンが決まってる時は不自由しません。けど、.NET Framework 4 と Silverlight 4 を見比べたいなんてこともありますし、他サイトのリンクからやってきて 「あっ、バージョンが違う」 なんてこともありますし。

URL を書き換えてやれば違うバージョンに飛ぶことは出来ることは出来るんですが。
たとえば、 http://msdn.microsoft.com/ja-jp/library/system.aspx のときは

  • http://msdn.microsoft.com/ja-jp/library/system(v=VS.100).aspx とすると .NET Framework 4
  • http://msdn.microsoft.com/ja-jp/library/system(v=VS.95).aspx とすると Silverlight

というように .aspx の前でバージョンを指定できます。(”v=” は省略してもいいみたい)
けど、正直めんどくさい。
各バージョンの対応は以下のようになってるみたいです。

.NET Framework 4 (v=VS.100)
.NET Framework 3.5 (v=VS.90)
.NET Framework 3.0 (v=VS.85)
.NET Framework 2.0 (v=VS.80)
.NET Framework 1.1 (v=VS.71)
Silverlight (v=VS.95)

2010年8月25日水曜日

[Silverlight] MEF を使って XAP を動的に読み込む その2

[Silverlight] MEF を使って XAP を動的に読み込む その1」 の続きです。

今回は XAP を動的に読み込んでみる例です。

なお、Visual Studio 2010 のソリューションを http://cid-ca42d76a68f54d16.office.live.com/self.aspx/Public/Sample/MefSample.zip に置いておきます。
ZIP ファイル内の MefSample02 フォルダが以下の例です。
この例では、本体である MefSample02 が Page1.xap、Page2.xap を読み込んでいます。そしてボタンクリックで Page1.xap や Page2.xap に入っているコントロールを表示しています。

まずは本体側の XAML と C# コードです。

<UserControl x:Class="MefSample02.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400"
    Loaded="OnLoaded">

    <StackPanel x:Name="LayoutRoot" Background="White">
        <StackPanel Orientation="Horizontal">
            <Button x:Name="button1" Content="Page1" Click="button1_Click"/>
            <Button x:Name="button2" Content="Page2" Click="button2_Click"/>
        </StackPanel>
        <StackPanel x:Name="panel1"/>
    </StackPanel>
</UserControl>
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Windows;
using System.Windows.Controls;

namespace MefSample02
{
    public partial class MainPage : UserControl
    {
        [ImportMany("Page", AllowRecomposition = true)]
        public List<FrameworkElement> controls = new List<FrameworkElement>();

        public MainPage()
        {
            InitializeComponent();
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            var catalog = new AggregateCatalog();
            catalog.Catalogs.Add(CreateCatalog("Page1.xap"));
            catalog.Catalogs.Add(CreateCatalog("Page2.xap"));
            var container = new CompositionContainer(catalog);
            container.ComposeParts(this);
        }

        private DeploymentCatalog CreateCatalog(string uri)
        {
            var deploymentCatalog = new DeploymentCatalog(uri);
            deploymentCatalog.DownloadCompleted += (s, e) => { /*ダウンロード完了イベント*/ };
            deploymentCatalog.DownloadProgressChanged += (s, e) => { /*ダウンロード進行中*/ };
            deploymentCatalog.DownloadAsync();
            return deploymentCatalog;
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            NavigatePage("Page1");
        }

        private void button2_Click(object sender, RoutedEventArgs e)
        {
            NavigatePage("Page2");
        }

        private void NavigatePage(string pageName)
        {
            this.panel1.Children.Clear();
            foreach (var page in this.controls)
            {
                if (page.Name == pageName)
                {
                    this.panel1.Children.Add(page);
                    break;
                }
            }
        }
    }
}

controls フィールドに ImportMany 属性を付けて、ここにインポートされるようにしています。前回と違って controls は FrameworkElement のコレクションなので Import では無く ImportMany 属性を使っています。
そして、インポートの解決をしているのが OnLoaded イベントの

var catalog = new AggregateCatalog();
catalog.Catalogs.Add(CreateCatalog("Page1.xap"));
catalog.Catalogs.Add(CreateCatalog("Page2.xap"));
var container = new CompositionContainer(catalog);
container.ComposeParts(this);

の部分です。
カタログは CreateCatalog() メソッドで作っています。DeploymentCatalog クラスという、XAP を読み込んでインポート元として使えるようにしてくれるそのものズバリなクラスが用意されていますからそれを使っています。なお、DeploymentCatalog クラスは今のところ Silverlight 4 にしかありません。(MEF は .NET Framework 4 にも入ってますが DeploymentCatalog クラスは .NET Framework 4 には無いみたいです)
AggregateCatalog は複数のカタログをひとつに束ねるカタログです。
そしてコンテナを作り、this へのインポートを実行を依頼しています。

前回紹介したようにデフォルトのコンテナを使うこともできます。
その場合は以下のようになります。

var catalog = new AggregateCatalog();
catalog.Catalogs.Add(CreateCatalog("Page1.xap"));
catalog.Catalogs.Add(CreateCatalog("Page2.xap"));
CompositionHost.Initialize(catalog);
CompositionInitializer.SatisfyImports(this);

実は CompositionHost.Initialize() メソッドには複数のカタログを渡せるようになっているので、それを使うと AggregateCatalog で束ねてやる必要も無くなります。以下のような感じ。

CompositionHost.Initialize(
    CreateCatalog("Page1.xap"),
    CreateCatalog("Page2.xap"));
CompositionInitializer.SatisfyImports(this);

ソースの説明に戻って、残りの部分はボタンクリックイベントでコントロールを表示しているだけです。
button1_Click()、button2_Click() イベントで、MEF が controls フィールドに格納してくれた Page1.xap、Page2.xap の中のコントロールを名前で探して panel1 に配置しています。(名前で探せるように Page1.xap、Page2.xap の中の UserControl には x:Name=”Page1” のように名前を付けてあります)

本体側でやっているのは以上です。
続いてエクスポートする Page1、Page2 側。

Page1 という名前の Silverlight アプリケーションのプロジェクトを作ります。これをビルドすると Page1.xap という名前の XAP ファイルができあがることになります。
その Page1 の XAML とコードは以下のような感じです。

<UserControl x:Class="Page1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400"
    x:Name="Page1">

    <Grid x:Name="LayoutRoot" Background="Blue">
        <TextBlock>Page1</TextBlock>
    </Grid>
</UserControl>
using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;

namespace Page1
{
    [Export("Page", typeof(FrameworkElement))]
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }
    }
}

見ての通りかなり簡単なものです。
MEF のためにやっていることと言えば MainPage クラスに Export 属性を付けていることだけです。
Export 属性に typeof(FrameworkElement) と書いていますが、これには意味があります。MEF では名前と型でインポート・エクスポートの解決を行うようです。Import の側が List<FrameworkElement> と FrameworkElement のコレクションになっていますので、Export する側でも typeof(FrameworkElement) として FrameworkElement 型でエクスポートすることを明示してやらないといけません。これがないと MainPage 型としてエクスポートすることになってしまい、インポートする側と型が一致しないのでインポートしてくれなくなっちゃいます。
あと、ボタンクリック時に名前で探せるように XAML の UserControl に x:Name=”Page1” を追加してあります。

Page2.xap も内容的には同じようなものです。

こんな風にすると XAP を読み込んでその中のクラスやプロパティをインポートすることができます。
今回は UserControl をエクスポートしていますが、MEF は単に 「クラスやプロパティをエクスポート・インポートする仕組み」 ですから使い方次第でいろんなことに応用できるんじゃないかと思います。

ちょっと注意
DeploymentCatalog は非同期で XAP をダウンロードします。
なので、上記のサンプルで言うと container.ComposeParts(this) を実行したからと言ってその直後に controls フィールドが更新されるとは限りません。XAP のダウンロードに時間がかかった場合には遅れて非同期で controls フィールドが更新されます。また、順序もダウンロードが速く終わった順になるようです。なので controls フィールドの内容は Page1、Page2 の順かもしれませんし、Page2、Page1 の順かもしれません。
必要であれば、DeploymentCatalog の DownloadCompleted イベントなんかを使えばダウンロード完了を知ることができます。(サンプルでは何もしない空のハンドラを指定してるだけですが)

なお、サンプルでは名前でコントロールを探すようにしているのでダウンロードに時間がかかってまだ該当するコントロールが読み込まれていない場合にもエラーにはならず単に何も表示されないだけとなります。また、名前で探すので controls の格納順も問題になりません。
ダウンロードに時間がかかるさまをテストしたい場合は Page1 プロジェクトなどに何でもいいのでサイズの大きいファイルを追加して、そのファイルのビルドアクションを 「コンテンツ」 にすればいいんじゃないかと思います。こうすると XAP ファイルにそのファイルが含まれるので XAP ファイルのサイズを簡単に大きくすることができます。

ん?あれ?
よくよく考えたら、これって非同期で controls フィールドの内容が更新されるってことだよな。
ということは何も考えずに controls フィールドにアクセスしちゃまずいってことになるな。
MEF って controls フィールドを更新するときに SyncRoot で look してくれてるのかな?そうじゃないとどうしようも無くなってしまうような気がする。うーん、どうなんだろ?

最後に
と、MEF の基本的なところと XAP を動的にダウンロードして読み込むサンプルを示してみました。
ざっくりしたものではありますが、MEF がどんなものなのかはわかって頂けるんじゃないかと。
私もよくわかってませんが、他にもいろいろとできたりします。今回は Import 属性、Export 属性を使うやり方にしましたがリフレクションベースで指定する方法もあるようです。あとは、Lazy<T> を活用するとより柔軟なインポートができるとかなんとか。。。
あっ、そうそう、メソッドを Export することもできます。この場合の Import の型は Action、要するにデリゲートで受け取ることになります。使い方によってはおもしろいかもしれません。

2010年8月24日火曜日

[Silverlight] MEF を使って XAP を動的に読み込む その1

前の記事で Silverlight 4 では MEF (Managed Extensibility Framework) を使って XAP を動的に読み込めばいいんじゃないかと書きましたが、せっかくなのでサンプルを紹介しときます。
内容自体は VSUG Day 2010 Summer 大阪 のセッションで紹介したのとだいたい同じだったりしますが。

まず、MEF っていうのはプラグインといった仕組みを実現するフレームワークだと思ってもらえばいいんじゃないかと思います。結構いろいろな機能があるため全体としてはそれなりにややこしくなっていますが、基本的な部分を使うだけならば、いくつかの約束ごとを覚えればなんとかなる感じです。
私も全体をきちんと理解出来ているわけではありませんが、単純な例と XAP を動的に読み込む例の 2回に分けて MEF の概要を紹介してみます。
なお、MSDN ライブラリの解説は 「Managed Extensibility Framework の概要」 などにありますから詳しい話はそちらを参照してみてください。(残念ながら MSDN ライブラリの解説もすべてを網羅してるわけではありませんが)
ちなみに、MEF は 「メフ」 と発音されることが多いみたいです。(Microsoft の人のチュートリアル動画を見てたらそう発音してた)

では、まず手始めに単純な例。
(C# で書いてますがもちろん VB でも同じです)

なお、Visual Studio 2010 のソリューションを http://cid-ca42d76a68f54d16.office.live.com/self.aspx/Public/Sample/MefSample.zip に置いておきます。
ZIP ファイル内の MefSample01 フォルダが以下の例です。

Visual Studio 2010 で Silverlight 4 プロジェクトを作ります。
MEF の多くは System.ComponentModel.Composition.dll に含まれてますので、まずはこれを参照設定します。
続いて以下のような内容の Person.cs を追加。

using System.ComponentModel.Composition;

namespace MefSample01
{
    public class Person
    {
        public Person()
        {
            this.Name = "MEFサンプル";
        }

        [Export("PersonName")]
        public string Name { get; set; }
    }
}

次に Silverlight のメインページのクラス (MainPage.xaml.cs) を以下のように書き換えます。

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;

namespace MefSample01
{
    public partial class MainPage : UserControl
    {
        [Import("PersonName")]
        public string personName;

        public MainPage()
        {
            InitializeComponent();

            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            container.ComposeParts(this);

            MessageBox.Show(this.personName);
        }
    }
}

これで実行してみると実行直後に “MEFサンプル” という内容のメッセージボックスが表示されるはずです。
このメッセージボックスは personName フィールドの内容を表示してるんですが、一見すると personName フィールドに内容をセットするコードが見当たりません。もちろん、本当にセットされていなかったら null 参照で例外が出ちゃうので、どこかではセットされてるんですが。
それをやってるのが

var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);

この 3行です。そして、この 3行が MEF のキモと言っていい部分です。

まず 1行目。ここでカタログを作っています。カタログというのは 「エクスポートしたいもの」 の所在を指定するもの、と思ってもらえばいいんじゃないかと思います。上のサンプルでは AssemblyCatalog を使って今現在実行中のアセンブリをカタログとして指定しています。(要するに自分自身をカタログに指定しているってことです) カタログには、AssemblyCatalog の他にも、ディレクトリの中のアセンブリを探してくれる DirectoryCatalog、XAP をダウンロードしてくれる DeploymentCatalog などがいくつかの種類があります。
次の 2行目でコンテナを作っています。
そして、3行目でこのコンテナに対して自分 (this) のインポートを解決するように依頼しています。
この 2行目と 3行目はお約束と思ってもらっていいんじゃないかと思います。

この 「カタログ作って、コンテナにカタログ入れて、そのコンテナにインポートの解決を依頼」 というのが MEF のキモなわけですが、もうひとつのキモがインポートとエクスポートの指定です。

MainPage クラスの personName フィールドには Import 属性が、そして、Person クラスの Name プロパティには Export 属性が付いています。
MEF のインポートの解決とは、

  • container.ComposeParts() メソッドに渡された引数のオブジェクト (今回の場合は this) の中の Import 属性が付いているものを探す。
  • コンテナの中の Export 属性が付いているものを探す。
  • 名前と型が一致するものがあったらエクスポート側から値を取り出し、インポート側にセットする。

ということです。
ちなみに、エクスポート側から値を取り出し、インポート側にセットするというのは、具体的に書くと

var person = new Person();
this.personName = person.Name;

というのと同じことが実行されています。

あと、MEF のサンプルを見ていると、上記の 3行が

var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
CompositionHost.Initialize(catalog);
CompositionInitializer.SatisfyImports(this);

こんな感じに書かれているものがあります。
これは自分で明示的にコンテナを作らずに、MEF に内蔵されているデフォルトのコンテナを使っているということみたいです。ただ、デフォルトのコンテナを使うことにどんな利点があるのかは私は良くわかってません。
ちなみに CompositionHost などを使う場合は System.ComponentModel.Composition.Initialization を参照設定する必要があります。

この例ではプロパティを Export しましたが、クラスを Export することもできます。
次の例ではクラスを Export し、XAP をコンテナとして指定して動的に XAP を読み込むようにしてみます。

[Silverlight] MEF を使って XAP を動的に読み込む その2」 に続きます。

2010年8月23日月曜日

[Silverlight] Silverlight アプリケーションの起動時に関するベストプラクティス

Silverlight Startup Best Practices」 より。
Silverlight アプリの起動を速くするために気をつけるべきことがまとめられていました。
なかなかおもしろかったのでざっと和訳してみました。

それにしても Info-ZIP で圧縮しなおしたらそれだけで 20% くらい小さくなるとか、ちょっと驚き。というか、起動時間を気にしなくちゃいけないくらいの規模のアプリにとっては 20% って大きいよな。
あと、紹介されている Tim Heuer 氏のビデオ 「Loading Dynamic XAPs and Assemblies」 ですが、これは Silverlight 2 のころの内容です。Silverlight 4 では MEF (Managed Extensibility Framework) を使えばいいんじゃないかと思います。

では、以下和訳。Introduction と Conclusion 以外はだいたい訳してますが、一部はしょったり適当だったりします。あと、そもそもそんなに英語力があるわけじゃないので間違ってるところもあるでしょう。なので、きちんとした内容は原文を見てください。

---- ここから和訳したもの

■ ダウンロードサイズを最小にしろ
起動する前にまずはダウンロードしなくちゃいけないわけだから、ダウンロードサイズは起動時間に直結する。複数の XAP ファイルに分けることができないか検討すべし。メイン画面に必要な部分と中核となる機能の部分だけを最初の XAP ファイルに入れておく。Tim Heuer 氏のビデオ 「Loading Dynamic XAPs and Assemblies」 にこういったやり方の詳細が解説されている。

他にもすばらしい方法がある。XAP ファイルを圧縮するんだ。Silverlight の XAP ファイルは ZIP ファイルをリネームしたものだけど、Silverlight 4 の XAP 圧縮アルゴリズムは最高のものってわけじゃない。最適な圧縮アルゴリズムの物を使って ZIP 圧縮しなおせばダウンロードサイズを 20% くらい (最高では 70% くらい) 小さくできるかも。詳細は David Anson 氏の 「Smaller is better!」 を参照。

■ ネットワーク I/O を待つな
これは起動中にしちゃうことの中でもっともリスキーなことのひとつ。ネットワーク I/O の待ち時間は一定ではない。Web サービスを呼び出したりネットワークからデータを取り出したりするとき、その要求は 2ミリ秒で済むかもしれないし、2秒かもしれないし、2分かもしれないし、それ以上かもしれない。UI を表示する前にデータが必要な場合は画面を表示しようがないかもしれない。一番いいのは、起動時間を決めるのがネットワーク接続の速さだってことに賭けちゃうってことかな。 せいぜい、アプリの起動時間を決定づけるネットワーク速度がそこそこ速いということに期待するぐらいしかなかろう [訳注: 原文は “At best, you are gambling on the speed of your network connection to determine your startup time.” こんな訳でいいのかまったく自信なし。いいとしたら、当然これは文章通りの意味ではなく、「だからそんなことにならないようにうまいこと考えろ」 ということだと思われ] [8/24 追記: コメントにてやねうらおさんに正しい訳を教えていただいたので修正。ただ、原著者さんは 「だからそんなアプリの作りにはするな」 ということが言いたいのであろうことは変わりません]

■ ディスク I/O を最小にしろ
ディスクからロードするデータが増えれば待ち時間も増える。

・起動時に読み込むアセンブリを少なくする
ディスク I/O を少なくするにはアプリのロード時に読み込まれるアセンブリを少なくすることだ。Silverlight では、CLR の just-in-time (JIT) コンパイラがあるメソッドに最初にアクセスしたときに、そのメソッドを含むアセンブリが読み込まれる。例: 単純な “Hello World” アプリに DataGrid を加える。すると DataGrid が含まれている System.Windows.Controls.Data.dll が起動時に読み込まれるようになる。Sysinternals の VMMap を使えば読み込んでいるアセンブリとそれぞれのメモリ使用量を見ることができる。[訳注: 原文には WMMap の使用例のスクリーンショットあり]

アセンブリはそれの型のどれかが使われるまで読み込まれないので、2つのやり方を覚えておくといい。

  1. スタティックメンバーはアセンブリの読み込みの依存関係に強く影響する。たとえば、DataGrid をスタティックフィールドに持つようなコードがあると、起動時にそのスタティックフィールドを初期化するために System.Windows.Controls.Data.dll が読み込まれる。これは System.Lazy<T> を使えば回避できる。詳細は、MSDN の 「Lazy の初期化」 などを。
  2. 必要になったときにアセンブリが読み込まれるようにメソッドを書き直す。
    [訳注: 以下、とりあえず原文のままコード例をコピペ。コンストラクタで生成するんじゃなく OnLoaded で生成するようにすれば起動時にアセンブリが読み込まれなくなる、ということだとはわかる。MehodImplOptions.NoInlining でインライン展開されないようにするってのはどういう意味なんだろ?インライン展開されても OnLoaded に組み込まれるだけだと思うんだけど。”if (b == true)” もわからないな。というかこれって最適化されたら if 文自体が消されちゃうと思うんだけど。]
private void OnLoaded(object sender, RoutedEventArgs e)
{
    bool b = true;
    if (b == true)
    {
        // Instead of this, refactor into a method, this will
        // prevent System.Windows.Controls.Data.dll where
        // DataGrid lives (and which is not one of the core
        // SL assemblies) from getting loaded.
        //   DataGrid myDataGrid = new DataGrid();
        //   LayoutRoot.Children.Add(myDataGrid);
        AddDataGrid();
    }
} 
//prevent in-lining if method implementation is too small.
[MethodImpl(MethodImplOptions.NoInlining)] 
public void AddDataGrid()
{
    DataGrid myDataGrid = new DataGrid();
    LayoutRoot.Children.Add(myDataGrid);
}

・データの読み込みを減らす
最初の表示に必要無いコンテンツやコンフィギュレーションデータの読み込みを避ける。例: フラットファイルにメッセージデータを保存している email クライアントがある。この場合、メイン画面が表示されてからメッセージデータを読み込むようにすべき。そして、ロード中アニメーションを表示するか、もしくは、操作可能であることを示すようなものを表示する。
他の例: 拡張可能なプラグインモデルのアプリケーション。この場合、メイン部分を表示してから各プラグインを読み込む。そうすれば無作法なプラグインによって起動に余計な時間がかかるのを防ぐことができる。

■ テンプレートとスタイルの最適化
アプリの初期表示が定義されているいくつかの XAML を起動時にパースする必要があるのは避けられない。だから、起動時間のために初期表示に使われる XAML を最適化すべき。

  • 要素数を最小に。ビジュアルツリー上の要素数がパースの時間に影響する。XAML をリファクタリングしたとき、無意味になった要素を見つけることがある。子がひとつしかなく、意味あるプロパティを持たないような Grid なんかがいい例だ。こういう要素はとっとと削除すべき。
  • 死んだ XAML を削除しろ。スタイルやツリーの一部に使っていないものがあったら削除するように!見えもしないし使うこともないものをなんで取っておく?
  • ユーザーコントロールにはテンプレートを使え。ユーザーコントロールはインスタンス化のたびにパースされるが、テンプレートは最初の一度だけパースされる。特に、同じスタイルのセルが何百もあるような DataGrid では DataGrid セル・テンプレートを使うことがとても重要。もしセルにユーザーコントロールが使われていたら何百回も同じ XAML をパースすることになるがテンプレートを使っていれば一回で済む。
  • プロパティにデフォルト値をセットするな。Opacity=”1” だとか、値なしの RenderTransform をセットするだとかいうような。デザインツールの中にはこういった嫌な癖を持つものもあるから出力結果を監視した方がいいかもしれない。我々もこの点がもっと良くなるように作業していく。

■ スプラッシュスクリーンの使うか検討する
起動時に関するベストプラクティスをいろいろ実装しても、まだ機敏さが足りないと思ったらどうする?そんなときはスプラッシュスクリーンを使うことを検討すべき。Silverlight アプリはデフォルトのスプラッシュスクリーンをもってる。(球体がくるくる回るやつ) このデフォルトのスプラッシュスクリーンを変更すれば起動時間が早くなったように見えるし、自分のブランドをすぐにユーザーに見せることができる。スプラッシュスクリーンを置き換える方法については 「方法 : Silverlight の単純なスプラッシュ スクリーンを定義する」 を参照。

2010年8月18日水曜日

[F#] F# の開発環境

Announcing the F# 2.0 Standalone Tools Update (for .NET 2.0, 4.0 and other CLI Implementations)」 より。
(私自身は F# はまったく触ったこと無いんですが)

どうやら F# のスタンドアローンの開発環境の新しい CTP がリリースされたようです。
以前からある “Visual Studio 2010 Shell” を入れておいて、今回リリースされた “the F# 2.0 August 2010 MSI” を入れると F# が使えるようになるみたい。よくわかってませんが、要するに Visual F# Express Edition みたいなもんだと思えばいいのかな?

ちなみに、一番下に 「Visual Studio 2010 Professional かそれ以上ですでに F# を使ってる人は気にする必要は無い。今回のでサポートされている F# のバージョンは今までのと同じだ」 というようなことが書かれてますのでそういうことみたいです。

2010年8月13日金曜日

[C#][VB][C++] All-In-One Code Framework Coding Standards

All-In-One Code Framework というサンプル集は有名ですが、その All-In-One Code Framework を作っているプロジェクトチームが C#、VB、C++ でコードを書く上でのコーディング規約をまとめたそうです。
http://1code.codeplex.com/releases/view/50431

ちょこっと中身を見てみましたが、コメントはこう書くべしとか、”{“ の位置はここにすべしとか、命名はこうすべしとか、よくあるコーディング規約です。ただ、それだけじゃなく、例外の使い方とか Dispose パターンの使い方とか P/Invoke を使うときの書き方とか結構突っ込んだところまで書いてあります。

なお、「VC++, C#, VB.NET Coding Guideline of All-In-One Code Framework」 によると、継続的に進化中なのでもっといい方法とか追加すべきことがあったらメール頂戴とのこと。(ガイドラインにも同じことが書いてありました)

いいなぁ、これ。日本語版なんて出ないんだろうなぁ。

2010年8月11日水曜日

[Win7] こんなショートカットがあったのか

Windows 7 Tip: Aero Snap Feature and Multiple Monitors」 より。
Windows 7 に標準であるキーボードショートカットについてです。

アクティブウインドウを左にやったり右にやったりするショートカットなんですが、マルチディスプレイのときは隣のディスプレイに移動するようになってます。

  • Windows キー + 左カーソルキー … アクティブウインドウが左側に張り付く。すでに左側だったときは左隣のディスプレイの右側に行く。
  • Windows キー + 右カーソルキー … アクティブウインドウが右側に張り付く。すでに右側だったときは右隣のディスプレイの左側に行く。
  • Windows キー + 上カーソルキー … アクティブウインドウが最大化する。すでに最大のときは元に戻る。
  • Windows キー + 下カーソルキー … アクティブウインドウがアイコン化する。

紹介されているのは以上ですが、ひょっとして他にもあるかも、と思いやってみたらみつけました。

  • Windows キー + Shift キー + 左カーソルキー … アクティブウインドウが左隣のディスプレイに移動。
  • Windows キー + Shift キー + 右カーソルキー … アクティブウインドウが右隣のディスプレイに移動。

おぉ、こんなのあったのか!

って、改めて見てみたらちゃんと 「Windows 7 のキーボード ショートカット集」 ここにも載ってるんですけどね。

[LINQ] CompiledQuery を使ったときの LINQ のパフォーマンスの注意点?

Potential Performance Issues with Compiled LINQ Query Re-Compiles」 より。
何度も実行するクエリーは CompiledQuery を使ってキャッシュするとパフォーマンスを向上することができます。しかし、気をつけないとリコンパイルされてしまってパフォーマンスに影響する場合があるので注意、というような内容です。
紹介されているクエリーはこんな感じ。

static Func<NorthwindEntities, string, IQueryable<Customer>> compiledCustQuery =
   CompiledQuery.Compile((NorthwindEntities ctx, string start) =>
   (from c in ctx.Customers
    where c.CustomerID.StartsWith(start)
    select c));

これを

var qryAnyCust = compiledCustQuery(ctx, "C");
if (qryAnyCust.Any())
{
    var qryCust = compiledCustQuery(ctx, "C");
    qryCust.ToList().Count();
}

このように使う場合の問題点が紹介されています。
Any() を使って 「該当データが存在すれば○○、しなければ××」 みたいなことをする場合ってことですね。
(2回 compiledCustQuery を呼び出さずに 1回にまとめられるだろ、というのは横に置いておいて。あくまで例ってことで)
compiledCustQuery がコンパイル済みクエリーなんですが、Any() を使うとリコンパイルを引き起こすそうです。
ではどうするんだ、というと、

static Func<NorthwindEntities, string, bool> compiledAnyCustQuery =
   CompiledQuery.Compile((NorthwindEntities ctx, string start) =>
   (from c in ctx.Customers
    where c.CustomerID.StartsWith(start)
    select c).Any());
if (compiledAnyCustQuery(ctx, "C"))
{
    var qryCust = compiledCustQuery(ctx, "C");
    qryCust.ToList().Count();
}

こんな風に Any() の呼び出しまで含めたコンパイル済みクエリーを別に用意してやればよい、とのことです。
これで 9ms だったものが 6ms と 33% も速度が向上したそうです。

と、ここまで読んで、ちょっと疑問が。。。
リコンパイルされるってほんと?

もともと、LINQ to SQL の仕組みは

コンパイル時にやること
LINQ 構文を本来の拡張メソッド (Queryable.Where() とか Queryable.Select() とか) に置き換えてそれらを呼び出すコードを作るだけ。
ちなみに、Queryable.Where() の引数はラムダ式で書かれた条件式が式木 (Expression Tree) に変換されたものが渡されるようなコードができあがる。これは引数の型が Expression<Func<TSource, bool>> とかになってるのでコンパイラがラムダ式を Exression 型へと暗黙の型変換してくれるから。Select() とかも同じ理屈。

実行時にやること
LINQ to SQL プロバイダが Where() やら Select() やらに渡された式木を見つつ SQL 文を構築する。
そして、実際に SQL を実行して結果を得る。

のはず。
これは CompiledQuery クラスを使っていても基本的には同じ。
ただ、実行時の遅延実行のタイミングが微妙に変わります。
というか、私もきちんとは知らなかったので SQL Server のトレースログを取って確認してみました。
そうしたら以下のようになってました。

var qryCust = compiledCustQuery(ctx, "C");
    ↑この行を実行した時点で SQL 文が構築される。
     (SQL 文はキャッシュされて 2回目以降は使いまわされる)
     また、この時点で SQL Server に接続し、SQL 文が発行される。

foreach (var row in qryCust)   ←データの取得はこの段階で行われる。
{
    
}

このように CompiledQuery.Compile() メソッドで取得したデリゲートにアクセスした時点で SQL 文が構築され、SQL Server に接続し、SQL 文が発行されていました。
ちなみに、CompiledQuery を使っていないときは foreach などで最初に結果セットにアクセスしたときに SQL 文構築、SQL Server への接続、SQL 文の発行が行われます。

ということを踏まえて Any() の話。
紹介した記事では Any() を呼びだすとリコンパイルされる、となっていますが本当でしょうか?
実際に DataContext.Log や SQL Server トレースログを見てみましたが、Any() を呼び出す時点では SQL 文の構築らしきことは何もされていません。

var qryAnyCust = compiledCustQuery(ctx, "C");
    ↑この行を実行した時点で SQL 文構築、SQL Server 接続、SQL 文発行。

if (qryAnyCust.Any())   ←SQL 文を再度発行なんてことはしていない。
{
    
}

上で示した CompiledQuery の遅延実行の流れとまったく同じですが、このように Any() を呼び出す前の段階で SQL 文の発行までは終わってました。Any() は IQueryable を通じて結果セットにアクセスしてるだけにしか見えません。

ですから、Any() を使うとリコンパイルされるということは無いんじゃないかと思います。

では、Any() まで含んだ CompiledQuery を用意してやると 33% も速くなったという話。
これは、試してみればわかりますがそもそも構築される SQL 文が違います。

SELECT
    (CASE
        WHEN EXISTS(
            SELECT NULL AS [EMPTY]
            FROM [dbo].[Customers] AS [t0]
            WHERE [t0].[CustomerID] LIKE @p0 ESCAPE '~'
            ) THEN 1
        ELSE 0
     END) AS [value]

Any() が付いてるとちゃんと 「条件に該当するものが 1件でもあれば 1、なければ 0 を返す」 という SQL 文を作ってくれます。実行している SQL 文が違うんですから、そりゃ速度も違うでしょうし、普通に考えてこっちの SQL 文の方が効率いいでしょうね。
しかし、Any() があるとこんな SQL 文を作ってくれるなんて、ほんとに LINQ to SQL のプロバイダーはよくできてるなぁ。

あと、紹介した記事の後半に IQueryable の代わりに IEnumerable を使うという例が紹介されていますがこれはよくわかりません。
IEnumerable を使うと一気にメモリに取り込むからそれに Any() を適用してもそれなりに速い、ということみたいですが、私が試してみたところでは IQueryable の場合とほとんど変わらないような感じでした。理屈的に考えても、結果セットの大きさが小さい場合はあまり変わらないように思うんですが。。。

■ 結論
Any() を使ったからといってリコンパイルされることは無いですし、遅くなるということもありません。(と思う)
けど、CompiledQuery で取得したものに後から Any() を付け足したりすると非効率になる場合があるのは確か。きちんと Any() まで含んだクエリーを CompiledQuery にした方が効率が良くなる場合が多い、とは言えると思います。まぁ、どっちの方がいいかは、構築された SQL 文を見てみないとわからないって場合もあるとは思いますが。

一応書いておきますが、ずっと Any() を例にしてますが、もちろん他のメソッドでも同じです。Any() が特別なことをしているというわけではありません。

2010年8月6日金曜日

[Silverlight] Zoom.it

Microsoft Live LabsSeadragonZoom.it になったそうです。
(正確には Seadragon の Deep Zoom フォーマットを作ってくれるサービスが Zoom.it になったということかな?)

Zoom.it は Web 上のイメージを Deep Zoom フォーマットに変換して公開できるようにしてくれるサイトです。
JPEG や PNG といった画像の URL を与えるとそれを Deep Zoom フォーマットにしてくれます。また、Web サイトの URL を与えるとその Web サイトのイメージを画像化して Deep Zoom フォーマットにしてくれます。
表示には Silverlight が使われています。ちなみに、Zoom.it のサーバー側は Windows Azure が使われているそうです。

試しにこのブログ自体を Create して埋め込んでみました。
(えらく縦長に。そりゃ、このブログの URL をそのまま渡せばそうなるわな。ちなみに、Create には数分間くらいかかりました)

Zoom.it は API も公開しています。
この API とは、URL を与えるとそのイメージを Deep Zoom フォーマットに変換してくれる、というものです。

API Quickstarts - JavaScript」 には、jQuery で Zoom.it を呼び出して Deep Zoom フォーマットに変換、その結果の URL を Seadragon Ajax Viewer に与えて Ajax で表示するというサンプルがあります。
また、「API Quickstarts - Silverlight」 には、Silverlight helper library を使って Zoom.it で Deep Zoom フォーマットに変換、その結果の URL を DeepZoomImageTileSource に渡してそれを MultiScaleImage.Source にセットして表示するというサンプルがあります。ちなみに、DeepZoomImageTileSource や MultiScaleImage は Silverlight の標準クラスです。
(Zoom.it だったり Seadragon だったり Deep Zoom だったりと、同じようなものが違う名前で呼ばれていてちょっとややこしい)

ところで、以下のサイトって 70ギガピクセルの画像を Silverlight で表示してるんですが、これって Deep Zoom なのかな?
http://70gigapixel.cloudapp.net/index_en.html

70gigapixel.jpg

2010年8月5日木曜日

[IE9] ついに ActiveX Scripting Engine がお払い箱に!

HTML5, Modernized: Fourth IE9 Platform Preview Available for Developers
この記事を見てて知りました。

記事の前半は 「IE9 のプレビューでは SVG、canvas、video、audio、text がハードウエアアクセラレーションされてめちゃ速になったよ」 というような話ですが、中程に 「Native JavaScript Integration」 なんていう話が。

今までの IE は JScript や VBScript のエンジンを内蔵しているわけではなく、別に存在するこういった言語を解釈・実行するエンジンを呼び出してるだけでした。そのエンジンのことを ActiveX Scripting Engine なんていうわけです。これは COM ベースなので、きちんとプログラミングすれば自分で作っているアプリに JScript や VBScript を使ったスクリプティング機能を持たせることができたりするわけです。
検索してみたら、まだ一応ドキュメントは残ってるみたいですね。「Microsoft Windows Script Interfaces - Introduction

しかし、普通に考えて、こういう汎用的に作られたエンジンはパフォーマンスなんかが犠牲になったりするわけです。で、ついに IE9 では JavaScript のエンジンを内蔵するようにしたそうです。(”Chakra” というのがコードネームとのこと)
すると、IE8 に比べて何倍も速くなったそうです。

そうそう、タイトルには 「お払い箱」 なんて書いちゃいましたが、内蔵されるのは JavaScript だけで、VBScript などは依然として ActiveX Scripting Engine を使うみたいですね。過去の資産が使えるようにってことでしょうね。さすがに、HTML5 アプリを作るのに VBScript を使う人はいないでしょうし。

ところで、IE9 って Web Strage とか Web SQL Database とかもサポートするのかな?あと WebGL とか WebSockets とかは?

2010年8月4日水曜日

[Silverlight] ビヘイビアーの開発 その4

[Silverlight] ビヘイビアーの開発 その3」 の続きです。

TargetedTriggerAction<T> の補足
[Silverlight] ビヘイビアーの開発 その2」 で書いた TargetedTriggerAction<T> の補足です。
Target プロパティの型が T になっていると書きましたが、(それはそうなんですが)、TargetedTriggerAction<T> では AssociatedObject プロパティの型が DependencyObject となっています。

Behavior<T>、TriggerAction<T> では、たとえば、T を Button にすれば Blend 上でボタンとその子孫のオブジェクトにしかドロップできなくなります。しかし、TargetedTriggerAction<T> では T は AssociatedObject の型ではなく Target の型の指定になっているためこういったことができません。そこで TypeConstraint 属性が用意されています。

[TypeConstraint(typeof(Button))]
public class TargetedTriggerActionSampleBehavior : TargetedTriggerAction<FrameworkElement>
{
    protected override void Invoke(object parameter)
    {

    }
}

このようにクラスに対して TypeConstaint 属性を指定してやれば OK です。

■ 最後に
以上、ビヘイビアー開発関連でとりあえず知ってることをまとめてみました。
もともとは その3、その4 あたりで書いた属性のことがきちんとまとまってる資料がなかなか見つからなくて探し出すのに苦労したので忘れないようにと書いておいたのがベースになってます。

2010年8月3日火曜日

[Silverlight] ビヘイビアーの開発 その3

[Silverlight] ビヘイビアーの開発 その2」 の続きです。

■ サンプル
トリガーをきっかけにストーリーボードを開始するビヘイビアーを作ってみます。
と言っても、たいしたものではなく、基本的には

  • Storyboard 依存プロパティを定義する。
  • Invoke メソッドで Storyboard 依存プロパティのストーリーボードを開始する。

としてやればいいだけです。(実際にはこれだけではちょっと問題があります。それについては後述)
実際のコードは以下のようになります。

public class PlayStoryboardBehavior : TriggerAction<FrameworkElement>
{
    public Storyboard Storyboard
    {
        get { return (Storyboard)GetValue(StoryboardProperty); }
        set { SetValue(StoryboardProperty, value); }
    }

    public static readonly DependencyProperty StoryboardProperty =
        DependencyProperty.Register("Storyboard", typeof(Storyboard), typeof(PlayStoryboardBehavior), null);

    protected override void Invoke(object parameter)
    {
        if (this.Storyboard != null)
        {
            this.Storyboard.Begin();
        }
    }
}

■ CustomPropertyValueEditor 属性
上記のコードで一番の問題はストーリーボードの選択が Blend 上できちんとできないことです。
Storyboard プロパティの部分は以下のようになっています。

PlayStoryboardBehavior_StoryboardProp1.jpg

XAML 上にストーリーボードがあっても 「新規作成」 ボタンしか無いためそれらを選択することができません。これでは実用的に使うことができません。(XAML を手で編集すれば使うことはできると思いますが)
これを可能にするための専用の属性 CustomPropertyValueEditor が用意されています。

    [CustomPropertyValueEditor(CustomPropertyValueEditor.Storyboard)]
    public Storyboard Storyboard
    {
        get { return (Storyboard)GetValue(StoryboardProperty); }
        set { SetValue(StoryboardProperty, value); }
    }

このように Storyboard 依存プロパティに CustomPropertyValueEditor(CustomPropertyValueEditor.Storyboard) を追加してやるだけです。
このようにすると Blend 上での表示が以下のようになります。

PlayStoryboardBehavior_StoryboardProp2.jpg

ちゃんとドロップダウンコンボボックスになって既存のストーリーボードを選択できるようになりました。

なお、CustomPropertyValueEditor は他にもいくつか用意されています。詳しくはリファレンスマニュアルの 「CustomPropertyValueEditor 列挙」 を。

■ Category 属性
プロパティに Category 属性を付けておくと Blend のプロパティウインドウ上での表示場所を指定することができます。

    [Category("Common Properties")]
    [CustomPropertyValueEditor(CustomPropertyValueEditor.Storyboard)]
    public Storyboard Storyboard
    {
        get { return (Storyboard)GetValue(StoryboardProperty); }
        set { SetValue(StoryboardProperty, value); }
    }

このように Category("Common Properties") を付けてやると 「その他」 ではなく下図のように 「共通プロパティ」 の方に入るようになります。

PlayStoryboardBehavior_StoryboardProp3.jpg

■ DefaultTrigger 属性
トリガーのデフォルト値についてですが、Button に接続した場合は Click イベントがデフォルトとなるようです。
しかし、他の多くのオブジェクトでは Loaded がデフォルトとなってしまいます。以下は TextBlock に接続したときのトリガーのデフォルト値です。

PlayStoryboardBehavior_TriggerProp1.jpg

今回のような 「ストーリーボードを開始するビヘイビアー」 の場合は、ボタンでは Click イベント、それ以外では MouseLeftButtonDown イベントあたりをデフォルトにしておきたいところです。
それが、DefaultTrigger 属性で指定できます。

[DefaultTrigger(typeof(Button), typeof(System.Windows.Interactivity.EventTrigger), "Click")]
[DefaultTrigger(typeof(FrameworkElement), typeof(System.Windows.Interactivity.EventTrigger), "MouseLeftButtonDown")]
public class PlayStoryboardBehavior : TriggerAction<FrameworkElement>
{
    ...
}

■ サンプルの完成コード
一応、サンプルコードの完成形を載せておきます。 

[DefaultTrigger(typeof(Button), typeof(System.Windows.Interactivity.EventTrigger), "Click")]
[DefaultTrigger(typeof(FrameworkElement), typeof(System.Windows.Interactivity.EventTrigger), "MouseLeftButtonDown")]
public class PlayStoryboardBehavior : TriggerAction<FrameworkElement>
{
    [Category("Common Properties")]
    [CustomPropertyValueEditor(CustomPropertyValueEditor.Storyboard)]
    public Storyboard Storyboard
    {
        get { return (Storyboard)GetValue(StoryboardProperty); }
        set { SetValue(StoryboardProperty, value); }
    }

    public static readonly DependencyProperty StoryboardProperty =
        DependencyProperty.Register("Storyboard", typeof(Storyboard), typeof(PlayStoryboardBehavior), null);

    protected override void Invoke(object parameter)
    {
        if (this.Storyboard != null)
        {
            this.Storyboard.Begin();
        }
    }
}

[Silverlight] ビヘイビアーの開発 その4」 に続きます。

2010年8月2日月曜日

[Silverlight] ビヘイビアーの開発 その2

[Silverlight] ビヘイビアーの開発 その1」 の続きです。

■ TriggerAction<T>
何らかのトリガーを持つビヘイビアーを作成するときの基本クラスです。
以下のように中身が何もないビヘイビアーを作ってみて Blend に貼り付けてみるとすぐ意味がわかるんじゃないかと思います。

public class TriggerActionSampleBehavior : TriggerAction<FrameworkElement>
{
    protected override void Invoke(object parameter)
    {
        
    }
}

Invoke メソッドが abstract なので中身のないメソッドを定義してますが、それだけで他には何にもありません。これを Blend でボタンに接続してみたときのプロパティウィンドウは以下のようになります。

TriggerActionSampleBehaviorProrperty.jpg

コードの中身は何も無いのにちゃんとトリガーが選択できるようになっています。
上記の図だと親オブジェクトの Click イベントをトリガーに指定しています。この場合、Click イベントが発生すると Invoke メソッドが呼ばれます。
だから「接続しているオブジェクトでトリガーが発生したら○○する」 というビヘイビアーを作るときに必要なのは Invoke メソッドの中身を書くことだけです。

■ TargetedTriggerAction<T>
こちらも TriggerAction<T> と同じように中身無しでビヘイビアーを作り Blend に貼り付けてみます。

public class TargetedTriggerActionSampleBehavior : TargetedTriggerAction<FrameworkElement>
{
    protected override void Invoke(object parameter)
    {

    }
}

TargetedTriggerActionSampleBehaviorProrperty.jpg

まぁ、クラス名からだいたい予想は付くと思いますが、TriggerAction<T> と比べると 「TargetName」 が増えています。
要するに 「接続しているオブジェクトでトリガーが発生したらターゲットに○○する」 というようなビヘイビアーを作りたいときに TargetedTriggerAction<T> を基本クラスとして使えばいいわけです。
TargetName に指定したオブジェクトは Target プロパティに格納されています。Target プロパティの型は T です。

[Silverlight] ビヘイビアーの開発 その3」 に続きます。

2010年7月30日金曜日

[Silverlight] ビヘイビアーの開発 その1

ビヘイビアーを開発する際に必要となる基本的なところをまとめておきます。

■ ドキュメント
とりあえず、以下のヘルプファイルが入っているのは見つけました。
これらのファイルがいつ入ったのかまでは調べてませんが、名前的に考えて Expression Blend を入れたときだと思います。

※以下、”%PROGRAMFILES%” と表記していますが、64bit OS の場合は “%PROGRAMFILES(x86)%” になります。

Blend 3 (英語版) %PROGRAMFILES%\Microsoft SDKs\Expression\Blend 3\Help\en\BlendSDK.chm
Blend 4 (日本語版) %PROGRAMFILES%\Microsoft SDKs\Expression\Blend\.NETFramework\v4.0\Help\ja\.NETFramework40BlendSDK.chm

また、Blend SDK は Microsoft のダウンロードサイトからダウンロードできますので、きっとそれにも入ってるんだと思います。(確認はしてませんが)

もっとも、このヘルプファイルにはちょっと不満が。。。
Blend 3 のやつはクラスのリファレンスが載ってるだけで解説なんかは全然ありません。
Blend 4 の方はちょっと解説なんかも増えてますが、「ビヘイビアーの追加の仕方」 みたいな Blend の使い方について数ページの解説があるだけで、ビヘイビアーの開発方法についてはほとんど何も説明がありません。「カスタムのトリガーとアクションの作成」 というページと 「カスタム ビヘイビアーの作成」 というページがそれぞれ 1ページづつあるだけみたいです。
英語のヘルプと日本語のヘルプとでは翻訳してあるだけで基本的に内容は同じみたいですし。。。
うーん、どっかにまともな開発用ドキュメントってあるんでしょうか?

■ 参照設定
Visual Studio で作った Silverlight プロジェクトの場合、ビヘイビアーの開発に必要となるアセンブリを手動で参照設定する必要があります。
Blend で作ったプロジェクトの場合は最初からこれらの参照設定が含まれています。どうやら以下のファイルが参照設定されているようです。

Blend 3 (英語版) %PROGRAMFILES%\Microsoft SDKs\Expression\Blend 3\Interactivity\Libraries\Silverlight フォルダの下
  • System.Windows.Interactivity.dll
  • Microsoft.Expression.Interactions.dll
Blend 4 (日本語版) %PROGRAMFILES%\Microsoft SDKs\Expression\Blend\.NETFramework\v4.0\Libraries フォルダの下
  • System.Windows.Interactivity.dll
  • Microsoft.Expression.Interactions.dll
  • Microsoft.Expression.Drawing.dll

ビヘイビアーの開発という意味では本当に必要なのは System.Windows.Interactivity.dll だけみたいです。Behavior<T> や TriggerAction<T> といったビヘイビアーの基本クラスとなるクラスなどがこれに含まれています。
Microsoft.Expression.Interactions.dll には標準で付いているビヘイビアー (ChangePropertyAction や PlaySoundAction といったもの) が入っているようです。
Blend 4 で増えた Microsoft.Expression.Drawing.dll には星とかのシェープが入っています。

■ ビヘイビアーの基本クラス
ビヘイビアーを開発する場合、Behavior<T>、TriggerAction<T>、TargetedTriggerAction<T> のいずれかを継承する必要があります。

■ Behavior<T>
もっともシンプルなビヘイビアーの基本クラスです。
サンプルコードは以下のようになります。

public class BehaviorSampleBehavior : Behavior<FrameworkElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
    }

    void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        MessageBox.Show("left button down");
    }
}

AssociatedObject プロパティにビヘイビアーが接続しているオブジェクトが格納されています。
ですので、アタッチのときにイベントハンドラを登録し、デタッチのときに削除する、というようにしてやると好きなイベントに反応することができます。

■ ジェネリック引数 T について
Behavior<T> の T ですが、これは 「どのオブジェクトに接続することができるのか」 を示していると考えるとわかりやすいです。
T を FrameworkElement か UIElement にしておけば 「何にでも接続できるビヘイビアー」 ということになります。(XAML に出てくる普通のオブジェクトはみんな FrameworkElement から派生したオブジェクトだから。Resource とかは違うかもですが、そういう特殊なやつはここでは横に置いておきます)
T を Button にすれば Button (と Button から派生しているもの) に接続できるビヘイビアーということになります。

Blend はきちんと T の型を見ています。
Blend でビヘイビアーをドロップしようとすると、マウスの下がドロップ不可のときは進入禁止マークになりますが、T が Button になっていると Button (と Button から派生しているもの) の上にマウスがある時だけドロップ可能になって、それ以外のときは進入禁止マークになります。

そして、もちろん AssociatedObject プロパティの型は T です。

[Silverlight] ビヘイビアーの開発 その2」 に続きます。

2010年7月29日木曜日

[ASP.NET] RSS を受け取って表示する ASP.NET のコード

勤め先の Web サイトで RSS を受け取って表示したいってことだったので、ちょこちょこっと書きました。

aspx は単に DataList を置いただけです。以下のような感じ。

<asp:DataList ID="DataList1" runat="server">
    <ItemTemplate>
        <asp:HyperLink ID="HyperLink1" runat="server"
            Target="_blank"
            NavigateUrl='<%# DataBinder.Eval(Container.DataItem, "Link") %>'
            Text='<%# "[" + DataBinder.Eval(Container.DataItem, "Published", "{0:MM/dd HH:mm}") + "] " + DataBinder.Eval(Container.DataItem, "Title") %>'></asp:HyperLink>
    </ItemTemplate>
</asp:DataList>

aspx.cs で以下のよう DataList にデータバインドして表示します。

protected void Page_Load(object sender, EventArgs e)
{
    var rss = XElement.Load("http://shinichiaoyagiblog.divakk.co.jp/feeds/posts/default");
    var ns = XNamespace.Get("http://www.w3.org/2005/Atom");
    var titles = from entry in rss.Elements(ns + "entry")
                 select new
                 {
                     Published = DateTime.Parse(entry.Element(ns + "published").Value),
                     Title = entry.Element(ns + "title").Value,
                     Link = entry.Elements(ns + "link").Single(x => (x.Attribute("rel").Value == "alternate")).Attribute("href").Value,
                     Author = entry.Element(ns + "author").Element(ns + "name").Value
                 };

    this.DataList1.DataSource = titles.Take(5);        // 最初の 5件
    this.DataList1.DataBind();
}

Linq を使ってごく普通に RSS を取ってるだけです。
あぁ、RSS って書いてますが、上記は Atom 専用のコードになっちゃってます。
あと、例外なんかもまったく考慮してないので、そのあたりはもうちょっとちゃんとした方がいいかもしれません。

[ASP.NET] ASP.NET 4 と ASP.NET 1.x/2.0 を混在させる

最近 (ASP.NET 4 から?) は複数のバージョンの ASP.NET を一つの Web サイトでホストできるということで、それをやってみようとしたときの記録です。

サイトの構成を以下のようにしようとしました。

もともと .Text (C# で書かれたブログエンジン) が動いているところの親フォルダを ASP.NET 4 にしてみようとしたわけです。
OS は Windows Server 2003 の IIS6 です。
.NET Framework 4 をインストールしたらいつの間にか /blog/ の方も 4 になっていたので、以下の手順でマルチバージョンにしました。

  1. IIS マネージャの 「アプリケーションプール」 を右クリックして v1Pool という名前のプールを新規作成。
  2. IIS マネージャで仮想フォルダ blog を右クリックしてプロパティで 「アプリケーションプール」 を v1Pool に、「ASP.NET バージョン」 を 1.1.4322 にした。
  3. (ルートの方は始めから「アプリケーションプール」 が DefaultAppPool、「ASP.NET バージョン」 が 4.0.30319 になっていたのでそのまま)

ひとつのアプリケーションプールに違うバージョンを入れることはできません。マルチバージョンにしたいときはこのようにバージョン別にアプリケーションプールを作ってやれば OK です。

が、ここで問題発生。
blog フォルダの方は勝手に親フォルダの web.config を見にいっちゃうんですね。
もちろん、blog フォルダは Web アプリケーションにしてあるので独自に web.config を持ってるんですが、実行時に勝手に親フォルダの web.config も内容がチェックされてしまいます。
それで、親フォルダは ASP.NET 4 になってるので web.config も ASP.NET 4 のものになっています。それを ASP.NET 1.1 としてチェックしてしまうため (blog フォルダは ASP.NET 1.1 だから)「connectionString なんて知らね」 「targetFramework なんて知らね」 とかって感じで web.config が不正だというエラーになってしまいます。
この、親フォルダの web.config までチェックしにいっちゃうのって止められるんでしょうか?
ちょっと調べてみたところでは止め方がわかりませんでした。
今回の場合は、親フォルダは特に web.config に書かなくちゃいけないことは無かったので、中身をばっさりと削除して ASP.NET 1.1 と非互換な部分が無くなるようにしちゃいました。その結果、親フォルダの web.config のせいでエラーになることは無くなりました。

そして、次の問題が発生。
.Text では http://www.divakk.co.jp/blog/aoyagi/ のようにアクセスできるようになっています。aoyagi という名前のフォルダは実在しないのですが、うまいことマッピングして処理するようになっているんです。
ところが、実際にアクセスしてみると 「eurl.axd/xxxxxxxxxxxxxxxxxx が見つからない」 とかっていうエラーになります。
検索してみるとどうやらこちら。
http://www.asp.net/learn/whitepapers/aspnet4/breaking-changes#0.1__Toc256770153
(日本語は [PDF]ASP.NET 4 の互換性に影響する変更点
一部抜粋してみると、

ASP.NET 4 を使うように構成されていると、拡張子なしの URL に eurl.axd を追加してしまう。ASP.NET 2.0 は eurl.axd を認識しないのでファイルが存在しないという例外が発生する。

ということだそうです。
解決法も書いてあるんですが、

  1. ASP.NET 4 を使うのをやめる
  2. ASP.NET 2.0 だけのサイトを別に作る
  3. レジストリを書き換えて eurl.axd を使わないようにする

うーむ。
今回は 3 の eurl.axd を使わないようにして対処しました。

一応、最初に意図した構成にはできましたが、ちょっとなんだかなぁという感じです。
ASP.NET のバージョンを混在させるのはいろいろと落とし穴がありそうですね。

2010年7月28日水曜日

[始めに] 技術系の記事を書くためのブログを作成しました

今まで http://shinichiaoyagi.blog25.fc2.com/ で技術系のことも趣味系のこともごちゃまでで書いてましたが、技術系専用のブログをここに立ち上げました。
ここでいう技術系っていうのは、Windows、.NET Framework、C#、VB、WPF、Silverlight といったもののことです。

見てもらったらわかるとおりここは http://shinichiaoyagiblog.divakk.co.jp/ と私の勤め先のドメイン ( http://www.divakk.co.jp ) の下になってます。(下というか、ドメイン内のホストですが)
けど、まぁ、これにはあんまり深い意味は無くて、普通に青柳個人が好き勝手に書くブログですし、もちろん、会社の公式ブログとかそういうのとも違います。

また、独自ドメインを使っていますが、バナーに出しているようにここは http://www.blogger.com/ です。
blogger.com は無料で独自ドメインを使えるとのことでしたのでお借りすることにしました。

2010年6月24日木曜日

[CRT] Re:一歩ずつ (CRT エミュレーション)

前の記事に続いて、Jitta さんの 「一歩ずつ」 の出題 7 の CRT のにじみについて。
(またまた回答とかじゃありません)
そういや、以前に見かけたなぁ、と思って検索してみた。

A Television Simulator
Atari 2600 VCS エミュレーターの話なんですが、CRT エミュレーションをするにはどういったことをすればいいかといったことが書かれているようです。
すごくいい加減な訳ですが、どうやら

  • Texture … 格子を抜けてきた電子ビームによって蛍光物質が光ってるとかそういった構造上の理由から CRT はドットとドットの間にわずかに隙間がある。
  • Afterimage … 蛍光物質がわずかな間焼きつくのと、LCD に比べると人の網膜に残像が残りやすい。結果、画像が動いたり変化したりしたあとすぐに消えずに残存したりする。
  • Color Bleed … LCD のように輪郭がくっきりせず CRT では周囲ににじみ出すし、色が混じりあったりする。
  • Noise … 伝送に RF を使っているのでノイズがそれなりに乗る。普通のテレビと違ってビデオゲームの広くフラットに塗りつぶしたようなところでは、それがわずかなバイブレーションとして見える。

といったところみたいです。

で、2009年春に Stella という Atari 2600 VCS エミュレーターに上記の内容を実装してみたと。
結果は “ファンタスティック” だったそうです。
こちら の June 9, 2009 のところに CRT エミュレーションが追加されたとあります。
Open GL 2.0 以上で GLSL が必要とのことです。

残像とかは以前のフレームの状態とかがわからないとダメそうなので難しいかもだけど、ドット間の隙間やにじみなんかは Silverlight の PixcelShader でもなんとかなるかな?(いや、私は作りませんが。つか、HLSL とかあまりよく知らないし)


[Small Basic] Re:一歩ずつ (Small Basic でやってみた)

Jitta さんの 「一歩ずつ」 を読んで、ふと、Small Basic でやってみました。
(出題に対する回答ではなく、そのまんま Small Basic でやってみたというだけです)

実は Small Basic を使ってみるのは今回が初めてです。存在を知ってただけで、インストールもしたことがありませんでした。
なので、まずは http://smallbasic.com/ から msi をダウンロードしてセットアップ。
ダウンロードサイズも 5Mバイトちょっととすごくコンパクトなんですね。
セットアップ中に聞かれる Custom Setup のところの “Main Files” を展開して English と Japanese を “Will be installed on local hard drive” にしておきました。
そうすると、ちゃんとスタートメニューに Microsoft Small Basic (English) と (Japanese) が追加されました。(English は別にいらなかったかも)

さっそく Small Basic を起動して以下のコードを入力。
Small Basic の文法もほとんど知りませんでしたが、インテリセンスもよくできてるし、すぐに完成。

GraphicsWindow.Width = 800
GraphicsWindow.Height = 600

For x = 0 to GraphicsWindow.Width Step 5
GraphicsWindow.PenColor = "Blue"
  GraphicsWindow.DrawLine(0, 0, x, GraphicsWindow.Height)
EndFor

For y = 0 To GraphicsWindow.Width step 5 GraphicsWindow.PenColor = "Blue" GraphicsWindow.DrawLine(0, 0, GraphicsWindow.Width, y) EndFor

実行してみたらちゃんと青い線が描けました。

Small Basic のおもしろいところは、書いたコードをすぐに公開できるところです。
やり方は簡単でツールバーの 「発行」 ボタンを押すだけ。
ユーザー登録とかそういったとも何にも無しにボタンを押すだけでコードが Web で公開されます。
そうやって公開したのがこちら
http://smallbasic.com/program/?ZWL954

Web 上では Silverlight で動いてるんですね。
リンクするだけでなく、<object> タグで自分のブログなんかに貼り付けることもできます。(右側に表示されている “Embed this in your website” のタグ)
なかなかおもしろいなぁ。
リファレンスマニュアルも日本語化されてました。 http://smallbasic.com/doc.aspx?l=ja

あと、Jitta さんの記事の出題 7 の CRT のにじみについてですが、これについては別記事にて。


2010年1月5日火曜日

[WPF][Silverlight] Silverlight を WPF/XNA でホスト

CodePlex にこんなのがありました。
http://silverlightviewport.codeplex.com/
すごく簡潔な説明しか無いですし、ソースを見たわけでもないので詳細はわかんないんですが、WPF/XNA 上で Silverlight をホストするものみたいです。(Host ではなく Render になってますが)
また、「まだめっちゃ実験的なもんだよ!」 とのこと。

作者さんのブログの記事はこちら
Render Silverlight in WPF ? WPF SilverlightViewport
まず、「より明確に理解するために取り組んでみたホビープロジェクトだ」 といった文からはじまってます。
で、以降をざっと要約すると 「Silverlight は COM API でホストできるC++ のサンプルコードもある。けど、これは Win32 のウインドウに描画するのが前提。Silverlight はウインドウレスもサポートしてるはず。なので、Silverlight をウインドウレスにロードして画面の代わりに GDI ビットマップに描画させてみたらうまくいった。ただ、CPU 使用量が高かったのとアルファが透過できなかったけど。で、ちょっと汚くなったけど interop でごにょごにょしたらアルファも抜けるようになったし、パフォーマンスも良くなった。WriteableBitmap も使ってみたけど、結局 InteropBitmap にした。パフォーマンスも良くなったし。」 という感じです。

ソースをダウンロードして Visual Studio 2008 でソリューションを読み込んでやればビルドして実行できます。
(XNA を入れていない場合、XNATestApplication プロジェクトと Primitives3D プロジェクトが読み込めませんが、無視して読み込んでしまえばとりあえず TestApplication を動かしてみることはできました)
TestApplication を実行してみるとちゃんと動いてます。動いているのは http://silverlight.net/ にある SHOWCASE を表示しているやつです。
Window1.xaml の <slvp:SilverlightViewportElement ...> の Source を Source=”http://silverlight.net/content/samples/sl3/toolkitcontrolsamples/run/System.Windows.Controls.Samples.xap “ と書き換えてやって Silverlight 3 Toolkit を動かしてみましたが、それなりに動いてます。
なお、マウス入力はそれなりに生きてますが、キー入力は未対応みたいです。
(マウス入力は WPF 上のイベントを転送してやってるんじゃないかと思いますが、キー入力は IME とかいろいろ絡んできそうなので簡単ではないのかも)

作者さんもホビープロジェクトと言ってる通り、実用性についてはよくわかりません。
そもそも、「Silverlight をホストしなくても WebBrowser を使えばいいんじゃね?」 という気がしなくもないですw
けど、試みとしてはおもしろいですね。