WPF アプリの国際化 (多言語対応) と、実行中の動的な言語切り替え

ご好評頂いている「提督業も忙しい!」ですが、実は日本語だけでなく、英語・中国語 (簡体字)・韓国語の4ヶ国語に対応しています。 また、@KatsuYuzu さんから pull request を頂き、実行中に使用言語を動的に切り替えられるようになりました。

(余談ですが、この pull request の内容について、@KatsuYuzu さんと Twitter の DM で会話していました。 そのため、最終的にマージしたコードに至る経緯等が issue から読み取れなくなっていて、惜しいなぁと思ってこのエントリー書いていたりします)

今回は、WPF アプリにおける国際化 (多言語化) と、実行中にアプリの言語を動的に切り替える手法を紹介します。 国際化、と言うと数値や日時の表記方法、UI 設計 (レイアウト時の自動サイズ設定など) まで含まれますが、今回はアプリ内の表示言語に絞ります。

多言語リソースを用意する

何はともあれ、多言語化されたリソースがなければ始まりません。まずはリソースを用意しましょう。 と言っても、WPF アプリケーションの場合は、Windows Phone アプリや Windows ストア アプリのような多言語アプリ ツールキットが使えるわけではありません。 アセンブリ リソース (Resources.resx) を使用します。

プロジェクトを作成すると Resources.resx が Properties 下に入っているはずです。 なければ (あるいは消してしまった場合は) プロジェクトのプロパティのリソース タブから作成できます。

Resources.resx

さっそくリソースを追加しましょう。 [名前] にリソース名、[値] にリソース値 (実際に画面に表示したりする文字列) を入力します。 また、編集画面右上で [アクセス修飾子] を Public に設定するのを忘れないでください。

Resources.resx (en-US)

次に、別の言語を追加してみます。 Properties 以下に、Resources.<カルチャ>.resx という名前のリソースを作成し、配置します。 (Properties 以下に直接ファイルを追加できないので、いったんプロジェクト直下に作成し、移動するとよいかも)

Resources.ja-JP.resx

カルチャを ja-JP にしたので、日本語です。 先に作った Resources.resx と同じキーを用意し、リソース値に日本語の文字列を指定します。

Resources.ja-JP.resx

これで、英語と日本語のリソースができました。 この状態でビルドすると、以下のようなファイルができます。

bin/debug

ja-jp フォルダーの中身はこれ。

bin/debug/ja-jp

この MultilingualApp.resources.dll はサテライト アセンブリと呼ばれ、カルチャに固有のリソースのみを含んだアセンブリです。 MultilingualApp.exe には、ニュートラル カルチャのリソース (Resources.resx) のみが含まれており、多言語化されたリソース (たとえば、Resources.ja-JP.resx) はカルチャごとに作成されたフォルダー内のサテライト アセンブリからロードされます。

「提督業も忙しい!」version 2.3 の場合、KanColleViewer.exe には英語リソースが含まれており、日本語・中国語 (簡体字)・韓国語のサテライト アセンブリが同梱されています。 (なので、サテライト アセンブリを消して実行すると、英語しか表示できなくなる)

KanColleViewer

なお、Transifex という翻訳プラットフォームを用いて Resources.resx の翻訳・管理を実践した例をまとめたエントリーも書きましたので、ご参考までに。

Transifex でリソース (Resources.resx) の翻訳・管理をする -導入編- http://grabacr.net/archives/1687

XAML からリソースを指定

XAML で多言語化されたリソースを指定し、画面に表示してみましょう。

13 行目、TextBlock の Text プロパティに、先ほど作った Resources.resx の HelloWorld キーを指定しています。 これで、OS のカルチャに対応するサテライト アセンブリがある場合はそちらを使用するようになり、ja-JP の Windows で実行した場合は Resources.ja-JP.resx のリソースが参照されるようになります。

実行したものがこちら。 左が Windows 8.1 Pro (ja-JP)、右が Windows 7 Professional (en-US) です。

Hello world!

動的な言語切り替え

多言語化されたリソースを使用して、アプリケーション実行中に表示言語を切り替えられるようにしてみましょう。 需要があるかどうかは知らない。

下準備

@KatsuYuzu さんから頂いた pull request をベースに、最終的にマージした実装方法です。 まずは、リソースを管理するクラスを別途作成します。 シングルトン パターンになりますが許して…

XAML 側は、Properties.Resources を直接参照するのではなく、ResourceService にデータ バインディングするように変更します。 先ほどの TextBlock の Text プロパティを、以下のように修正しました。

バインディング ソースに ResourceService の唯一のインスタンス (Current プロパティ)、バインディング パスで ResourceService の Resources プロパティから多言語リソースのキーを指定します。

この時点では、結果は同じです。 OS のカルチャに応じてリソースが自動的に切り替わるだけです。

プロパティ変更通知を利用しよう

次に、ResourceService に以下のコードを追加します。

ResourceService クラスに INotifyPropertyChanged インターフェイスを実装し、プロパティの変更通知機能を持たせます。 そして、ChangeCulture メソッドで外部からカルチャを指定できるようにし、カルチャを変更したら Resources プロパティの変更通知イベントを発砲します。

XAML 側は、Current インスタンスの Resources プロパティにデータ バインディングしています。 そのため、Resources プロパティの変更通知を受け取ると、再度 Resources プロパティを参照し、カルチャが切り替わったリソースを取得できます。

あとは、カルチャを変更させるだけ

ResourceService.Current.ChangeCulture("en-US"); のようにカルチャを指定すると、UI の表示言語が即切り替わります。例として、ドイツ語リソースを追加し、UI からドイツ語に変更するようにしました。

sample

最初に紹介した Properties.Resources を直接指定した TextBlock (3 行目) と、ResourceService.Current.Resources にデータ バインディングした TextBlock (4 行目) を両方乗せています。

分離コードは以下。10 行目で ResourceService に対しカルチャの変更を呼び出しています。

ボタンを押すと、以下のようになります。

sample (de)

ResourceService からのプロパティ変更通知を受け取っている 4 行目の TextBlock は、変更されたカルチャに追従し、表示が切り替わりました。 完成。

KanColleViewer もご覧ください

実際に動いているところは、「提督業も忙しい!」で確認して頂くと一目瞭然です。 艦これをプレイしてなくても、起動した直後に設定画面から言語切り替え機能にアクセスできます。

言語切り替え

該当するソースはこちら。

ResourceService.cs https://github.com/Grabacr07/KanColleViewer/blob/3e0a8461561a883814569196ac2df34e4ef8e8c2/source/Grabacr07.KanColleViewer/Models/ResourceService.cs

おわりに

というわけで、@KatsuYuzu さんありがとうございましたスペシャルでした。

今後もこんな感じで、「提督業も忙しい!」の開発ネタを放出していければいいなー、と思っています。

Primitive コントロール探訪 – ToggleButton, RepeatButton

ちょいネタです。
(もともと XAML Advent Calendar 2013 で人が集まらなかったとき用に溜めておいた小ネタ的なもので、再編・放出したく)

Primitive コントロールとは

私が勝手にそう呼んでいるだけですが、System.Windows.Controls.Primitives 名前空間に属しているコントロールのことです。 WinRT では Windows.UI.Controls.Primitives 名前空間 になります。

WPF の場合、Visual Studio の [ツールボックス] の中に出てこないコントロールなので、UI をポトペタ開発していたりすると、意外とその存在に気付きません。 例えば ToggleButton の場合。
ToggleButton (Visual Studio)

Windows ストア アプリの場合は、Visual Studio の [ツール ボックス] に出てきます。 また、WPF でも Blend for Visual Studio ではしっかり出てきます。
Toggle Button (Blend)

Primitive コントロールは、その名の通り一般的なコントロールのベースとなっていたりする、基本的なコントロールです。今回は、その Primitive コントロールをいくつか紹介します。

Continue reading

WPF でウィンドウ位置とサイズを保存・復元しよう

デスクトップ アプリで、ウィンドウの位置を保存したいという要望はちらほら来ます。 ただし、真面目に実装しようとすると、細かい挙動まで実装するのが大変面倒です。 最大化して終了したら、最大化する直前のウィンドウの位置とサイズも保存しておかなければならなかったり。

何かベスト プラクティスはないかなーと思って調べたところ、MSDN に該当するコードがあったので、やってみました。

元ネタはこちら。 http://msdn.microsoft.com/ja-jp/library/vstudio/aa972163(v=vs.90).aspx

結局 P/Invoke だった!

いかな WPF といえど所詮は Windows の民。P/Invoke の運命からは逃れられないのだ…

という冗談は置いておいて、SetWindowPlacement 関数GetWindowPlacement 関数を使います。 以下、ちょっと長いですが、そのための下準備コードです。

WINDOWPLACEMENT 構造体 に、ウィンドウの位置・サイズ・状態を格納します。 normalPosition が通常状態のウィンドウの位置とサイズ、showCmd がウィンドウの状態 (最小化・最大化・通常 など) です。

どこに保存すべきか?

これがまた悩ましいのですが、もっとも簡単な方法は Settings.settings を使ってしまう方法です。 MSDN のサンプルもこれを使っています。

ただ、それでは面白くないので、今回はもう少しだけ工夫して作ります。 ウィンドウ位置の保存方法を外部から設定できるようにしましょう。

ウィンドウ位置の保存と復元のためのインターフェイスとして、IWindowSettings を用意します。 ウィンドウの位置・サイズ・状態を持つ Placement プロパティのほか、保存のための Save メソッド、復元のための Reload メソッドを定義しました。 また、その既定実装として、ApplicationSettingsBase を使ったクラスも用意しました。

ApplicationSettingsBase は Settings.settings と同じく (というか、その Settings クラスは ApplicationSettingsBase を継承しています)、%UserProfile%\AppData\Local\ に設定を自動的に保存するための機構です。 アセンブリのバージョンが変わるとパスが変わるため、通常はバージョンアップ時に設定を引き継げない、等の若干の使いにくさがありますが、特に意識しなければこれで事足ります。

もし、レジストリに保存しなければならない、指定したフォルダーに保存しなければならない、など ApplicationSettingsBase の挙動では要件を満たせない場合は、IWindowSettings を実装したクラスを別途用意しましょう。

設定自動保存ウィンドウ

以上を踏まえ、ウィンドウの位置を自動的に保存・復元できるウィンドウを作ります。

WindowSettings 依存関係プロパティは、ウィンドウ位置の保存・復元手段を外部から与えるためのものです。 何も指定しなければ (= null)、ApplicationSettingsBase を使った既定実装になります。

あとは、P/Invoke 時はお馴染みの SourceInitiazlized イベント (ウィンドウ ハンドルが取れるようになるタイミング) で設定情報を復元し、SetWindowPlacement 関数でウィンドウの位置を設定します。

WINDOWPLACEMENT の length メンバーには、必ず sizeof(WINDOWPLACEMENT) が設定されている必要があります (それ以外の場合は失敗します)。flags は 0 で構いません。 showCmd には、復元した値が SW_SHOWMINIMIZED (最小化状態) であった場合、つまり前回の起動時に最小化されたまま終了した場合は、SW_SHOWNORMAL (通常状態) で表示するための処理を加えています。

同様に、ウィンドウが閉じられる直前で、かつキャンセルされていない場合に、GetWindowPlacement 関数で現在のウィンドウの状態を取得し、保存します。

ね? 簡単でしょう?

サンプル コード

上記コードを使用したサンプル コードを用意しました。 ビルドして、起動 -> サイズ&位置変更 -> 終了 を繰り返してみてください。 サイズと位置、最大化等の状態も保存・復元されます。

RestorableWindowSample (3029 downloads)

設定ファイルは、以下のように %UserProfile%\AppData\Local\RestorableWindowSample に保存されるはずです。

user.config

なお、この方法は、MSDN で公開されているだけでなく、モダンなデスクトップ アプリ実装のためのライブラリである MahApps.MetroMetroRadiance でも使われています。

最大化や最小化直前のウィンドウ位置とサイズは Window.RestoreBounds プロパティでも取得できます。 しかし、SetWindowPlacement 関数に任せておけば、復元した座標ではウィンドウが完全に画面の外に出てしまうような異常ケース (モニターが切断されたときとか) でも自動調整してくれたりと、上手いことやってくれます。 それ専門の関数に任せておいた方が楽ですし、標準で確実な挙動になるでしょう。

以上、ご参考までに。

デスクトップ アプリのタイルをカスタマイズする

あけましておめでとうございます。 今年も C# や XAML を中心に情報発信していきますので、どうぞよろしくお願い申し上げます。


さて、当ブログで公開している艦これツール「提督業も忙しい!」ですが、version 1.2 で @Nrtwd 様より頂いたアイコンが実装されました。 今までは、[スタート] にショートカットを作っても、こんな残念な感じになっておりましたが… 残念ショートカット

version 1.2 からこんな感じに。トースト通知にもアイコンが表示されます。すばらです。 すばらアイコン

だがしかし、タイルの色は黄色っぽくなるのかなーと思っていたら、なぜかグレーのまま。 他のデスクトップ アプリはアイコンの色に近いタイルの色が自動選択されているのに、KanColleViewer はなぜグレーなのか…

ということで、今回はデスクトップ アプリが [スタート] 画面に表示されたときのタイルをカスタマイズします。Windows 8.1 限定です。

*.VisualElementsManifest.xml

その答えは、ズバり VisualElementsManifest.xml 拡張子を付与したファイルです。 実行ファイルと同じフォルダーに、実行ファイルと同じファイル名で、VisualElementsManifest.xml 拡張子を付与します。 こんな感じ。

visualelementmanifest.xml

*.VisualElementsManifest.xml ファイルの中身は、以下のように書きます。

以降、VisualElements 要素の各属性について解説します。

BackgroundColor

タイルの色を指定します。
必須の属性です (指定しないと visualelementmanifest.xml ファイルが正しく認識されません)。

色は、定義済みの  black  silver  gray  white  maroon  red  purple  fuchsia  green  lime  olive  yellow  navy  blue  teal  aqua  を指定するか、もしくは  #54427D  のように 16 進数値で指定します。

ShowNameOnSquare150x150Logo

普通サイズ (150 x 150) のタイルで、アプリ名を表示するかどうかを指定します (デスクトップ アプリは 150 x 150 タイルしか作れません)。
必須の属性です (指定しないと visualelementmanifest.xml ファイルが正しく認識されません)。

on を指定するとタイル上にアプリ名が表示され、off を指定すると表示されなくなります。

上記のコードで、ShowNameOnSquare150x150Logo="on" なのが左のタイル、 ShowNameOnSquare150x150Logo="off" にしたのが右のタイルです。 ShowNameOnSquare150x150Logo

ForegroundText

タイルのテキストの色を指定します。
必須の属性です (指定しないと visualelementmanifest.xml ファイルが正しく認識されません)。また、ShowNameOnSquare150x150Logo 属性に off を指定していても必須となりますので注意してください。

白色のテキストには light、黒いテキストには dark を指定します。

上記のコードで、ForegroundText="light" なのが左のタイル、 ForegroundText="dark" なのが右のタイルです。 ForegroundText

Square150x150Logo および Square70x70Logo

タイルのフルブリード画像を指定します (「フルブリード」って「余白なし」という意味らしいですね)。
この属性は省略可能です。ただし、指定する場合は Square150x150Logo と Square70x70Logo の両方を指定する必要があります。片方しか指定しなかった場合は visualelementmanifest.xml ファイルが正しく認識されません。

以下の条件を満たす画像を指定します。

  • 種類: .png / .jpg / .jpeg / .gif
  • 画像サイズ: 1,024 x 1,024 px 以下
  • ファイルサイズ: 200 KB 以下

以下のサンプル画像を作成しました (といっても、@Nrtwd 様より頂いたアイコンそのままですが!)。透過情報付き PNG ファイル、画像サイズはそれぞれ 70 x 70 px と 150 x 150 px です。 sample

実行ファイルと同じフォルダーに Assets フォルダーを作成、その中に上記のファイルを格納し、*VisualElementsManifest.xml ファイルには以下のように記述しました。

その結果がこちら。 sample

なお、画像は名前付け規則と構造の規則に従い、スケーリング、ハイ コントラスト、ローカライズに対応できるようです。 詳細は MSDN を参照してください (試すのが面倒だった!)。

KanColleViewer

ということで、KanColleViewer は以下のコードを使用し、タイルをカスタマイズしました。

BackgroundColor を指定、色は黄色の補色である紫。トースト通知の色もキレイになりました。 kancolleviewer

画像や設定は .exe に埋め込むわけでなく、外部ファイルで指定しなければならない点、ちょっと残念感はありますが。デスクトップでもタイルを設定できますよ、というお話でした。めでたしめでたし。

オチ

で、そもそも何故アイコンを実装した KanColleViewer がグレーのタイルになっていたのかという話。

このアプリのアイコンは、16/20/24/32/40/48/64/128/256 の各サイズを含む .ico ファイルです。このうち、128 以上のサイズには、グレーの背景色付きのアイコンにしていたわけですが。 icons

どうやら、タイルの色の自動選択に、この背景色の方が使われたのではないかと… タイルのアイコンに 64 (背景色がないやつ) が使用されていたので気付きませんでした。というのを、一緒に調べていた @guitarrapc_tech 先生に言われて気付いた。

あほー

2013 年総括++;

完全に自分向けのチラ裏ネタです。大晦日ということで、今年の振り返り + 来年やりたいことなど。

仕事

ひどかった。

昨年の総括で「プロセス改善に力を入れたい」「後進の育成、一人で何とかするパターンから脱却したい」などという目標を掲げていましたが、何もできませんでした

まず、プロセス改善。 担当していたプロダクトが (大人の事情で) 縮退モードに入ってしまい、ただ 1 人で細々と保守やプロトタイプ開発をしていました。 プロセスもへったくれもなかった。

また、秋以降はそのプロダクトからも離れ、秘伝のソースと悪しき風習伝統を地で行くプロジェクトに応援要員として参画しましたが、こちらはチーム開発のあり方やプロセスに関して口の出しようがありませんでした。 なんせ Visual Studio 2005 + Visual SourceShredder SourceSafe 6.0 が現役でした。 貴重なタイムスリップ経験でした。

そして、後進の育成。 これまた途中で部署異動があった影響で、OJT パートナーとして指導していた新人君と離ればなれになり、誰の面倒を見るわけでもなく。 1 人プロジェクトだったため、新人どころか一緒に仕事をするチーム メンバーもおらず、技術を共有したり議論したりといった場はありませんでした。

どうしてこうなった。 正直運が悪かったと言いたいところですが、運も実力のうちと言うように、訴求力が足りなかったんですかね。 主に自分の力を発揮する + 成長の場を得るスキル、働きかけが足りなかったように思います。

個人活動

12/22 に、艦これツール「提督業も忙しい!」を公開しました。
公開から 3 日で 10,000 ダウンロード、現時点で 17,500 ダウンロードという反響に驚いています。 私自身、このツールがないと艦これできません (熱い自画自賛)。

今後も (私向けの) アップデートを続けていくとともに、C# + WPF で作るモダンな UI のデスクトップ アプリの事例のひとつとして、中身や実装の話なども積極的に共有していきたいと思います。

ただ、まともに GitHub でプロジェクト運営したのが初めてだったのもあり、git/GitHub を使いこなせていない感じが。release ごとに tag つけ忘れたり、wiki が真っ白だったり、GitHub Pages を知らなかったり。 git の教養は今後必須になりそうですね。私にとっての来年の課題です。

また、昨年の総括では「積極的に勉強会に参加」という方針を掲げました。今年はその方針通り、コミュニティでの活動として @tanaka_733 さん主催のめとべや東京スタッフとして運営のお手伝いをしつつ (したのか?)、毎回 LT やセッション登壇させて頂きました。本当にお世話になりました。

Windows 8/8.1 を追いつつ、その Windows 8/8.1 時代のデスクトップ アプリあり方を示した …かった。

提督業

無課金 (無理のない課金) ユーザーです。
が、7 月末に着任して以来、ログインしなかった日はおそらく 1 日もなかったと思われるレベルのドハマリっぷり。あ艦これ。

なお、当司令部の状況

8 月のイベントは E-2 までで無念の敗退でしたが、11 月のイベントは 1 週間で E-5 突破、阿賀野・矢矧も入手する成果。 12 月には 250 周に達した 5-2 で三隈入手、大型艦建造で大鳳入手、と首尾は上々です。 現時点で未入手なのは大和と長波のみ。はやく大和ほしいですネー。

そして 2014 年へ

まず、新年明けて早々になりますが、1 月のめとべや東京勉強会にて早速「提督業も忙しい!」の話で登壇させて頂く予定です。 C# + WPF 中心になると思いますが、具体的な内容までは決めていません (リクエストください!)。 「めとべや」なのに Windows Desktop の話しかしなくて申し訳ありませんが、よろしくお願いします。

勉強会登壇をはじめとした情報発信に関わることは、自分のスキル向上に直結する活動として、来年もしっかり続けていきます。

また、12 月に XAML Advent Calendar を主催させて頂きましたが、個人的にはもっと国内で XAMLer が増えてほしい & XAML そのものが活発化してほしいです。日本語情報少ないです。 私自身の XAML に関する情報発信量を増やしつつ、そういった支援活動もしていきたい (具体的には何かわからんけど)。

あとは…
仕事? はい、いろいろ考え直します。 もっと自分のスキルを生かせる & スキルアップできる場所に行きたいです。

ということで、2013 年、お世話になった方々本当にありがとうございました。来年もよろしくお願い致します。

XAML Advent Calendar 2013 完走!

僭越ながら私が主催させて頂いた XAML Advent Calendar 2013 は、12/25 分の投稿をもって無事完走しました。 師走という多忙な時期にありながら、某悪意しか感じられない記事で「あうとー」の烙印を押されることなく、完璧に走り抜けました。

参加し、記事を投稿して下さった皆さま、そして盛り上げてくださった皆さま、本当にありがとうございました。

XAML Advent Calendar

IT 技術界隈における Advent Calendar とは、12/1 ~ 12/25 までの 25 日間に、延べ 25 人が 1 日ずつ交代で、決められたテーマに基づき自分のブログ等に記事を書くというオンラインイベントです。 gihyo.jp さんの記事で、今年開催された Advent Calendar の一覧を見られます。

私が XAML Advent Calendar 2013 を立ち上げた理由はただ一つ、「いろいろな人の XAML を見たいから」でした。 WPF や Windows Store apps をはじめとした様々なプラットフォームで用いられる XAML は、柔軟性が高く、表現力豊かなマークアップ言語です。

それだけに、人によって様々な XAML があるだろうし、それらをもっとオープンにして日本の XAML 界を盛り上げてほしいという想いで、この Advent Calendar を主催しました。 結果は大成功で、25 回の記事で様々な XAML を見ることができました (中には火曜日でビンゴしていたり、2 回書いてくれた人たちもいますが、それもまたよし)。次は、XAML に的を絞った勉強会などやってみたいですね。

記事分類

勝手ながら記事を分類させて頂いた結果、以下のような結果に。

分類 投稿数
Windows Presentation Foundation (WPF) 10
Windows Workflow Foundation (WF) 1
Windows Store apps 7
UI 共通 7

スタートする前は Windows Store apps の記事が大勢を占めるかと予想していましたが、意外に WPF の記事が多くて驚きです。 デスクトップは終わってなどいないのだ… (Microsoft も、ストアにリソースが割かれているだけで、やめたわけではないと言っているようですし!)

ただ、WPF や Windows Store apps の XAML はベースが違うとはいえ共通で使える知識も多く、実際に UI 共通で使える Tips 等も多く投稿して頂き、とても勉強になりましたね。

おわりに

いろいろな人の XAML を見たいという私の目標は達せられ、無事完走できて本当によかったです。記事を投稿して下さった皆さま、そして盛り上げてくださった皆さま、本当にありがとうございました (再)。 また、某悪意しか感じられない記事を意識しすぎて、一部の方には急かすような形となってしまい、申し訳ありませんでした。

来年もまたやりたいですね。もっと心にゆとりを持ちつつ。

それでは皆さま、よいお年を。そして来年も素晴らしい XAML ライフを。


※心の中で勝手にライバルカレンダーとして認定していた @od_10z さん主催の Windows Phone Advent Calender 2013 も、無事に完走したようです。チッ おでめとうございました!

ItemsControl 攻略 ~ 外観のカスタマイズ

これは、XAML Advent Calendar 2013 の 1 日目のエントリーです。
WPF (または Windows ストア アプリ) におけるコレクション コントロール (ItemsControl) の外観をカスタマイズするための基礎として、4 つのプロパティの使いどころを紹介します。

前置き

WPF でアプリ開発をしていると、そのデザイン性の高さを生かし、WPF っぽい (?) UI のアプリを開発したくなりませんか? なりますよね! 既定のデザインで開発しても、データ バインディング等々の恩恵を多分に受けられるので十分素晴らしいのですが、「その UI だったら WindowsForms で作れるじゃん」なんて思ってしまったり。

なので、私が WPF アプリを開発するときは、最近の Visual Studio や Zune、GitHub for Windows などに見られるモダンなウィンドウ (正式名称教えてください…) で作っていますし (こんな感じの)、実は仕事でもこういったフルカスタムな UI の WPF アプリを開発していました。
このモダンなウィンドウを作る試みは、当ブログの過去のエントリーにもあります。

フルカスタムな UI は、WPF Themes 等から既成のテーマを持ってくるか、Style や Template を使って外観を作りこむ (あるいはその両方) ことになります。外観を作りこむとき、個人的に感じた “頻出かつ難易度が高い UI 部品” が、コレクション コントロールでした。ComboBox や ListBox をはじめとするコレクション コントロールは、外観のカスタマイズに関して高い柔軟性がある一方、Style や Template を指定するプロパティも多く、思い通りの外観を作成するのは大変です (実際、仕事でのチーム メンバーの中にもつまずいている人が多かった印象)。

そこで今回は、(今更感はありますが) コレクション コントロールの外観をカスタマイズする上での基礎をおさらいしましょう。サンプルはすべて WPF でのコードになりますが、Windows ストア アプリ開発でも基本的な考え方は同じ、なはず。

コレクション コントロールの要素

ここで言うコレクション コントロールは、複数の項目の提示をサポートする ItemsControl クラスやその派生クラス (ListBox など) のことを指します。ItemsControl クラスは、用途に応じて 4 つのプロパティを設定し、外観をカスタマイズすることができます。

  • Template property
  • ItemsPanel property
  • ItemContainerStyle property
  • ItemTemplate property

ItemsControl

Blend for Visual Studio (または Expression Blend) を使用する場合は、コントロールを選択した状態で、デザイナー上部の [{コントロール名}] のメニューから、それぞれのプロパティを編集することができます。

Template property

ControlTemplate を指定します。
主に Border コントロール等で全体の枠線や背景色の設定を行うことが多いです。また、コントロール内のどの部分にコレクション本体を配置するかも、ここで決定します (ItemsPresenter)。以下のコード例では、太さ 1px の黒い枠線に対し 10px の余白を開け、そこにコレクション本体を配置しています。

ControlTemplate

ItemsPanel

ItemsPanelTemplate を指定します。
Panel の派生クラスを使用し、コレクション項目をどのようにレイアウトするかを決定します。StackPanel や WrapPanel で垂直または水平方向に並べることが多いでしょう。以下のコードで 3 つの例を示します。

ItemsPanel

ItemsPanelTemplate 内には、必ず Panel から派生する要素が含まれていなければならないため、例えば以下のようなケースではエラーが発生してしまいます。ご注意。
ItemsPanel without Panel

また、ItemsPanel を指定せず、前述の ControlTemplate でコレクション項目の並べ方を指定する方法もあります。ControlTemplate 内に ItemsPresenter を配置する代わりに、Panel の派生クラスを指定し、IsItemsHost プロパティに True を設定します。以下の 2 つのコードは、同じ結果が得られます。

ちなみに ItemsControl とその派生クラスは、その用途に応じて既定の ItemsPanelTemplate を持っています。例えば、ListBox の場合は VirtualizingStackPanel、MenuItem の場合は WrapPanel、StatusBar の場合は DockPanel、など。

ItemTemplate

ここからは、項目ごとの外観のカスタマイズになります。
項目の外観のカスタマイズは、ItemContainerStyle で設定するコンテナー要素と、ItemTemplate で設定するデータ オブジェクトの 2 つが対象となります。

先に、簡単な ItemTemplate から説明しましょう。
ItemTemplate プロパティには DataTemplate を指定し、各項目のデータ オブジェクトをどのように表示するかを決定します。
以下のような SampleData 型のコレクションを画面に表示することを考えます。

ItemsControl の ItemsSource プロパティに Samples をバインドします (1 行目)(MainWindow.DataContext に MainWindowViewModel オブジェクトがバインドしてある前提)。そして、SampleData オブジェクトを表示するための ItemTemplate プロパティに、以下のような DataTemplate を設定してみましょう。

ItemTemplate

このように、コレクションの各項目のデータ オブジェクトをどのように表示するかを決定するのが ItemTemplate です。この例では、データ オブジェクトは SampleData クラスであり、DataTemplate 内の TextBlock に SampleData クラスの各プロパティをバインドして表示させています。

ちなみに、この ItemTemplate を指定しなかった場合、表示されるのは「各オブジェクトの文字列表現」です。上記の例の場合、SampleData オブジェクトを .ToString() した文字列が並びます。また、複数の DataTemplate を定義し、コレクション内のデータに応じて DataTemplate を切り替えたい、などの場合は ItemTemplateSelector プロパティを使用して実現できます。

ItemContainerStyle

項目ごとの外観のカスタマイズ、その 2。
コレクション コントロール (ItemsControl) は、データ オブジェクトを格納する「コンテナー要素」を生成し、そのコンテナー要素を並べて表示します。生成されるコンテナー要素は ItemsControl の派生クラスごとに異なり、例えば ListBox が生成するコンテナー要素は ListBoxItem、ComboBox が生成するコンテナー要素は ComboBoxItem です。

ItemContainerStyle は、そのコンテナー要素をカスタマイズするためのプロパティです。項目にマウスが乗ったときに色を変える、項目が選択されたときに色を変える、といった動作はコンテナー要素が実現しているもので、これらの動作を変更したい場合は ItemContainerStyle を設定しカスタマイズします。

ListBox と、そのコンテナー要素 ListBoxItem をカスタマイズする ItemContainerStyle の例を示します。ListBoxItem は ContentControl の派生型です。コンテナー要素の Template 内に ContentPresenter を配置 (8 行目) して、データ オブジェクト (= ItemTemplate で設定したやつ) を表示できるようにしましょう。

ItemContainerStyle

13 ~ 20 行目の Trigger により、コンテナー要素が選択されたとき (IsSelected が true)、コンテナー要素にマウスが乗ったとき (IsMouseOver が true)、それぞれ要素の背景色が変更されます。

前述の ItemTemplate は、あくまでデータ オブジェクトをどのように表示するかを定義するのに留めるべきで、コレクション内の項目としてどう表示するか (たとえば、項目ごとに区切り線を入れるのか、項目が選択されたときにどういう外観に変わるのか、コレクションの n 番目はどういう外観に変わるのか、など) は、こちらの ItemContainerStyle で設定しましょう。

なお、ItemTemplate 同様、複数の Style を定義しコレクション内のデータに応じて ItemContainerStyle を切り替えたい場合は、ItemContainerStyleSelector プロパティを使用して実現できます。

まとめ

ItemsControl の外観を作りこむための基本として、4 つのプロパティの使いどころを紹介しました。

  • Template property
    コレクション コントロール本体の外観を決定。
  • ItemsPanel property
    Panel の派生型を指定し、項目をどのようにレイアウトするかを決定。
  • ItemContainerStyle property
    コンテナー要素の外観を決定。マウスオーバー、項目の選択などで外観を変化させる場合に使用。
  • ItemTemplate property
    データ オブジェクト (ItemsSource にバインドされた各データ) の外観を決定。

例によって Silverlight 製のおもちゃを用意しようと思っていたのですが、ItemsControl の外観に関するネタはまだあるため、今月中に公開予定の第二回にご期待ください。

外観のカスタマイズを含め ItemsControl を自由に使いこなせるようになると、UI 作成の幅が広がって捗ります (小並感)。最近 Twitter でチラチラとツイートしている自作の艦これブラウザーですが、メイン画面だけでもそこそこ ItemsControl を使っていたり。
KanColleViewer
(赤枠部分が ItemsControl またはその派生クラスで実装したもの。クリックで拡大)。
ソースは GitHub で公開していますので、興味のある方は是非。

XAML Advent Calendar 2013、明日は @kaorun さんです。よろしくお願いします!

Surface 2 の誘惑には勝てなかった

本日から、Surface 2 / Surface Pro 2 が同時発売。
初代 Surface RT / Surface Pro が米国から半年遅れだったのに対し、今回はたったの 3 日遅れということで、いい意味で予想を裏切られました。

さっそく電気屋に行ったら

欲しくなっちゃった ☆〜(ゝ。∂)

Surface 2 (32 GB) + Type Cover 2 (Purple) お買い上げです。
IMG_3938

というわけで、開封の儀~雑感など。

開封の儀

さっそく開封。箱の作りは先代 Surface RT と同じです。
IMG_3939
IMG_3941
IMG_3942

本体の下から、Skype と SkyDrive の 特典クーポン が。裏面にクーポンコードが記載されています。
IMG_3943

こちらは AC アダプター。Surface 本体側のコネクターの形状だけ変わっていて、コンセント側は変わっていないように見えます。本体側のコネクターについては後述。
IMG_3944

続いて本体。Surface 2 は、本体の色が黒からシルバーに変更 (キレイに撮るのむずかしい…)。初代 Surface RT ほどツルツルしておらず、傷や指紋がつきにくそうで、とても好印象。また、24° に加え 40° でも固定できるようになったキックスタンドは、どちらで使用しても安定したのでよし。膝に乗せて使ったときは、40° 固定がぴったりでした。IMG_3947
IMG_3948
IMG_3946
IMG_3945

電源コネクターは、従来より LED がわかりやすい位置に変更。覗き込まなくても、光ってるのが確認できるようになりました。上が新しいアダプターで、下が従来のアダプター。もちろん従来のも使えます。
IMG_3954
IMG_3955

次は、ちょっとだけ薄くなった Type Cover 2。先代 Type Cover と打鍵感は変わらず。ただしタッチパッドのボタン部分は、従来のボタン型でなくタッチ型に変更されていました。装着したときのカチっという感じも変わらず。
IMG_3949
IMG_3952

そして、このバックライト。真っ暗な中でもしっかり識別できます (キレイに撮るのむずかしい…)。
IMG_3950

個人的に、タブレットやスマートフォン等のデバイスには必ず液晶保護シートを張っているのですが、Surface 2 専用のものはまだ発売されていない模様。電気屋の店員からは、「一応 Surface RT のものでも対応できる」とは言われましたが、ご覧のとおりセンサーの位置が異なるので (上が初代 Surface RT、下が Surface 2)、今回は見送り。そのうち Surface 2 対応のものが発売されるでしょう。
IMG_3958

雑感

ざっと触ってみた感想。

  • 本体のシルバー&質感がよかった (指紋&傷つきにくそう)
  • ただし、Pro ほどではないけど、使い込んでると本体がそこそこ熱くなる
  • 2 段階のキックスタンド、どっちで固定してもしっかり安定して使える
  • 膝に乗せて使うときは、40° のキックスタンドがちょうどいい角度
  • 電源アダプターの形状変更で、LED が見やすく
  • Type Cover 2 のバックライトは真っ暗でも普通に使えるレベル
  • Surface RT とセンサー位置が異なるので、液晶保護シートの流用はおすすめしない

本体は Surface RT –> Surface 2 で、キーボードは Type Cover –> Type Cover 2 で、それぞれ正当な進化、という感じでした (小並感)。

CPU パワーやバッテリー駆動時間等々、ほかにも改善されている点はいくつもあるようなので、もう少し使い込んでからレポートしてみたいです。

Windows 8.1 でバージョン判別するときの注意点

Windows 8.1 が公開されました!

ということで、Windows 8.1 に関するネタを 1 つ。

Windows 8.1 のバージョン

Windows 8.1 になり、OS バージョンは 6.3 に上がりました。

OS バージョン
Windows Vista 6.0
Windows 7 6.1
Windows 8 6.2
Windows 8.1 6.3 ← New!

Windows 8.1 固有の機能 (例えば、Per-Monitor DPI とか) を使いたい場合などで、実行中の OS が Windows 8.1 かどうか判別するためには、 OS バージョンが 6.3 であるかどうかを確認すればよい、ということになります。

バージョンを取得してみる

というわけで、.NET Framework のアプリケーションでバージョンを取得します。

オーソドックスに、Environment.OSVersion を使いましょう。

バージョンを表示するだけの簡単なコンソールアプリを組みました。

これを、「Visual Studio ホスティング プロセスを有効にする」にチェックを入れた状態 (たぶん、既定の設定) でデバッグ実行します。

スクリーンショット (10)
スクリーンショット (8)

NT バージョン 6.3 を取得できました。

ところが、これを「デバッグなしで実行」をすると…
スクリーンショット (9)

はい、取れません。

Windows 8 のバージョン 6.2 が帰ってきます。

デバッグ実行している間は正しいバージョン 6.3 が取れるのに、リリース ビルドで実行すると正しいバージョンが取れないという、非常にややこしい状況になります。

これは、Win32 API の GetVersion(Ex) 関数を使っても同じです (というか、Environment.OSVersion は内部で GetVersion 関数を呼んでいるので、同じなのは当たり前!)。

対処方法

これは、実はバグでもなんでもなく、意図してこのような動作になっています。

Operating system version changes in Windows 8.1 and Windows Server 2012 R2
http://msdn.microsoft.com/en-us/library/windows/desktop/dn302074.aspx

That means that while you can still call the APIs, if your app does not specifically target Windows 8.1, you will get Windows 8 versioning (6.2.0.0).

Windows 8.1 をターゲットとしない限り、Windows 8 のバージョンが返ってくる、というのが仕様のようです。ですので、Windows 8.1 をターゲットとしていることを、マニフェストで宣言します。

Visual Studio 2013 でアプリケーション マニフェスト ファイルを新規追加すると、以下のようにコメント アウトされた状態で既に記述されているので便利です。
SS131024005622KD

マニフェストで Windows 8.1 をターゲットとしていることを宣言した状態で、先ほどと同じく「デバッグなしで実行」すると…
SS131024010120KD

しっかり取得できました。

もちろん、GetVersion(Ex) 関数を使った場合も、同様に Windows 8.1 のバージョン 6.3 を取得できます。