最強.NET開発PCを作るよ(その3)

最強.NET開発PCを作るよ(その1) はじめに
最強.NET開発PCを作るよ(その2) メモリ編

の続き

CPU を選定する前に考えたのが、用途です。早い CPU にすればそれに越したことはないのでしょうが、予算の関係やそもそもその CPU の性能を使い切れるのか?という問題もあって、それなりに適切なものにしたいと。

候補に挙がっていたのが

  • Core i7 3930K
  • Core i7 3770K
  • Core i5 3570K

なところです。価格.com などの掲示板で見ると、3930K のほうはオーバークロッカーが使っていたり動画のエンコードで使っていたり。3770K のほうは現状のスタンダードというところで、動画のエンコードには十分という話が書いてあります。

Intel core i7-3820

動画のエンコードの場合には、CPU を使いっぱなしになる高速運転状態が特徴です。ですが、Visual Studio などを使った開発 PC の場合には、最大瞬間風速はそれほどいらないのですよ。確かに、OpneCV で顔認証の解析をするとか、テンプレートマッチングをがっつりと書くとか、というのならば動画のエンコード並に CPU 速度が効いてくるのですが、ソフトウェア開発をしているときには、ほとんどがキーボードを打っている時間なので、さほど瞬間風速は高くなくてもよいのです。

結果的に i7-3820 という CPU に落ち着いたのですが、実は i7-3930K と i7-3820 の間にはコア数が異なるという壁があります。6コアと4コアの違いなのです。価格的に2万円も違うので、オーバースペック&オーバー予算ということで、4コアに落ち着いたのですが、ちょっと6コアというのは、プログラマにとって魅力的なんですよね。

■なぜコアが多いほうがいいのか?

ちょっと、このタスクマネージャを見てください。VMWare 上で、2プロセッサを振り分けて VC++ のビルドをしているところです。Windows 7 のタスクマネージャでは、Visual Studio が2プロセッサを使ってビルドをしています。

image

ホストしている Windows 8 のほうで、ちょうど波があっているところが、この2プロセッサですね。動画のエンコードや Windows 8 上で Visual Studio だけを動かしてる場合は、プロセッサに均等に負荷が割り当てられるので、こういう風にはなりません。

image

実はプロセッサにプロセスを割り当てるほうが、タスクの切り替えなどが発生しないのでプログラムの実行は早くなるし、プログラムは簡単になるんですよね。C++ AMP などが出てきているので、パラレルのプログラミングも難しくはなくなってきていますが。まあ、そこまで本格的にプログラムで制御しなくても、開発パターンとして、Windows 8 で2枚のモニタを使って開発していれば、

  • ひとつのモニタで Visual Studio で組む
  • もうひとつのモニタで、Windows ストア アプリを常時稼働
  • VMWare でサーバー割り当て
  • SQL Server などのデータベース処理

を同時に実行することになります。まあ、さらに私の場合には雑音を消すために、動画が表示されているという GPU を使うパターンも多いのですが、データベース処理と開発環境(Visual Studio)をバッディングさせないというのが CPU のコアを求める理由のひとつです。

なので、2万円ほど予算を積んで、コアを1.5倍(4から6へ)にしたほうが良かったのでは?と思ったりもしますが、まあ、そのあたりは予算的なものもあるし。

そんなわけで i7-3820 に落ち着いたというところです。

ARK | Intel® Core™ i7-3820 Processor (10M Cache, up to 3.80 GHz)
ARK | Intel® Core™ i7-3930K Processor (12M Cache, up to 3.80 GHz)

■AMD はどうなのか?

今回は、無難に Intel を求めましたが、AMD という選択もあります。AMD だと同一の価格帯で8コアいうものもあるので、先の並列で動かすという条件を軽くクリアできます。

その場合には、マザーボードはどれを選べばいいの?ってことになるので、ちょっと私の知識では追いつかないので…そのあたりの選定は両氏に任せるということで。後で聞いてみますか。

お次は、ストレージ関係で SSD, HDD ということで。

カテゴリー: 最強.NET開発PC | 最強.NET開発PCを作るよ(その3) はコメントを受け付けていません

[C++/CLI] モニタの解像度をプログラムから変更する

たまに C++/CLI の記事などを。Visual Studio 2012 からは C++/CLI もインテリセンスが使えるようになったので、C# と win api を媒介する C++/CLI のプログラミングもばっちりですね…って、2010 の時に出してほしかったよなぁ。ちなみに、2012 では C++/CLI でフォームアプリケーションが作れない(ことはないけど、テンプレートに入っていない)ので、そのままフェイドアウト…なのか? それとも C++/CX と融合するのかは謎なところです。

■解像度とモニタを指定する

解像度自体は、ChangeDisplaySettingsEx 関数を使いますが、複数のモニタがある場合はデバイス名が必要にあんるので、あらかじめ EnumDisplayDevices で取得しておきます。
本来は、モニタのリストとかを取ればいいのですが、画面キャプチャ用のツールなので、モニタ数などは決め打ちで。

#include <windows.h>
#include <WinUser.h>

public ref class GDI
{
public:
	static void ChangeDisplay( int width, int height, int moniter )
	{
		// デバイス名の取得
		DISPLAY_DEVICE disp;
		EnumDisplayDevices( NULL, moniter, &disp, NULL );
		// 解像度を設定
		DEVMODE mode = {0};
		mode.dmSize = sizeof(mode);
		mode.dmPelsWidth = width ;
		mode.dmPelsHeight = height ;
		mode.dmFields = DM_PELSWIDTH|DM_PELSHEIGHT;
		// 解像度を変更
		long ret = ChangeDisplaySettingsEx( disp.DeviceName, &mode, NULL, 0, NULL );
	}
};

■C# から呼び出す

画面自体は、C# で作ります。
解像度自体は、コントローパネルから調べて決め打ちです。ボタン一発で切り替えることができます。

/// <summary>
/// 解像度変更
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
private void button10_Click(object sender, EventArgs e)
{
    scrcaplib.GDI.ChangeDisplay(1024, 768, 1);
}
/// <summary>
/// 解像度変更
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
private void button11_Click(object sender, EventArgs e)
{
    scrcaplib.GDI.ChangeDisplay(1280, 1024, 1);
}

これを C# だけでやろうとすると、構造体やら定数定義やらがややこしいんですよね。なので、win api を直接叩くのではなく、C++/CLI を媒介にすると簡単になるよ、という実例です。

カテゴリー: C#, C++ | [C++/CLI] モニタの解像度をプログラムから変更する はコメントを受け付けていません

最強.NET開発PCを作るよ(その2)

最強.NET開発PCを作るよ(その1)
http://www.moonmile.net/blog/archives/4116

の続き。

開発用 PC を構築したいと思ったときに、各氏に相談した条件として「メモリは 64GB」にしたい、というのが筆頭でした。円安になって、輸入もののメモリとか HDD とかがバンバンと高くなる折だったのですが、ここでケチってしかたがないし。かといって、どのくらいのメモリが「開発にとって快適になる程度なのか」はいまいちわからなかったのです。
BTO パソコンの場合は、ブラウザで見ながらポチポチとメモリの容量を挙げて「ああ、これだと全体が高くなるから、この程度でいいか」ってな具合に、価格比で計算してしまいがち。あるいは、マザーボードなどの制限から、積めるメモリはこれが限度、ってな具合に当時買うときの限界いっぱいまで、ってのが普通です。

image

で、今回の場合は、Windows 8 を入れるのは必須だから、最低でも 2GB 以上、最大は 128 GB までいけるそうで、その間の選定になるのは確かです。開発マシンの場合、客先の実行環境と揃えるためにあえて 32 bit環境を揃えないといけない場合もあるのですが、今回は自分のメイン開発マシンになるので 64 bit 環境にして、メモリ枠を外していきます。

そして、手元にある PC で Windows 8 を試している体感として、

  • メモリ 4GB では、Windows 8 そのものが重い。
  • メモリ 8GB は、Windows 8 は軽いのだが、Visual Studio 2012 の XAML デザイナが重い。

という感触を得ています。メモリ 4 GB のほうは、CPU が Dual core という古いマシンなので、i5 あたりの CPU であれば、もうちょっと快適でしょう。実際、acer w500 では、メモリが 2GB しかない状態ですが自作の Windows ストア アプリなどは、さくさくと動きます。ただ、GPU が非力なので、Windows ストア アプリのゲーム関係はほとんどダメですが。

メモリ 8GB のほうは、vaio のノートブックで、当時(2年前?)にメモリをいっぱいまで積んだものです。このノートパソコンだと、Windows 8 はさくさく動くのですが、微妙に Office 2013 が重かったり、Visual Studio 2012 のデザイナのところで倒れたりしています。たぶん、HDD 関係が重たいので、バランスの悪いマシンなのでしょう。

image

image

ビジネス仕様かつノートブックなので、グラフィック関係はあまり関係ありません。Windows 8 では HDD のアクセススピードが推奨環境に入っているので、これが足を引っ張っているような気がします。

これらの経験からすると開発機としては、最低 16GB のメモリを積んでないと「快適な環境」は望めないのでは?と考えました。Visual Studio 2012 自体は、100 MB と以前とあまり変わらないのですが、XAML デザイナが同じく 100 MB、シミュレータを動作させると、こんな風に自分自身にリモートログインをするために、利用メモリががんがん増えていきます。

image

なので、シミュレーター自体を快適に動かすためには、ホストのマシン(Visual Studio が動いているマシン)に、タブレット PC 並の余力が必要になるのでは?と考えています。このあたりは、時間を取って検証したいところですね。

プラス、サーバー系との接続をするため、執筆の関係や顧客環境の関係から、いくつかの Widows のバージョンを用意しておく必要があります。私の場合、VMWare を昔から愛用しているのでこれを使うと、最低でも 2GB は取られてしまうのです。所詮、仮想環境ですから(サーバー機ではないという意味で)接続できれば ok という考え方もあるのですが、逆に言えば仮想環境としてパフォーマンスを利用して、高速な LAMP 環境とか IIS/ASP.NET 環境として利用してもよいわけです。こうなると、仮想環境であってもメモリを 8 GB 取れれば、結構なことができそうです。

そういう経緯で考えて、

  • Windows 8 + Visual Studio 2012 + Blend + シミュレーター で 8GB
  • VMWare の仮想環境で 8GB

というパターンは最低おさえておきたい。なので、16 GB は最低ほしいメモリなのかなというところです。仮想環境を使わない場合は、8GB に抑えておいてもよいのですが、体感として16GB はないと足りないところでしょう。

これに、動作としては、

  • ブラウザ Firefox
  • Office 2013

が加味されます。ブラウザは、ie でも chrome でもなんでもよいのですが、Visual Studio で開発しているときにブラウジングが重いと、開発効率に影響します。google を使って検索しますからね。ええ、私の場合、家での開発なので「社内セキュリティでブラウジングできない」っていう縛りはありません。

Office 製品は、Excel/Word/Outlook を使っています。メーラー関係は、電八でもいいのですが、あんまりこだわりがなくなって、しばらく前から Outlook にしてしまいました。
プリントアウトをほとんどしなくなってから、モニタで仕様書の確認をしています。あと、進捗管理用に Excel は必須なのです。このあたりを数枚開いた状態で、Visual Studio を使って開発をするので、さらにプラスして 32 GB ってのがあれば、大丈夫かなと考えていました。

■メモリスロットの問題

32 GB の場合は、8GB のメモリを4枚ってことになります。マザーボードの関係から、メモリのスロットが 4 枚ということが多いので、この構成がベターなところかなと。同期とか相性の関係から、本来ならば 2枚セットを1つだけで済ませたいところですが、そうすると 16 GB x 2 とか、高い構成になりそうな感じ。

さて、当日、各氏と秋葉原を巡ったわけですが、各店舗で 16 GB のメモリが売り切れていました。32 GB のメモリにしてもよかったのですが、ここを必須としたのは、

  • 先の環境に加えて、SQL Server などの DB を乗せて実験する
  • VMWare を同時に2以上動かして、C/S のパフォーマンスチェック
  • 普通に出回っていそうな 32 GB では男が廃る

ってのを考えて(ええ、多少見栄もあってw) 64 GB になっています。「安心料」って感じですかね。あと、自分へのモチベーション燃料とか。

そんなわけで、メモリ購入の当日、16 GB のメモリがないので手詰まりか…と思ってみたものの、実は、各氏に選定してもらったマザーボードのメモリスロットルが 8 枚であることがわかりました。これならば、8 GB を 8 枚(2セットを4個)で差し込めるわけで、メモリの価格も抑えられます。

■で、実際どうなのか?

軽く Visual Studio 2012 を立ち上げて、Outlook を動かしてという具合で、現在こんな感じです。メモリ的には、5.4 GB なので、8 GB あれば十分かと思えるのですが、実体験からいうと Visual Studio + Office だけの組み合わせでも 16 GB は必要です。なんやかやと動かし続けると、8 GB ぎりぎりになってスワップが発生し続けて、Visual Studio が重すぎる感じになります。

image

なので、現状、Intel i5, i7 ぐらいの CPU であれば、16 GB ぐらいに増設すれば、そこそこ開発に快適な環境が得られるのではないかと。実売的には、1万円ぐらいの追加投資ですかね?

次は、CPU についてみていきます。

カテゴリー: 最強.NET開発PC | 3件のコメント

最強.NET開発PCを作るよ(その1)

去年の pp-club のイベント絡みの飲み会で「プログラマ用の開発PCとはなどんなスペックか?」という話があって、作ってみたい~と年末年始ごろに夢にまで見ていたのですが、リンクスインターナショナル社の阪口さんと、デザインラボの加藤さんのご協力もあって、晴れて最強.NET開発PCの自作にこぎつけました。

.NET 開発専用パソコンを考える(´・ω・)ス その1 全体とメモリ|WEB系技術電脳日記
http://ameblo.jp/konica/entry-11476853250.html

あたりも参照にしてください。

私の場合、自作PCってのはDOS/Vの頃ぐらいしかなくて、そのあとはちょっとしたメモリ増設とHDDの追加ぐらい。当時、オーバークロックが流行っていたのですが、手を出したことがありませんでした。1年前ぐらいから、duck さん のようなオーバークロッカーという方もいて、まさしく最近の自作PCの主流は OC ってことなのか…ってことで、ここ数年買うのは、BTO パソコンっていうお仕着せパソコンを買っていた訳ですが。

去年の 12/1 にリンクスインターナショナルさんからデモ機を持ち込んでもらって、その場で Windows 8 + Visual Studio 2012 + Office 2013 Preview あたりを軽く試してみました。詳しくは忘れてしまったのですが、CPU もメモリもそれなりに豪華版であったということで、サクサクと動いて密かに感激しておりました。私の PC が、3年ぐらい前の BTO PC だったこともあって、Windows 7 ではそれなりに動くのですが、Windows 8 だとちょっときつい、更に言うと、Visual Studio 2012 の動き重くなり、さらに XAML デザイナーの部分がかなり重たいので、「いったい、推奨スペックはどのくらいなのか?」と疑問を持っていました。実際、Windows 8 のパッケージにはそれなりに推奨スペックは書いてあるものの、実運用で Windows 8 + Visual Studio 2012 + Office 関連 + VMware 関連を動かしたときに「快適に仕事ができる程度の PC」ってのが、どのくらいのものなのか?をここ2か月ほど考えていたわけです。

■お仕事の前提条件

プログラマにしたって、いろいろな仕事があるわけで、そこそこのパソコンでいい場合と、高速なパソコンがほしい場合と、サーバーと組み合わせないとダメな場合とか、社内だとかフリーだとか、いろいろと条件があります。

普段使いで同時にうごいているのが、

  • Windows 8
  • Visual Studio 2012 が 2,3枚
  • シミュレーター
  • Word/Excel が 2,3枚
  • PDF が 2,3 枚
  • Firefox が 10タブぐらい

ってところが普通に稼働している範囲です。これに、別件の VMWare + 英語版 Windows 7 + Fortraun + VC++ があって、これは別にマシンで動かして、リモートデスクトップで接続させています。

このあたりをベースにして、ちょっと構成を考えたのが、以下なところです。

  • CPU は、それなりにコアが多いほうがいいと思う。
  • メモリは、64 GB 積んでみる(仮想環境も含めてみる)。
  • GPU はどのくらい必須のなのか?
  • C ドライブは SSD のほうが早いのか?コンパイル速度は速くなるのか?
  • HDD は、作業領域とデータベース用に 2 台用意する
  • モニタは 2 台必須

■購入と組立て

このあたりを、ざっくりと、加藤さんと阪口さんに相談して、パーツを選定して貰い、2/23(土) に秋葉原で購入&組立てました。組み立てました…とはいえ、実際に組立てたのは阪口さん。私は財布係です(苦笑)。

image

メモリチェックをして貰ったのですが、64GB だと 1時間経っても 30% を超えたぐらいなので断念(笑)。メモリは、CMX16GX3M2A1600C11 という 8GB x 2 を 4 つ購入しました。メモリの場合、型番なりロットを合わせないと同期の問題があって、動かないパターンってのが出てきます。また、最初の頃は大丈夫でも、使っているうちになんかおかしくてブルースクリーンになる、ってのはメモリ同期がおかしいパターンが多いんですよね。

image

あれこと2時間弱かかって(半分はメモリチェックです)。ここまで組んでもらいました。配線は、裏側に隠してあるという美しい仕様。これを自分が作るとなると大変…といいますか面倒なので、マザーボードの上をうにょうにょと配置する形になってしまうのですが。

image

一応、その場でチェック用に、

  1. Windows 8 Pro をインストール
  2. Visual Studio 2012 をインストール
  3. Office 2013 をインストール
  4. VMWare Workstation 8 をインストール
  5. VMWare 上に Windows 8 をインストール(メモリは 8 GB 割り当てる)
  6. VMWAre 上に、Visual Studio 2012 をインストール

な感じにして、ホスト側で、Visual Studio 2012 + Office 2013 + VMWare を同時に動かすというパターンを試してみましたが、全然大丈夫、びくともしませんね。この「びくともしないところ」が、メモリなのかCPUなのかGPUなのかSSDなのかを、ぼちぼちと検証していきたいと思っています。どう考えても 64 GB のメモリは積みすぎ、という感じがしないでもないのですが、VMWare 上に仮想的なサーバーを組み立てて、IIS + ASP.NET + SQL Server でテストをするとか、Apache + PHP + MySQLのパターンを使うとか、いろいろあるので開発&検証用としては、これぐらいあったほうがよいのか、どのくらいあったらいいのか、ってのを調べていきます。

予算をいくらでもつぎ込んでいけば、CPUなりメモリを積むことができるものですが、費用効果とか原価償却とか本当につぎ込まないとダメなところ、逆にいえば「ここをケチってしまうと、開発効率が格段に落ちて、できあがるものもできあがらない」っていう悪い要素を消していきたいですね。

カテゴリー: 最強.NET開発PC | 最強.NET開発PCを作るよ(その1) はコメントを受け付けていません

[Win8] スリープをした後にパスワードを入れなくてもログインできるようにする

Windows 8 の場合、Microsoft アカウントに紐づけるかローカルアカウントを設定するってことで、パスワードを入れておくのが普通です。特に Microsoft アカウントの場合は、パスワードなし、ってのができないので、ログインのときはセキュリティ的にパスワードが必須になります。

が、いざ、タブレット PC で使うと、スリープした後のパスワード入力ってのはちょっと面倒なんですよね。ソフトウェアキーボードを出して、ぽちぽち打ってもいいのですが、ハードウェアのキーボードでは打ちやすいけど、MS のソフトウェアキーボードではちょっと打ちづらい(数字が混じっているときは、いちいちモードを変えないといけないとか)ので、結構不便。

あと、2歳児にタブレットPCを遊ばせているときに、ふとスリープ状態になってしまって、パスワードが入れられなくて泣かれる、というパターンもあります。

そうなんです。家で使う分には他人が使うわけじゃないから、パスワードを入れなくてもいいようにしたい。しかし、SkyDrive やメールなどを使うために、Microsoft アカウントに紐づけているときは、必ずパスワードが必要だし、どうしたらよいか、ってときに。実は解決策があります。

image

チャームの「設定」をクリックした後で、下のほうにある「PC 設定の変更」をクリック。
そのあとに、 PC の設定から「ユーザー」をクリックして、「パスワードを持っている~」のところで「変更」ボタンをクリックします。

ちょっと文言がややこしいのですが、警告が出て OK をクリックすると、スリープの後ではパスワードの入力がいらなくなります。

image

なので、外に持って行ってしまったときはセキュリティ的によくないので(SkyDrive や Mail も見れてしまうので)すが、家で子供相手にしているときとか、家で自分しか使わないときは、これで十分ですね。

カテゴリー: windows 8 | [Win8] スリープをした後にパスワードを入れなくてもログインできるようにする はコメントを受け付けていません

[WinRT] Storyboard とユーザーコントロールでモーダル風ダイアログを作る

花札ゲームで、役ができたときに数秒間だけダイアログを出そうとしているので、その実験です。

■Blend で Storyboard を作る

ダイアログを開くときの sbOpen と閉じるときの sbClose という二つの Storyboard を作っておきます。

本当は枚数がいろいろなのですが、簡単にするために5枚だけ配置しておきます。タネやカスが成立したときは、別のユーザーコントロールを使うようにしようかなと。

長いですが、Blend で作った storyboard を晒します。

<UserControl.Resources>
	<Storyboard x:Name=&quot;sbOpen&quot;>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.RenderTransform).(CompositeTransform.TranslateX)&quot; Storyboard.TargetName=&quot;pict1&quot;>
			<EasingDoubleKeyFrame KeyTime=&quot;0&quot; Value=&quot;634&quot;/>
			<EasingDoubleKeyFrame KeyTime=&quot;0:0:0.5&quot; Value=&quot;0&quot;/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.RenderTransform).(CompositeTransform.TranslateX)&quot; Storyboard.TargetName=&quot;pict2&quot;>
			<DiscreteDoubleKeyFrame KeyTime=&quot;0:0:0.3&quot; Value=&quot;565&quot;/>
			<EasingDoubleKeyFrame KeyTime=&quot;0:0:0.8&quot; Value=&quot;0&quot;/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.RenderTransform).(CompositeTransform.TranslateX)&quot; Storyboard.TargetName=&quot;pict3&quot;>
			<DiscreteDoubleKeyFrame KeyTime=&quot;0:0:0.6&quot; Value=&quot;494&quot;/>
			<EasingDoubleKeyFrame KeyTime=&quot;0:0:1&quot; Value=&quot;0&quot;/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.RenderTransform).(CompositeTransform.TranslateX)&quot; Storyboard.TargetName=&quot;pict4&quot;>
			<DiscreteDoubleKeyFrame KeyTime=&quot;0:0:0.6&quot; Value=&quot;426&quot;/>
			<EasingDoubleKeyFrame KeyTime=&quot;0:0:1&quot; Value=&quot;0&quot;/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.RenderTransform).(CompositeTransform.TranslateX)&quot; Storyboard.TargetName=&quot;pict5&quot;>
			<DiscreteDoubleKeyFrame KeyTime=&quot;0:0:0.6&quot; Value=&quot;357&quot;/>
			<EasingDoubleKeyFrame KeyTime=&quot;0:0:1&quot; Value=&quot;0&quot;/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.Opacity)&quot; Storyboard.TargetName=&quot;pict1&quot;>
			<EasingDoubleKeyFrame KeyTime=&quot;0&quot; Value=&quot;0&quot;/>
			<EasingDoubleKeyFrame KeyTime=&quot;0:0:0.5&quot; Value=&quot;1&quot;/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.Opacity)&quot; Storyboard.TargetName=&quot;pict2&quot;>
			<EasingDoubleKeyFrame KeyTime=&quot;0&quot; Value=&quot;0&quot;/>
			<DiscreteDoubleKeyFrame KeyTime=&quot;0:0:0.3&quot; Value=&quot;0&quot;/>
			<EasingDoubleKeyFrame KeyTime=&quot;0:0:0.8&quot; Value=&quot;1&quot;/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.Opacity)&quot; Storyboard.TargetName=&quot;pict3&quot;>
			<EasingDoubleKeyFrame KeyTime=&quot;0&quot; Value=&quot;0&quot;/>
			<DiscreteDoubleKeyFrame KeyTime=&quot;0:0:0.6&quot; Value=&quot;0&quot;/>
			<EasingDoubleKeyFrame KeyTime=&quot;0:0:1&quot; Value=&quot;1&quot;/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.Opacity)&quot; Storyboard.TargetName=&quot;pict4&quot;>
			<EasingDoubleKeyFrame KeyTime=&quot;0&quot; Value=&quot;0&quot;/>
			<DiscreteDoubleKeyFrame KeyTime=&quot;0:0:0.6&quot; Value=&quot;0&quot;/>
			<EasingDoubleKeyFrame KeyTime=&quot;0:0:1&quot; Value=&quot;1&quot;/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.Opacity)&quot; Storyboard.TargetName=&quot;pict5&quot;>
			<EasingDoubleKeyFrame KeyTime=&quot;0&quot; Value=&quot;0&quot;/>
			<DiscreteDoubleKeyFrame KeyTime=&quot;0:0:0.6&quot; Value=&quot;0&quot;/>
			<EasingDoubleKeyFrame KeyTime=&quot;0:0:1&quot; Value=&quot;1&quot;/>
		</DoubleAnimationUsingKeyFrames>
		<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.Opacity)&quot; Storyboard.TargetName=&quot;textYaku&quot;>
			<EasingDoubleKeyFrame KeyTime=&quot;0&quot; Value=&quot;0&quot;/>
			<EasingDoubleKeyFrame KeyTime=&quot;0:0:0.3&quot; Value=&quot;1&quot;/>
		</DoubleAnimationUsingKeyFrames>
		<ColorAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(TextBlock.Foreground).(SolidColorBrush.Color)&quot; Storyboard.TargetName=&quot;textYaku&quot;>
			<EasingColorKeyFrame KeyTime=&quot;0&quot; Value=&quot;White&quot;/>
			<EasingColorKeyFrame KeyTime=&quot;0:0:0.3&quot; Value=&quot;#FFDAD002&quot;/>
			<EasingColorKeyFrame KeyTime=&quot;0:0:0.5&quot; Value=&quot;#FFFBFBFB&quot;/>
		</ColorAnimationUsingKeyFrames>
	</Storyboard>
	<Storyboard x:Name=&quot;sbClose&quot;>
		<DoubleAnimation Duration=&quot;0:0:0.4&quot; To=&quot;0&quot; Storyboard.TargetProperty=&quot;(UIElement.Opacity)&quot; Storyboard.TargetName=&quot;textYaku&quot; d:IsOptimized=&quot;True&quot;>
			<DoubleAnimation.EasingFunction>
				<CubicEase EasingMode=&quot;EaseIn&quot;/>
			</DoubleAnimation.EasingFunction>
		</DoubleAnimation>
		<DoubleAnimation Duration=&quot;0:0:0.5&quot; To=&quot;0&quot; Storyboard.TargetProperty=&quot;(UIElement.Opacity)&quot; Storyboard.TargetName=&quot;pict1&quot; d:IsOptimized=&quot;True&quot;>
			<DoubleAnimation.EasingFunction>
				<CubicEase EasingMode=&quot;EaseIn&quot;/>
			</DoubleAnimation.EasingFunction>
		</DoubleAnimation>
		<DoubleAnimation Duration=&quot;0:0:0.5&quot; To=&quot;0&quot; Storyboard.TargetProperty=&quot;(UIElement.Opacity)&quot; Storyboard.TargetName=&quot;pict2&quot; d:IsOptimized=&quot;True&quot;>
			<DoubleAnimation.EasingFunction>
				<CubicEase EasingMode=&quot;EaseIn&quot;/>
			</DoubleAnimation.EasingFunction>
		</DoubleAnimation>
		<DoubleAnimation Duration=&quot;0:0:0.5&quot; To=&quot;0&quot; Storyboard.TargetProperty=&quot;(UIElement.Opacity)&quot; Storyboard.TargetName=&quot;pict3&quot; d:IsOptimized=&quot;True&quot;>
			<DoubleAnimation.EasingFunction>
				<CubicEase EasingMode=&quot;EaseIn&quot;/>
			</DoubleAnimation.EasingFunction>
		</DoubleAnimation>
		<DoubleAnimation Duration=&quot;0:0:0.5&quot; To=&quot;0&quot; Storyboard.TargetProperty=&quot;(UIElement.Opacity)&quot; Storyboard.TargetName=&quot;pict4&quot; d:IsOptimized=&quot;True&quot;>
			<DoubleAnimation.EasingFunction>
				<CubicEase EasingMode=&quot;EaseIn&quot;/>
			</DoubleAnimation.EasingFunction>
		</DoubleAnimation>
		<DoubleAnimation Duration=&quot;0:0:0.5&quot; To=&quot;0&quot; Storyboard.TargetProperty=&quot;(UIElement.Opacity)&quot; Storyboard.TargetName=&quot;pict5&quot; d:IsOptimized=&quot;True&quot;>
			<DoubleAnimation.EasingFunction>
				<CubicEase EasingMode=&quot;EaseIn&quot;/>
			</DoubleAnimation.EasingFunction>
		</DoubleAnimation>
	</Storyboard>
</UserControl.Resources>

花札は、右から左に流れる感じで出てきます。1枚1枚ちょっと違うタイミングで出てくるとゲームらしいですよね。あと、役の名前が出るときにちょっとだけ黄色から白に色が変化します。このあたりグラフィックの「アクセラレーター」が有効に働くように注意しないとダメなのよで、たとえば、フォントの大きさを変えようとするとパフォーマンスが落ちますという警告が出ます。

これに注意するとスムースなアニメーションが作れるかなと。

あと、札を表示するときに一瞬だけ光を付けたかったのですが(ちょっとだけ光るやつ)WinRT の XAML には、Effect 関係がなくなっているので(コードで追加するんだっけ?)、これはあとで調節します。代案としては、花札の大きさが固定なのであらかじめ、縁をぼかした画像を用意して貼り付けておくのがよいかなと思っています。

■ユーザコントロールを配置する

役を表示するためのダイアログと、テストようのボタンを配置します。

<Grid Background=&quot;{StaticResource ApplicationPageBackgroundThemeBrush}&quot;>
    <local:YakuModal
        x:Name=&quot;yakuDlg&quot;
        HorizontalAlignment=&quot;Left&quot; Margin=&quot;281,93,0,0&quot; VerticalAlignment=&quot;Top&quot;/>
    <Button 
        Click=&quot;StartClick&quot;
        Content=&quot;開始&quot; HorizontalAlignment=&quot;Left&quot; Margin=&quot;65,55,0,0&quot; VerticalAlignment=&quot;Top&quot;/>
    <Button
        Click=&quot;EndClick&quot;
        Content=&quot;終了&quot; HorizontalAlignment=&quot;Left&quot; Margin=&quot;134,55,0,0&quot; VerticalAlignment=&quot;Top&quot;/>
    <Button 
        Click=&quot;OpenClick&quot;
        Content=&quot;開く&quot; HorizontalAlignment=&quot;Left&quot; Margin=&quot;65,93,0,0&quot; VerticalAlignment=&quot;Top&quot;/>
    <Button 
        Click=&quot;InoClick&quot;
        Content=&quot;猪鹿蝶&quot; HorizontalAlignment=&quot;Left&quot; Margin=&quot;134,93,0,0&quot; VerticalAlignment=&quot;Top&quot;/>
</Grid>

■モーダルダイアログの呼び出しコードを書く

MessageDialog 風に、ShowAsync メソッドを使って await で非同期待ちをしたいので、こんな風にできたらいいなぁ、という感じで書きます。OpenClick のところでデバッグ出力をしているのは、start/end がきちんと待ちになっているかどうかのチェック用です。

/// <summary>
/// ダイアログを開くだけ
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
private async void StartClick(object sender, RoutedEventArgs e)
{
	await this.yakuDlg.OpenAsync();
}
/// <summary>
/// ダイアログを閉じるだけ
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
private async void EndClick(object sender, RoutedEventArgs e)
{
	await this.yakuDlg.CloseAsync();
}
/// <summary>
/// Open/Close が連続した ShowAsync メソッドを使う
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
private async void OpenClick(object sender, RoutedEventArgs e)
{
	System.Diagnostics.Debug.WriteLine(&quot;start&quot;);
	await this.yakuDlg.ShowAsync(5000);
	System.Diagnostics.Debug.WriteLine(&quot;end&quot;);
}

/// <summary>
/// 猪鹿蝶の役が揃った場合
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
private async void InoClick(object sender, RoutedEventArgs e)
{
	var lst = new List<Card>();
	lst.Add(new Card(&quot;G1&quot;));
	lst.Add(new Card(&quot;J1&quot;));
	lst.Add(new Card(&quot;F1&quot;));
	this.yakuDlg.Cards = lst;
	this.yakuDlg.Message = &quot;猪鹿蝶&quot;;
	await this.yakuDlg.ShowAsync(3000);
}

■モーダルダイアログ側のコードを書く

役を表示するモーダルダイアログのコードを書きます。
アニメーションが、sbOpen と sbClose でわかれているので、それを連続させるために Completed イベントを使っています。そして、コードを簡単にするために await Task.Delay(100); を使って完了待ちをします。
最適化するならば、sbOpen.Completed イベントの中で、sbClose.Begin() を呼び出せばよいのですが、そこは、ShowSync の実装で await の羅列を使いためにこうしています。

public sealed partial class YakuModal : UserControl
{
	public YakuModal()
	{
		this.InitializeComponent();

		// アニメーションの完了イベント
		this.sbOpen.Completed += (s, e) => { _completed = true; };
		this.sbClose.Completed += (s, e) => {
			_completed = true;
			this.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
		};

		_picts = new List<Image>();
		_picts.Add( this.pict1 );
		_picts.Add( this.pict2 );
		_picts.Add( this.pict3 );
		_picts.Add( this.pict4 );
		_picts.Add( this.pict5 );
	}

	private List<Card> _cards;
	/// <summary>
	/// 表示する花札を設定する
	/// </summary>
	public List<Card> Cards {
		get { return _cards; }
		set
		{
			_cards = value;
			if (value != null)
			{
				int i = 0;
				foreach (var c in value)
				{
					if (i < _picts.Count)
					{
						_picts[i].Visibility = Windows.UI.Xaml.Visibility.Visible;
						_picts[i].Source = CardUI.GetResName(c.ID);
					}
					i++;
				}
				for (; i < _picts.Count; i++)
				{
					_picts[i].Visibility = Windows.UI.Xaml.Visibility.Collapsed;
				}
			}

		}
	}
	string _message;
	/// <summary>
	/// 表示するメッセージを設定する
	/// </summary>
	public string Message
	{
		get
		{
			return _message;
		}
		set
		{
			_message = value;
			this.textYaku.Text = value;
		}
	}

	// アニメーションの完了フラグ
	private bool _completed = false;
	private List<Image> _picts;

	/// <summary>
	/// ダイアログを開く
	/// </summary>
	/// <returns></returns>
	public Task OpenAsync()
	{
		_completed = false;
		this.sbOpen.Begin();
		this.Visibility = Windows.UI.Xaml.Visibility.Visible;
		Task t = Task.Run(async () =>
		{
			while (_completed == false)
			{
				await Task.Delay(100);
			}
		});
		return t;
	}

	/// <summary>
	/// ダイアログを閉じる
	/// </summary>
	/// <returns></returns>
	public Task CloseAsync()
	{
		_completed = false;
		this.sbClose.Begin();
		Task t = Task.Run(async () =>
		{
			while (_completed == false)
			{
				await Task.Delay(100);
			}
		});
		return t;
	}

	/// <summary>
	/// 指定した時間表示して閉じる
	/// </summary>
	/// <param name=&quot;wait&quot;></param>
	public async Task<Task> ShowAsync(int wait = 0)
	{
		await OpenAsync();
		await Task.Delay(wait);
		await CloseAsync();
		// await が使いたいので、空のタスクを返す
		return new Task(() => { }); 
		// 最後のタスクを返すのでも ok
		// return CloseAsync();
	}
}

ShowAsync メソッドでは指定したミリ秒数で表示待ちをします。こんな風に、OpenAsync, Task.Delay, CloseAsync の羅列で書けるから良いかなと。
このあたりの内部実装はさておき、外部から使うときは、

	await this.yakuDlg.ShowAsync(3000);

な風にしようというのが UIDD なところです。

■実行してみる

ローカルコンピュータで実際に実行してみます。


右からすすーっと花札が出てきて、真ん中で猪鹿蝶が確定。しばくすると、すっと消えます。というモーダルダイアログができます。カードゲームにありがちなアニメーションだし、業務アプリのメッセージでも取り入れできそうな雰囲気です(自画自賛)。ためしに、acer w500 なタブレットPC で動かしてみましたが、スムースに動きます。
ただ、これだとゲームとしてかなり寂しい感じなので、影とかぼかしのエフェクトが入れたいですね…ちょっと調査しますか。

カテゴリー: C#, WinRT, 花札ゲーム | [WinRT] Storyboard とユーザーコントロールでモーダル風ダイアログを作る はコメントを受け付けていません

[WinRT] Storyboard.Clone を作る

アニメーションの場合は、

  1. Storyboard の場合は、Blend でちまちま編集して動作確認
  2. プログラムコードに組み込んで、動作確認
  3. もう一度、Blend に戻って修正。
  4. またまた、プログラムコードに組み込んで、動作確認

っていう繰り返しになるので、Blend でデザイン、Visual Studio でビルドしてからシミュレーターで確認、ってのが定番…になると思うのですが、どうなんでしょう?そんなに複雑な Storyboard は作らないのかな?

■Storyboard.Clone を拡張メソッドで作る

リフレクションとか使って正確に書こうとも思ったのですが、さほど複雑な構造でもないし、入れ子になるクラスは決まっているのでだらだらと150行ほど書きます。

public static class StoryboardExtensions
{
	/// <summary>
	/// Storyboard をコピーする 
	/// </summary>
	/// <param name=&quot;src&quot;></param>
	/// <returns></returns>
	public static Storyboard Clone(this Storyboard src)
	{
		var sb = new Storyboard();
		sb.AutoReverse = src.AutoReverse;
		sb.BeginTime = src.BeginTime;
		sb.Duration = src.Duration;
		sb.FillBehavior = src.FillBehavior;
		sb.RepeatBehavior = src.RepeatBehavior;
		sb.SpeedRatio = src.SpeedRatio;
		foreach (var tl in src.Children)
		{
			var tld = tl.Clone();
			sb.Children.Add(tld);
		}
		return sb;
	}

	public static Timeline Clone(this Timeline src)
	{
		Timeline dest = null;
		if (src is ColorAnimationUsingKeyFrames)
			dest = ((ColorAnimationUsingKeyFrames)src).Clone();
		if (src is DoubleAnimationUsingKeyFrames)
			dest = ((DoubleAnimationUsingKeyFrames)src).Clone();
		if (src is ObjectAnimationUsingKeyFrames)
			dest = ((ObjectAnimationUsingKeyFrames)src).Clone();
		if (src is PointAnimationUsingKeyFrames)
			dest = ((PointAnimationUsingKeyFrames)src).Clone();
		if (dest != null)
		{
			Storyboard.SetTargetProperty(dest, Storyboard.GetTargetProperty(src));
			Storyboard.SetTargetName(dest, Storyboard.GetTargetName(src));
		}
		return dest;
	}
	public static ColorAnimationUsingKeyFrames Clone(this ColorAnimationUsingKeyFrames src)
	{
		var dest = new ColorAnimationUsingKeyFrames();
		foreach (var kf in src.KeyFrames)
		{
			dest.KeyFrames.Add(kf.Clone());
		}
		return dest;
	}

	public static ColorKeyFrame Clone(this ColorKeyFrame src)
	{
		ColorKeyFrame dest = null;
		if (src is LinearColorKeyFrame)
			dest = new LinearColorKeyFrame();
		if (src is DiscreteColorKeyFrame)
			dest = new DiscreteColorKeyFrame();
		if (src is EasingColorKeyFrame)
			dest = new EasingColorKeyFrame();
		if (src is SplineColorKeyFrame)
			dest = new SplineColorKeyFrame();
		if (dest != null)
		{
			dest.KeyTime = src.KeyTime;
			dest.Value = src.Value;
		}
		return dest;
	}

	public static DoubleAnimationUsingKeyFrames Clone(this DoubleAnimationUsingKeyFrames src)
	{
		var dest = new DoubleAnimationUsingKeyFrames();
		foreach (var kf in src.KeyFrames)
		{
			dest.KeyFrames.Add(kf.Clone());
		}
		return dest;
	}
	public static DoubleKeyFrame Clone(this DoubleKeyFrame src)
	{
		DoubleKeyFrame dest = null;
		if (src is LinearDoubleKeyFrame)
			dest = new LinearDoubleKeyFrame();
		if (src is DiscreteDoubleKeyFrame)
			dest = new DiscreteDoubleKeyFrame();
		if (src is EasingDoubleKeyFrame)
			dest = new EasingDoubleKeyFrame();
		if (src is SplineDoubleKeyFrame)
			dest = new SplineDoubleKeyFrame();
		if (dest == null)
			return null;

		dest.KeyTime = src.KeyTime;
		dest.Value = src.Value;
		return dest;
	}
	public static ObjectAnimationUsingKeyFrames Clone(this ObjectAnimationUsingKeyFrames src)
	{
		var dest = new ObjectAnimationUsingKeyFrames();
		foreach (var kf in src.KeyFrames)
		{
			dest.KeyFrames.Add(kf.Clone());
		}
		return dest;
	}
	public static ObjectKeyFrame Clone(this ObjectKeyFrame src)
	{
		ObjectKeyFrame dest = null;
		if (src is DiscreteObjectKeyFrame)
			dest = new DiscreteObjectKeyFrame();
		if (dest == null)
			return null;

		dest.KeyTime = src.KeyTime;
		dest.Value = src.Value;
		return dest;
	}
	public static PointAnimationUsingKeyFrames Clone(this PointAnimationUsingKeyFrames src)
	{
		var dest = new PointAnimationUsingKeyFrames();
		foreach (var kf in src.KeyFrames)
		{
			dest.KeyFrames.Add(kf.Clone());
		}
		return dest;
	}
	public static PointKeyFrame Clone(this PointKeyFrame src)
	{
		PointKeyFrame dest = null;
		if (src is LinearPointKeyFrame)
			dest = new LinearPointKeyFrame();
		if (src is EasingPointKeyFrame)
			dest = new EasingPointKeyFrame();
		if (src is DiscretePointKeyFrame)
			dest = new DiscretePointKeyFrame();
		if (src is SplinePointKeyFrame)
			dest = new SplinePointKeyFrame();
		if (dest == null)
			return null;

		dest.KeyTime = src.KeyTime;
		dest.Value = src.Value;
		return dest;
	}
}

■アニメーションの開始点と終了点を変更する拡張メソッドを作る

アニメーションをする対象と、開始終了点を指定するのですが、色々ややこしいので拡張メソッドを用意しておきます。本当は別クラスのほうがいいんでしょうが、面倒なので Storyboard にくっつけてしまいます。

/// <summary>
/// 各種の値を変えるための拡張メソッド
/// </summary>
public static class StorybaordValueExtentions
{
	/// <summary>
	/// ターゲット名を指定する
	/// </summary>
	/// <param name=&quot;sb&quot;></param>
	/// <param name=&quot;name&quot;></param>
	public static void SetTarget(this Storyboard sb, UIElement el)
	{
		foreach (var tl in sb.Children)
		{
			Storyboard.SetTarget(sb, el);
		}
	}
	public static void SetTargetName(this Storyboard sb, string name)
	{
		foreach (var tl in sb.Children)
		{
			Storyboard.SetTargetName(sb, name);
		}
	}
	public static void SetMovePoint(this Storyboard sb, Point start, Point end)
	{
		((DoubleAnimationUsingKeyFrames)sb.Children[0]).KeyFrames[0].Value = start.X;
		((DoubleAnimationUsingKeyFrames)sb.Children[1]).KeyFrames[0].Value = start.Y;
		((DoubleAnimationUsingKeyFrames)sb.Children[0]).KeyFrames[1].Value = end.X;
		((DoubleAnimationUsingKeyFrames)sb.Children[1]).KeyFrames[1].Value = end.Y;
	}
}

storyboard は複数のターゲットを同時に動かすこともできるので、一律で変えてしまうのは困るのですが、まあ、そのときはそのときで考えるということで。

■実際に動かしてみる

あらかじめ、コピー元の storyboard を blend で作っておきます。

<Page.Resources>
    <Storyboard x:Name=&quot;sbMove&quot;>
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.RenderTransform).(CompositeTransform.TranslateX)&quot; Storyboard.TargetName=&quot;pictAni&quot;>
            <EasingDoubleKeyFrame x:Name=&quot;sbStartX&quot; KeyTime=&quot;0&quot; Value=&quot;180.597&quot;/>
            <EasingDoubleKeyFrame x:Name=&quot;sbEndX&quot; KeyTime=&quot;0:0:1&quot; Value=&quot;182.09&quot;/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;(UIElement.RenderTransform).(CompositeTransform.TranslateY)&quot; Storyboard.TargetName=&quot;pictAni&quot;>
            <EasingDoubleKeyFrame x:Name=&quot;sbStartY&quot; KeyTime=&quot;0&quot; Value=&quot;-152.239&quot;/>
            <EasingDoubleKeyFrame x:Name=&quot;sbEndY&quot; KeyTime=&quot;0:0:1&quot; Value=&quot;171.642&quot;/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Page.Resources>

アニメーションする対象は、あらかじめ、Image.RenderTransform と CompositeTransform を書いておきます。
これは Blend が出力する Storyboard の癖なので、手作業で作れば Margin とか Canvas.Left あたりを直接変更することも可能でしょう。今は、Blend と Visual Studio の行き来を簡単にするということで、Blend のほうにあわせておきます。

<Image x:Name='pict1' HorizontalAlignment=&quot;Left&quot; Height=&quot;100&quot; 
            VerticalAlignment=&quot;Top&quot; Width=&quot;64&quot; Source=&quot;/Images/FC097-2.png&quot;  Margin=&quot;288,151,0,0&quot;>
    <Image.RenderTransform>
        <CompositeTransform/>
    </Image.RenderTransform>

</Image>

自作した Clone と拡張メソッドを使って、pict1 を移動させます。ここでは、Clone した storyboard は使い捨てになっていますが、実際は Initialize 時にキープしておきます。

private void SbClone2Click(object sender, RoutedEventArgs e)
{
	Storyboard sb = this.sbMove.Clone();
	// ターゲットを変える
	sb.SetTarget( this.pict1 );
	sb.SetMovePoint(new Point(0, 0), new Point(100, 200));
	sb.Begin();
}

これで、storyboard の XAML を量産しないでよくなるので(特に開始終了点の名前付けとかが面倒なので)、花札ゲームのアニメーションに活用できそう。なので、再び花札ゲーム製作に戻るということで。

カテゴリー: C#, WinRT | [WinRT] Storyboard.Clone を作る はコメントを受け付けていません

[WinRT] XAML タグを XmlReader.Load を使ってコピーする

WPF の XAML には Clone メソッドがあって、Storyboard とか UI のタグをコピーできたのですが、WinRT の XAML には Clone がありません。継承関係とかすっきりしてメソッドの整理もされいるので、同じ XAML とはいえ挙動が異なる…のが微妙なところですが、まあ、すっきりした分 XAML の描画は高速になっているんだろうなッ!!! と実験してみたいものです。当時、非力…でもないマシンを使っても WPF アプリは結構重かったわけで、では、現在の PC ならばどうでしょうってのが疑問なところですが、描画として XAML と C++/CX の DirectX が直結しているのか否か?そのうえで、GPU は有効に使われているのか?(なんか、CPU のパワーだけを使っている感じがするので)ってのを確かめたうえで、XAML の描画部分を C# のみで頑張るか、C++/CX のパワーを使ったほうが(内部的に DirectX に直結しているという点で)を探っていきたいと思っています。

3Dのアクション系のゲームアプリのように描画速度に完全に依存する場合には、C++/CX と DirectX の組みわせは外せないところなのですが、花札ゲームのような、

  • それぞれのアニメーション以外、あまり画面が動かない
  •  → けれども、アニメーション部分は、それなりにリッチにしたい。

  • 3D ゲームのようにがっつり作るのではなく、手軽にリリースしたい(サンデープログラマー気分で)。
  •  → ロジック部分は、C# を活用するとか、描画じゃない部分をバージョンアップとか。

な形で、View と Logic を完全に分離させた上で、それぞれの速度(描画速度と開発速度)を追求したいということで。

それを踏まえたうえで、リッチな View と作ろうとすると、Blend を使ったデザインが欠かせないわけで、デザイナさまに作っていただいた XAML をいかに、きれにプログラムに組み入れていくのか?ってのが課題。いやいや、デザインするのは自分自身だったりするわけですがね。

■XamlReader.Load を使って、丸ごとコピーする

private void SbCloneClick(object sender, RoutedEventArgs e)
{
	string xaml = @&quot;
<Image 
	xmlns=&quot;&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;&quot;
	HorizontalAlignment=&quot;&quot;Left&quot;&quot; Height=&quot;&quot;100&quot;&quot; 
    VerticalAlignment=&quot;&quot;Top&quot;&quot; Width=&quot;&quot;64&quot;&quot; Source=&quot;&quot;/Images/FC097-2.png&quot;&quot;  Margin=&quot;&quot;288,151,0,0&quot;&quot;>
</Image>
&quot;;
	for (int i = 0; i < 10; i++)
	{
		object obj = XamlReader.Load(xaml);
		UIElement el = obj as UIElement;
		Image img = obj as Image;
		img.Margin = new Thickness(70 * i, 100, 0, 0);
		grid.Children.Add(img);
	}
}

XamlReader.Load を使うときに注意しないといけないのが、

  • ルートは単一のタグであること。
  •  → Xml の Load と同じなので、ルートタグはひとつ。

  • xmlns で名前空間を指定しておく。
  • x:Name=”…” のような名前を外しておく
  •  → x の namespace が必須になるし、プログラムから参照できません。

     → FindName を使っても見つかられないので、意味ないし。

  • イベントハンドラは、後からプログラムで指定。

なところです。バインディングは、どうなるんだろう?今は試していません。
サンプルのコードでは、Image タグを10個作って、grid に追加しています。これぐらいならば new Image() で作っても手間は変わらないのですが、もうちょっと複雑な例があります。

■Storyboard を XamlReader.Load でコピーする

void SbClone()
{
	string xaml = @&quot;
<Storyboard Name=&quot;&quot;sbMove&quot;&quot;
xmlns=&quot;&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;&quot;
>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;&quot;(UIElement.RenderTransform).(CompositeTransform.TranslateX)&quot;&quot; Storyboard.TargetName=&quot;&quot;pictAni&quot;&quot;>
        <EasingDoubleKeyFrame KeyTime=&quot;&quot;0&quot;&quot; Value=&quot;&quot;180.597&quot;&quot;/>
        <EasingDoubleKeyFrame KeyTime=&quot;&quot;0:0:1&quot;&quot; Value=&quot;&quot;182.09&quot;&quot;/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;&quot;(UIElement.RenderTransform).(CompositeTransform.TranslateY)&quot;&quot; Storyboard.TargetName=&quot;&quot;pictAni&quot;&quot;>
        <EasingDoubleKeyFrame KeyTime=&quot;&quot;0&quot;&quot; Value=&quot;&quot;-152.239&quot;&quot;/>
        <EasingDoubleKeyFrame KeyTime=&quot;&quot;0:0:1&quot;&quot; Value=&quot;&quot;171.642&quot;&quot;/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>
&quot;;
	object obj = XamlReader.Load(xaml);
	Storyboard sb = obj as Storyboard;
	// Storyboard.Target を書き換える
	foreach (var tl in sb.Children)
	{
		Storyboard.SetTarget(tl, this.pict1);
	}
}

ある点からある点まで移動するためのアニメーションですが、これをプログラムで書き直すとちょっと手間ですね。複雑な Storyboard を Blend で作成した後に、それをちまちまとプログラムコードに直すのは避けたいところです。
なので、XAML から該当する Storyboard を「手動」でコピーしてきて、コードに貼り付けるって作業ならば、まあ、それなりに許容範囲かと。
ただ、これも問題があって、アニメーションさせる要素を指定するのを、Storyboard.SetTarget を使ってちまちまと書き換えないとダメなんですよね(SetTargetName を使っても名前が設定変更されないので、SetTarget じゃないとダメみたいです)。このあたり、Setter を使う方法もあるんでしょうが、Setter を使うということは、元の Storyboard の XAML に手をいれないといけなくなって、Storyboard を再修正したきの作業手順が多くなってしまうのが難点です。

なので、Storyboard に Clone がないならば、作ってしまおう、ってのが次。

カテゴリー: C#, WinRT | [WinRT] XAML タグを XmlReader.Load を使ってコピーする はコメントを受け付けていません

View を遅延更新する MVVM を作ってみる

MVVM のメリットは、Model のプロパティを変更することによって、View の画面の更新が自動的にされる、というものがあります。まぁ、他にもメリットがあるんだけど、View のもろもろの構造は関係なく、好きなように Model を構築することができる(場合によってはデータベース周りに特化させても ok)ってのが良いのですが、その反面、Model のプロパティへの更新が、そのまま View に伝達されてしまうために、その「即時性」が、かえって View の描画の重さになってしまうってのが、デメリットといえばデメリットです。描画速度が遅くならない程度に、頻繁に Model を更新しなければよいだけの話なんですが…ふと、C++/CX のゲームアプリの説明では、描画のためのデータの更新は、描画をするタイミングまでためておいて、フレームレートの間で描画できる処理をする、というのがあったんので、では、MVVM の View の更新をフレームレート単位で更新するようにすれば、Model の頻繁な更新に耐えられるのでは?と思ったのが、次のアイデアです。

フレームレートっていうよりも、単純にタイマーで定期的に View の更新をしているだけなのです。ちょっとだけ、Model の INotifyPropertyChanged に手を加えます。

■ひとまず全文のコード

遅延用の Model と View のコードはこんな感じ。
スピードを重視するくせに、Queue のコードが遅すぎるだろう、という意見は却下で(苦笑)。

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();

        // 動的バインド
        this.textX.SetBinding( TextBlock.TextProperty, new Binding() { Path = new PropertyPath(&quot;X&quot;) });
        this.textMsg.SetBinding( TextBlock.TextProperty, new Binding() { Path = new PropertyPath(&quot;Msg&quot;) });

        // 遅延 View 更新タイマーを作成
        _timer = new DispatcherTimer();
        _timer.Interval = TimeSpan.FromSeconds(5.0);    // 5秒ごとに更新
        _timer.Tick += _timer_Tick;

        _model = new Model();
		// Queue クラスを作成
        _vq = new ViewQueue();
		// 遅延バインド
        _model.PropertyChangedQueue += _vq.PropertyChanged;
        _timer.Start();
        this.DataContext = _model;
    }

    DispatcherTimer _timer;
    Model _model;
    ViewQueue _vq;


    /// <summary>
    /// このページがフレームに表示されるときに呼び出されます。
    /// </summary>
    /// <param name=&quot;e&quot;>このページにどのように到達したかを説明するイベント データ。Parameter 
    /// プロパティは、通常、ページを構成するために使用します。</param>
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
    }

	/// <summary>
	/// View 更新用のタイマー
	/// </summary>
	/// <param name=&quot;sender&quot;></param>
	/// <param name=&quot;e&quot;></param>
    void _timer_Tick(object sender, object e)
    {
        while (true)
        {
            ViewQueue.PropChange prop = _vq.GetProperty();
            if (prop == null)
                break;

			// model の更新を通知
            var model = (INotifyPropertyChangedQueue)prop.Sender;
            model.OnPropertyChanged(prop.Name);
        }
    }

	/// <summary>
	/// ボタンをクリックして Model を更新
	/// </summary>
	/// <param name=&quot;sender&quot;></param>
	/// <param name=&quot;e&quot;></param>
    private void SetClick(object sender, RoutedEventArgs e)
    {
		// この時点では、View は更新されない。
        _model.X++;
        _model.Msg = string.Format(&quot;{0} カウント {1}&quot;, DateTime.Now, _model.X);
    }
}

/// <summary>
/// View の遅延更新用のインターフェース
/// </summary>
public interface INotifyPropertyChangedQueue : INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChangedQueue;
    void OnPropertyChanged(string name);
}

/// <summary>
/// モデル
/// </summary>
public class Model : INotifyPropertyChangedQueue
{
    private int _x;
    public int X
    {
        get { return _x; }
        set
        {
            _x = value;
			// Change イベントは、Queue のほうに通知
            OnPropertyChangedQueue(&quot;X&quot;);
        }
    }
    private string _msg;
    public string Msg
    {
        get { return _msg; }
        set
        {
            _msg = value;
            OnPropertyChangedQueue(&quot;Msg&quot;);
        }
    }

    public event PropertyChangedEventHandler PropertyChangedQueue;
    /// <summary>
    /// 遅延させるために Queue に配置
    /// </summary>
    /// <param name=&quot;name&quot;></param>
    void OnPropertyChangedQueue(string name)
    {
        if (PropertyChangedQueue != null)
        {
            PropertyChangedQueue(this, new PropertyChangedEventArgs(name));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    /// <summary>
    /// Queue から View にイベントを発生させる
    /// </summary>
    /// <param name=&quot;name&quot;></param>
    public void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged( this, new PropertyChangedEventArgs(name));
        }
    }
}
public class ViewQueue
{
    public class PropChange
    {
        public object Sender { get; set; }
        public string Name { get; set; }
    }

    List<PropChange> _lst = new List<PropChange>();
	/// <summary>
	/// Queue に溜め込んでおく
	/// </summary>
	/// <param name=&quot;sender&quot;></param>
	/// <param name=&quot;args&quot;></param>
    public void PropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        var prop = _lst.Find(p => p.Name == args.PropertyName);
        if (prop == null)
        {
            _lst.Add(new PropChange() { Sender = sender, Name = args.PropertyName });
        }
    }
	/// <summary>
	/// View から更新依頼があったときに通知する
	/// </summary>
	/// <returns></returns>
    public PropChange GetProperty()
    {
        if (_lst.Count > 0)
        {
            PropChange prop = _lst[0];
            _lst.RemoveAt(0);
            return prop;
        }
        else
        {
            return null;
        }
    }
}

ボタンを押すと、Model を更新するのですが、View の更新タイミングは5秒間毎になっています。なので、ボタンをクリックしたときに「反応がにぶい」というかって変な感じになるのですが、Model の変更を即時更新させるか、遅延更新させるかで、プロパティ単位で即時反映(OnPropertyChanged)と遅延反映(OnPropertyChangedQueue)を選択すればよいかと。

■進捗描画に引きずられない MVVM が作れる?

作ってはみたけれど、いまいち使いどころはどうなのか?って疑問はあるのですが、まあ、実験として MVVM モデルで遅延描画できるかどうか?という答えとしては、意外とシンプルに実装できるっていう実例です。

for ( int i=0; i<MAX; i++ ) {
	// 何かの処理
	Run();
	// 進捗を通知
	Model.ProgressRatio = i*100/MAX;
}

な感じで進捗率を画面に表示するための Model を作ったとしましょう。あるいは、Run の中で Model のプロパティを更新しているとか。
この場合、Model のプロパティへの更新が、即座に View に OnPropertyChanged で通知されるために、View が更新されるまで Model が待たされます。これは View の描画処理のスピードもあるのですが、せっかく Run の処理を非常に高速にしているのに、MVVM を使っているがために、View の描画処理に引きずられてしまうのも、おかしな話ですね~、という具合です。DataGridView の DataSource の場合でも、列を Add するたびに View が更新されるものだから、妙に遅くなってしまうという現在の View の実装が問題というものあるのですが。

方法としては、描画している間は、View を更新させないとか、

// View を更新させない
View.Update = false;
for ( int i=0; i<MAX; i++ ) {
	// 何かの処理
	Run();
	// 進捗を通知
	Model.ProgressRatio = i*100/MAX;
}
// 処理がおわったら View を更新させる
View.Update = true;

Model のプロパティを更新するタイミングを間引きするとか、

for ( int i=0; i<MAX; i++ ) {
	// 何かの処理
	Run();
	// 進捗を通知を間引きする
	if ( MAX % 100 == 0 ) {
		Model.ProgressRatio = i*100/MAX;
	}
}

もろもろのテクニックがあるのですが、Model のどのプロパティが、どのように View に影響を与えるのか?逆に View の速度がどうやって、Model のコーディングに影響を与えてしまうのか?という問題が出てきてしまうこと自体が「問題」ではないか?と思ったわけです。Model と View を分離するのだから、Model を更新するタイミングと View を更新するタイミングはずれても構わないだろう、という思惑です。

View の遅延をさせるのですが、Model から View への通知を Queue に貯めますが、実際の値は View を更新するときに取ってきています。

List<PropChange> _lst = new List<PropChange>();
/// <summary>
/// Queue に溜め込んでおく
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;args&quot;></param>
public void PropertyChanged(object sender, PropertyChangedEventArgs args)
{
    var prop = _lst.Find(p => p.Name == args.PropertyName);
    if (prop == null)
    {
        _lst.Add(new PropChange() { Sender = sender, Name = args.PropertyName });
    }
}

なので、キューには「通知があった」ことだけを貯めればよいので、List を使う必要もないのですが、まあ、試作品というとで。
Model のプロパティを変更するためのボタンを連打しても、遅延更新の場合は最後のひとつだけが Quene にたまるので描画が1回しか行われません。CPU/GPU に負担をかけにくい実装、ってことなんですが、本当に負担を掛けないかどうかは、DataGridView とかで実装してみないとわかりません。

使いどころとしては、DataGridView に 2000行ぐらいのデータを表示しようとして、妙に遅くて困るとか、DataGridView を頻繁に更新しているためか画面が固まった感じがする、ってのが解消されるといいかなと。
DataSource プロパティに設定して、コレクションの内容を変えるたびに画面が切り替わる(描画的には、更新された描画部分だけ切り替わっているのでしょうが)ってのが解消されるかなと。

また、コーディング上としては、Model に View が連結されているか否かに関係なく、Model のプロパティを自由に設定できます。先の進捗率の表示のように、頻繁に Model の値を変える場合であっても、描画は定期的にしか再表示されないので、間引き処理とか、画面を描画しないとか、いうテクニックを使わずに済むのでコードが簡単になるかと。

カテゴリー: C# | 1件のコメント

連続したアニメーションをつなぐために、await/async を使う

連続したアニメーションをつなぐために、Completed イベントでつなげる | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/4071

の続きで、storyboard の Completed イベントで連続させるのではなくて、async/await を使ってつなげてみます。

■開始 Image と 終了 Image を渡してアニメする関数を作る

モーダルダイアログを作ったときと同じように、Completed イベントの発生待ち(アニメーションの完了待ち)をします。Task.Delay() をループさせて待つので、ダサいと言えばダサんですが。他によい方法があったら、また検討するということで。
Task.Run に渡すラムダ式に async がついているという不思議なコードですが、これで動くのだからたいしたものです。WinRT の場合には Thread.Sleep がなくて、非同期の Task.Delay を使うためにこんな風になっています。このあたり、Sleep 相当のブロッキング用のタイマーを自作すればよいのか?ちょっと思案中。

/// <summary>
/// アニメーションする関数
/// </summary>
/// <param name=&quot;p1&quot;></param>
/// <param name=&quot;p2&quot;></param>
/// <returns></returns>
private async Task GoAnime( Image p1, Image p2 )
{
	SetMovePos(p1, p2);
	bool _complete = false;
	this.sbMove.Begin();
	this.sbMove.Completed += (s, e) => { _complete = true; };
	await Task.Run(async () =>
	{
		while (_complete == false)
		{
			await Task.Delay(100);
		}
	});
}

■アニメーションを羅列させる

アニメーションさせる GoAnime 関数ができたので、await を付けて羅列させてみます。Completed イベントで書くよりも、ちょっとは状態遷移がみやいかな、と。yeild return の場合はイテレーターを使う必要があって、ちょっとトリッキーな感じがするのですが、これだと自然かと。

/// <summary>
/// 札をクリックして連続アニメーションを開始
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
private async void pictAniClick(object sender, TappedRoutedEventArgs e)
{
	if (_moving == false)
	{
		_moving = true;
		await GoAnime(this.pict1, this.pict2);
		await GoAnime(this.pict2, this.pict3);
		await GoAnime(this.pict3, this.pict4);
		await GoAnime(this.pict4, this.pict1);
	}
	else
	{
		this.sbMove.Stop();
		_moving = false;
	}
}
bool _moving = false;

■汎用的にクラスのメソッドにしてみる

GoAnime メソッドは、画面の内部メソッドなので汎用性がありません。なので、クラスとして括りだすのが良かろうってことで、クラス化してみたのが、これです。

public class AnimeWaitable
{
    public Storyboard sb { get; set; }
    public EasingDoubleKeyFrame startPosX { get; set; }
    public EasingDoubleKeyFrame startPosY { get; set; }
    public EasingDoubleKeyFrame endPosX { get; set; }
    public EasingDoubleKeyFrame endPosY { get; set; }
    public Image pictAni { get; set; }
    public Image pictStart { get; set; }
    public Image pictEnd { get; set; }

    public bool IsCompleted { get; set; }
    public int Result { get; set; }
    public AnimeWaitable()
    {
    }
    public async Task<int> RunAsync(Image pictS, Image pictE)
    {
        this.pictStart = pictS;
        this.pictEnd = pictE;

        var ptS = this.pictStart.TransformToVisual(null).TransformPoint(new Point(0, 0));
        var ptE = this.pictEnd.TransformToVisual(null).TransformPoint(new Point(0, 0));
        this.startPosX.Value = ptS.X;
        this.startPosY.Value = ptS.Y;
        this.endPosX.Value = ptE.X;
        this.endPosY.Value = ptE.Y;
        sb.Completed += (s, e) =>
        {
            this.IsCompleted = true;
            this.Result = 1;
        };
        this.IsCompleted = false;

        this.sb.Begin();
        // アニメーションの完了待ち
        while (this.IsCompleted == false)
        {
            await Task.Delay(100);
        }
        return this.Result;
    }
    public void Stop()
    {
        this.sb.Stop();
        this.IsCompleted = true;
        this.Result = 0;
    }
}

storyboard に座標を設定していた SetMovePos メソッドをクラス内に展開しているので、冗長になっていますが、基本は GoAnime メソッドと同じで、Task.Delay() を使ってアニメーションの完了待ちをしています。
クラス名が AnimeWaitable なのは、awaitable パターンにすればよいのでは?って時の名残りです。最初 awaitable パターンを使おうと思ったのですが、そうする必要がなかったってことですね。awaitable については別途書こうと思います。

アニメーションを連続させるところは、次のように await ani.RunAsync を羅列していきます。ここでは、ぐるぐると回るように List を使っていますが。

async void GoAnime()
{
	var ani = new AnimeWaitable();
	ani.sb = this.sbMove;
	ani.startPosX = this.sbStartX;
	ani.startPosY = this.sbStartY;
	ani.endPosX = this.sbEndX;
	ani.endPosY = this.sbEndY;
	ani.pictAni = this.pictAni;
	ani.pictStart = this.pict1;
	ani.pictEnd = this.pict2;

	var lstS = new List<Image>();
	var lstE = new List<Image>();
	lstS.Add(pict1); lstS.Add(pict2); lstS.Add(pict3); lstS.Add(pict4);
	lstE.Add(pict2); lstE.Add(pict3); lstE.Add(pict4); lstE.Add(pict1);
	this.textMsg.Text = &quot;開始...&quot;;
	int i = 0;
	_go = true;
	_ani = ani;
	while (_go)
	{
		await ani.RunAsync(lstS[i], lstE[i]);
		i = ++i % 4;
		/* このように書き並べることができる
		await ani.RunAsync(pict1, pict2);
		await ani.RunAsync(pict2, pict3);
		await ani.RunAsync(pict3, pict4);
		await ani.RunAsync(pict4, pict1);
		*/
	}
	this.textMsg.Text = &quot;終了...&quot;;
	_go = false;

}

bool _go = false;
AnimeWaitable _ani;

private void StartClick(object sender, RoutedEventArgs e)
{
	if (_go == false)
	{
		GoAnime();
	}
	else
	{
		_ani.Stop();
		_go = false;
	}
}

ここでちょっと奇妙なのは、GoAnime メソッドの中で、while ループでぐるぐるしている間でも StartClick イベントが発生します。ええ、Start ボタンを押すことができます。一見すると while ループで画面が固まるのでは?と思えるのですが、ani.RunAsync は非同期メソッドなので、途中でボタンクリックイベントなどを入れられるんですよね。このあたりが、非同期な書き方 async/await の面白いところです。

逆にいえば、シーケンス図としてどう書き表すのか、というのが難しいところです。RunAsync という名前を付けてあるので非ブロッキングということがわかるので、手続きっぽい書き方をしても UI の各種イベントを阻害しないという不思議な≒ある意味で直感的な、書き方ができるかと。

カテゴリー: C#, WinRT | 連続したアニメーションをつなぐために、await/async を使う はコメントを受け付けていません