kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

タグアーカイブ: Metro

Metro アプリケーションテンプレートを読み解く 「新しいアプリケーション」プロジェクトテンプレート


続いて「新しいアプリケーション」のプロジェクトテンプレートを読み解いていきましょう。

image

プロジェクトテンプレートから作成した状態で以下の通りのアプリケーションが作られます。

image

Common 配下は前回の Grid アプリケーションと一緒の気がしますね、クラスライブラリとして共有してしまえば一つ育てれば全部育つという読みは正しい気がします。

DataModel ディレクトリ自体が消えてなくなっていますが BindableBase.cs はあるわけで、好きにモデル作れって事でなくGridテンプレートでの SampleDataSource.cs は実装サンプルとしては重要です。

App.xaml / App.xaml.cs も内容に変化は有りません。

BlankPage.xaml は当然に表示する物が無いのでえらくシンプルになっていて、BlankPage.xaml.cs はLayoutAwarePage の継承ではなくなっています。どうなんでしょう、此処をLayoutAwarePage の派生にしない理由が良く解りません。

xaml 定義をPage から Common のLayoutAwarePage にするには以下、xaml.cs 側のベースクラス変更も行えば普通に動きますので基本としてやっとく方が良いんじゃないでしょうか、DataContext が基本としてDefaultViewModelで見えて ObservableDictionary になっている方が何かとコードも共通化しやすいし。

<common:LayoutAwarePage
    x:Class="FirstPlainApplication1.BlankPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:FirstPlainApplication1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:common="using:FirstPlainApplication1.Common"
    mc:Ignorable="d">

xmlns:common での記述内容はアプリケーションの名前空間の影響を受けますが、Commonをクラスライブラリにしてしまいそれを参照すれば固定される様になりますね。

実際プロジェクトへの項目の追加で基本ページを追加すると LayoutAwarePage の派生が作られます。

image

<common:LayoutAwarePage
    x:Name="pageRoot"
    x:Class="FirstPlainApplication1.BasicPage1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:FirstPlainApplication1"
    xmlns:common="using:FirstPlainApplication1.Common"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

Common をクラスライブラリとして切り離すのはここでちょっと待ったがかかりますね、項目テンプレートとか全部置き換えないといけませんので。まぁ、プロジェクトテンプレート全体を自作するつもりになればたいした話ではないのですが。その気になった人は以下のフォルダの zip を色々いじってみれば良いんじゃないかな。(デフォルトのインストールの場合)

C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\VSWinExpress\ProjectTemplates\CSharp\Windows Metro style\1041

image

プロジェクトテンプレートや項目テンプレートをいじるのが新規環境に対して道具を研ぎあげるという感じの自分なので自分はきっとやりますけどね。

んで、このテンプレートそのものでは LayoutAwarePage も BindableBase も使われていませんので Common をぶちっと削除する事ができます。最少のテンプレートを目指す人はそれも良いでしょう。

使われている要素が少ないのでちょっとした事を試すには良いですが、項目テンプレートで使われている何かをサポートする為の余計なファイルが多いので初心者が道に迷う可能性多々ありな気もしないでもないです。

Metro アプリケーションテンプレート Gridアプリケーションテンプレートを読み解く


というわけで、Metro 開発始めました。 Windows 8 Consumer Preview と Visual Studio 11 Express for Windows 8 を開発環境として利用してアプリケーションを開発する上で、テンプレートから Grid Application テンプレートで開発スタートしてみたので、このテンプレートの基本構成その他読み取った物をつらつらとメモがきしてみます。

プロジェクトのファイル構成

パッと見でCommon, DataModel に多少の C# ファイルがある事のトップに XAML ファイルがあり、XAMLに関連づいた C#ファイルがあります。

image

Common/BindableBase.cs

Common の BindableBase.cs が INotifyPropertyChanged 等データバインディングの為の基本実装をしているようです。INotifyPropertyChanged の実装コードのシグネチャとしては以下のような感じ

protected bool SetProperty<T>(
ref T storage,
T value,
[
CallerMemberName] String propertyName = null)

さっそくC#の新機能が使われていますね CallerMemberName 属性で SetProperty はプロパティ名を引きとっています。

ref T storage で指定されたフィールドに value で指定された値を突っ込み OnPropertyChanged でイベントを発火するというのが基本になってます。 object.Equals で比較して変化の無い場合にはフィールド更新およびイベントの発火をショートカットする様になっています。

object.Equals で比較しているので null チェックが行われてから obj1.Equals(obj2) と同義になりますね。

object Equals( object other ) ですので、object への暗黙変換が発生し、boxing が発生するなんて細かい事に気づいてチューニングする必要は殆どありません。この程度で影響があるとしたらプロパティの連鎖更新が大量に発生する酷いモデルをあなたが作っているという事です。

酷いモデルもあるはずって事でちょっと見てみたのですが、何かがオカシイです。

image

System.Runtime.dll の System.Type をILDASM で見た所、IsClass が無いですよね。

ドキュメントを見た限りでは IsClass は存在する事になっているのですが見当たりません。

IsClass が false であれば object への暗黙変換で boxing が発生するからそれを避けるとかいうコードを書きたくてもどうにもなりません。どうにもならないので次のソースを見る事にします。

Common/BooleanNegationConverter.cs

とりあえずbool値を反転するIValueConverterですねテンプレート内の何処かで使われてる気はしません(検索でヒットせず、コメントアウトしてビルド、実行で問題無し)、うざいと思う人は消しちゃって良いでしょう。ぶっちゃけてノイズだと思います。

Common/BooleanToVisibilityConverter.cs

同じく bool 値をVisibility に変換する IValueConverterで、同様にどこも使ってません。なぜ Common に共通定義してるのか解らない物なので邪魔ならば消してしまいましょう。

Common/LayoutAwarePage.cs

テンプレート内に定義されている各 XAML ページの基本クラスになっています。

いくつかイベントハンドラーが定義されています、GoHome は Frame.CanGoBack が true の間 Frame.GoBack を繰り返すという形で実装されています。

FrameはWindows.UI.Xaml.Controls.Frameで、Metro UI Framework において進む、戻るの一連のUIの進行状況を管理する仕組みを構成しています。(しかし、MSDNのメニューシステム、なんとかならんのでしょうか、日本語でMSDN使うと翻訳できてる部分しかメニューに出てこなくてメニューが全くつながらずで困ります)

このテンプレートでのナビゲーションはこのFrameに一任されていますので正しくFrameを使う事という事が画面遷移に関しての必要事項でありテスト項目となりそうですね。

残念ながらFrameはINavigateを実装していますが、INavigate がカバーしているメソッドセットには CanGoBack や GoBack は含まれていませんのでFakeするのに多少困る面が残りそうです。

Common/RichTextCollumns.cs

Panelを派生したカスタムコントロールによって最終的な詳細ビューで使われるリッチテキスト表示を実装しています。これがここにある意味は「お前らこれぐらいのコントロール自分で書けよ、サンプルをテンプレートに入れてるんだから」って事でしょうね。

完全な表示用コントロールなのでサイズ計算ロジックの置き換えである MeasureOverride ArrangeOverride が主体で後は関連プロパティを実装しているだけですね。

サイズ計算ロジックとしては単純に LoadContent が返した内容をRichTextBlockOverflowとして見て OverflowContent があれば次のブロックを生成する事の繰り返しです。

この実装の中には仮定が含まれている事がコードの見た目で解ります。

まずRichTextBlockOverflow へのキャストです。キャストは戻り値の型が違えば失敗しますが LoadContent が帰した物をノーチェックでキャストしていますので、DataTemplate.LoadContentはRichTextBlockOverflow を返さなければなりません。

そこのところに防御的なアプローチは全く見えませんので全くの専用コントロールという事が見て取れます。

DataModel/SampleDataSource.cs

いわゆる ViewModel と Model のハイブリッドな物がここに実装されています。アプリケーションにする場合には何かしら意味のあるデータにする為にまず此処に手を入れる事になるでしょう。

Model に相当しているのは SampleDataSource クラスです、ここで他のクラスのインスタンスを単純にnew しまくってデータモデルを構成しています。何かしらのサーバないしはデータを持っている物を検索して new する様に変更すればアプリケーションは意味あるデータを表示する様になるでしょう。

SampleDataGroup と SampleDataItem が共通基底 SampleDataCommon から派生されていてそれぞれグループと要素を表現しています。

SampleDataGroup が ObservableCollection<SampleDataItem> を保持し、SampleDataItem はコンテンツとなる文字列を持っています。SampleDataCommon は共通となるタイトルやイメージを保持しているわけでそれほど面倒な構造ではありません。

SampleDataCommon が Common 配下で定義された BindableBase から派生されていますのでINotifyPropertyChanged でのプロパティ更新通知がサポートされます。

アプリケーションとしては完全な参照系なので SampleDataSource であるモデルには更新系が存在しません。サンプルとしてはいかがなものかとか言いませんよね、自分で考えやがれと突き放されただけです。

所でフォルダがDataModel で名前空間がData って気持ち悪くねぇ?気持ち悪い人はテンプレートを直しやがれが例題なのでしょうか?

App.xaml と App.xaml.cs

xaml 側は Common/StandardStyles.xaml をResourceDictionaryとして取り込んでいるほかAppName としてアプリケーション名をリソース定義しているだけです。アプリケーション固有のリソース定義したければここで好きにしやがれって事でしょう。

App.xaml.cs がアプリケーションの開始処理としての非常に重要な事を実装しています。

「コードビハインドにコードを書いたら負けだと思っている」な自分としては非常に気になるわけですが、実装内容としては OnLaunched でモデルからのデータローディング(PreviousExecutionState が Terminated であれば Suspendからの復帰も)と rootFrameの生成と初期Navigate、 OnSuspending でのアプリケーション状態の保存なわけで絶対にアプリケーション固有な領域なので致し方ないかなな気持ちもあります。

他の xaml と xaml.cs

基本的な表示等はすべて xaml側での記述になっておりxaml.csでデータをコネコネは基本しない構造になっています。xaml.cs での記述内容は OnNavigateTo で画面遷移してきた時にDataContextに結び付けられている DefaultViewModel にデータを突っ込んでいる事と Click での FrameへのNavigate 発行だけです。

思いつく例題とか

  1. Common 名前空間に含まれる実装をクラスライブラリに切り出ししなさい。

    特にBindableBase はこの先あなたのお供として延々育てていくべきコードになるでしょう。これをあなたが育て続けられる環境に移すか他のMVVMインフラを導入するかの選択時期は早々に訪れるはずです。どっちにしても作ったアプリケーション毎に微妙に異なる Commonを延々メンテナンスするのが嫌ならCommonの実装が共有できるようになっていなければなりません。
  2. SampleDataSource.cs に相当するソースを自動生成するテキストテンプレートを記述しなさい。

    1をやると意味が出ますが各種データモデルの細かい修正でプロパティの所の記述をチマチマ SetProperty 呼び出しするとか書いてると疲れますのでもっと軽い仕組みにしましょう。

    Office XML を解釈してExcel 方眼紙からデータモデルの実装が自動で起こせるとかすると Excel でプログラムが書けます(やめましょう)し、テキストテンプレートでSQL Server のスキーマを参照すればDBテーブルを射影するのに必要なデータモデルが生成できる事になります。

  3. Suspending / Lounching をちゃんとして、Suspendされて終了された時に元の場所に戻る様にしようよ。