WindowsとPhoneでXAMLを切り分けるには?[ユニバーサルWindowsアプリ開発]:WinRT/Metro TIPS
WindowsストアアプリとPhoneアプリで、細部が異なるだけでほぼ同一のUIを共有したい場合にはどうすればよいだろうか。サードパーティ製のライブラリを用いて、これを実現する方法を解説する。
powered by Insider.NET
ユニバーサルプロジェクトの共有プロジェクトでUIを記述しているときに、XAMLコードを切り分けたいと思ったことはないだろうか? Windowsストアアプリ(以降、Windowsアプリ)とWindows Phone 8.1のWindows Runtimeアプリ(以降、Phoneアプリ)で、ほとんど同じUIなので共有プロジェクトに置きたいのだが、ほんの少しだけコントロールやその属性が異なるような場合だ。例えば、WindowsアプリとPhoneアプリで、1カ所だけコントロールを変えたいようなときだ。あるいは、デバッグビルドとリリースビルドで、コントロールや属性を使い分けたいこともあるだろう。そのようなことは標準ではできないが、サードパーティ製のライブラリを利用することで可能になる。本稿ではその使い方を解説する。なお、本稿のサンプルは「Windows Store app samples:MetroTips #82」からダウンロードできる。
事前準備
ユニバーサルプロジェクトを使ってユニバーサルWindowsアプリを開発するには、以下の開発環境が必要である。本稿では、無償のVisual Studio Express 2013 for Windowsを使っている。
- SLAT対応のPC*1
- 2014年4月のアップデート*2適用済みの64bit版Windows 8.1 Pro版以上*3
- Visual Studio 2013 Update 2*4適用済みのVisual Studio 2013(以降、VS 2013)*5
*1 SLAT対応ハードウェアは、Windows Phone 8.1エミュレーターの実行に必要だ。ただし未対応でも、ソースコードのビルドと実機でのデバッグは可能だ。SLAT対応のチェック方法はMSDNブログの「Windows Phone SDK 8.0 ダウンロードポイント と Second Level Address Translation (SLAT) 対応PCかどうかを判定する方法」を参照。なお、SLAT対応ハードウェアであっても、VM上ではエミュレーターが動作しないことがあるのでご注意願いたい。
*2 事前には「Windows 8.1 Update 1」と呼ばれていたアップデート。スタート画面の右上に検索ボタンが(環境によっては電源ボタンも)表示されるようになるので、適用済みかどうかは簡単に見分けられる。ちなみに公式呼称は「the Windows RT 8.1, Windows 8.1, and Windows Server 2012 R2 update that is dated April, 2014」というようである。
*3 Windows Phone 8.1エミュレーターを使用しないのであれば、32bit版のWindows 8.1でもよい。
*4 マイクロソフトのダウンロードページから誰でも入手できる。
*5 本稿に掲載したコードを試すだけなら、無償のExpressエディションで構わない。Visual Studio Express 2013 Update 2 for Windows(製品版)はマイクロソフトのページから無償で入手できる。Expressエディションはターゲットプラットフォームごとに製品が分かれていて紛らわしいが、Windowsストアアプリの開発には「for Windows」を使う(「for Windows Desktop」はデスクトップで動作するアプリ用)。
用語
本稿では、紛らわしくない限り次の略称を用いる。
- Windows: Windows 8.1とWindows RT 8.1(2014年4月のアップデートを適用済みのもの)
- Phone: Windows Phone 8.1
サンプルコードについて
Visual Studio 2013 Update 2では、残念なことにVB用のユニバーサルプロジェクトのテンプレートは含まれていない*6。そのため、本稿で紹介するコードはC#のユニバーサルプロジェクトだけとさせていただく*7。
*6 VB用のユニバーサルプロジェクトは、来年にリリースされるといわれているVisual Studio「14」からの提供となるようだ。「Visual Studio UserVoice」(英語)のリクエストに対する、6月18日付けの「Visual Studio team (Product Team, Microsoft)」からの回答による。
*7 Visual Studio 2013 Update 2のVBでユニバーサルWindowsアプリを作る場合のお勧めは、「The Visual Basic Team」のブログ記事(英語)によれば、PCLを使う方法のようである。しかし、本稿で説明する方法は、PCLでは利用できない。
「XAML Conditional Compilation」について
「XAML Conditional Compilation」(以降、XCC)は、「XAML Spy」(WindowsストアアプリやWPFアプリのXAMLを解析するツール)などで有名なFirst Floor Software(オランダ)が無償で公開しているライブラリである。
このライブラリを組み込むことで、共有プロジェクトにおいてXAMLの切り分けが可能になる。切り分ける条件として、ビルド時の条件付きコンパイルシンボルを利用する。そのため、WindowsとPhoneで切り分けることも、デバッグビルドとリリースビルドで切り分けることもできる。切り分けの対象は、コントロールまたはその属性である。なお、Windows/Phoneだけでなく、Xamarin Formsにも対応しているとのことだ。
XCCを導入するには?
まず、ユニバーサルプロジェクトのソリューションに対して、NuGetからXCCのパッケージをインストールする。
それには、ソリューションエクスプローラーでソリューションを右クリックし、そのメニューから[ソリューションの NuGet パッケージの管理]を選ぶと[NuGet パッケージの管理]ダイアログが表示される。そこで、左のリストで[オンライン]を選び、右上の検索ボックスに「XCC」と入力してXCCのパッケージを検索する。XCCが見つかったら、その脇の[インストール]ボタンをクリックする(次の画像)。
[NuGet パッケージの管理]ダイアログでXCCを検索してインストールする(VS 2013)
ソリューションを右クリックして[ソリューションの NuGet パッケージの管理]を選ぶと、このダイアログが出てくる。左で[オンライン]を選び、右上で検索語句に「XCC」と入力すると、このように中央にXCCが表示される。その右にある[インストール]ボタンをクリックすると、XCCライブラリのインストールが始まる。
なお、筆者の環境では一度の検索ではXCCが検索結果に出てこないことがあった。そのときは、検索を何回か繰り返してみてほしい。
XCCのインストールを指示すると、[プロジェクトの選択]ダイアログが表示される(次の画像)。ここはデフォルトのまま(=WindowsとPhoneの両方のプロジェクトにインストールする)として、[OK]ボタンをクリックしてインストールを実行する。
ユニバーサルプロジェクトのどのプロジェクトにNuGetパッケージをインストールするか問い合わせるダイアログ(VS 2013)
XCCライブラリのインストールを指示するとこのようにダイアログが出てきて、どのプロジェクトにインストールするのかを尋ねられる。ここでは、デフォルトのまま、WindowsとPhoneの両方のプロジェクトにインストールする。
XCCがインストールできたら、切り分けを行いたいXAMLファイルの先頭の要素に属性をいくつか追加する。
ここでは、ユーザーコントロールでやってみよう*8。共有プロジェクトに新しくユーザーコントロールを作り、ファイル名は「MyDatePickerControl.xaml」とする。その先頭の要素(=<UserControl>タグ)に、次のコードのように属性を追加する。
<UserControl
x:Class="MetroTips082CS.MyDatePickerControl"
……省略……
d:DesignWidth="400"
xmlns:win81="condition:WINDOWS_APP"
xmlns:wp81="condition:WINDOWS_PHONE_APP"
mc:Ignorable="d win81 wp81"
mc:ProcessContent="win81:* wp81:*"
>
太字の部分を追加する。なお、元からあった「mc:Ignorable="d"」の記述は削除する。
ここでは、名前空間プレフィックス「win81」(Windows用にコンパイルするときに有効になる)と「wp81」(同じくPhoneで有効)を定義している。この名前空間プレフィックスの名称は自由に変更しても構わない。
なお、デバッグビルドとリリースビルドで切り分けたい場合は、この名前空間プレフィックスの定義部分を「condition:DEBUG」(デバッグビルドで有効)と「condition:!DEBUG」(リリースビルドで有効)とする。
以上でXCCを利用する準備は完了だ。
*8 XCCの現在バージョン(=1.0.2)では、ユーザーコントロールに使用する場合にちょっとした問題がある。もちろん、ビルドや動作に支障はない。そのユーザーコントロールを貼り付けた画面で、デザイン時にそのユーザーコントロールが表示されないのである。将来のバージョンで改善されることを期待したい。
XCCでコントロールを切り分けるには?
一方のプロジェクトだけで使用したいコントロールに、名前空間プレフィックス「win81」/「wp81」を付ければよい。
例えば、WindowsではDatePickerコントロール(Windows.UI.Xaml.Controls名前空間)、PhoneではDatePickerFlyoutコントロール(Windows.UI.Xaml.Controls名前空間)を使いたい場合には、次のコードのように記述する。
<win81:DatePicker /><!-- ←Windowsプロジェクトをビルドするときに有効 -->
<wp81:DatePickerFlyout /><!-- ←Phoneプロジェクトをビルドするときに有効 -->
Windowsプロジェクトをビルドするときには、「win81」プレフィックスの付いたコントロールがビルドされ、「wp81」プレフィックスの付いたコントロールは無視される。Phoneプロジェクトをビルドするときは、その逆になる。
なお、DatePickerFlyoutコントロールはPhone専用であり、Windowsにはないものだ。
また、WindowsとPhoneでコントロールの名前空間が異なるような場合(例えば、Ad SDKのAdControlなど)には、コントロールごとに「xmlns」属性を指定して別々の名前空間にすればよい。
XCCで属性を切り分けるには?
一方のプロジェクトだけに適用したい属性に、名前空間プレフィックス「win81」/「wp81」を付ければよい。
例えば、StackPanelコントロール(Windows.UI.Xaml.Controls名前空間)の積み重ねの方向を、Windowsでは横、Phoneでは縦にしたい場合には、次のコードのように記述する。
<StackPanel win81:Orientation="Horizontal" wp81:Orientation="Vertical">
……省略……
</StackPanel>
このように指定すれば、Windowsでは横方向に、Phoneでは縦方向に、StackPanelの子要素が配置される。
実行結果
以上をまとめて、次のコードのようなユーザーコントロールに仕上げてみた。ボタンをタップすると日付を選ぶフライアウトが出てくるのだが、その日付選択にはWindowsではDatePickerコントロールを、PhoneではDatePickerFlyoutコントロールを使用する。また、ボタンと日付を表示するTextBlockコントロール(Windows.UI.Xaml.Controls名前空間)の位置関係を、Windowsでは左右、Phoneでは上下とした。
<UserControl
x:Class="MetroTips082CS.MyDatePickerControl"
……省略……
d:DesignWidth="400"
xmlns:win81="condition:WINDOWS_APP"
xmlns:wp81="condition:WINDOWS_PHONE_APP"
mc:Ignorable="d win81 wp81"
mc:ProcessContent="win81:* wp81:*"
>
<Grid>
<!-- StackPanelコントロール。Windowsでは横方向、Phoneでは縦方向に積み重ねる -->
<StackPanel win81:Orientation="Horizontal" wp81:Orientation="Vertical">
<!-- AppBarButtonコントロール。タップするとフライアウトが表示される -->
<AppBarButton Icon="Calendar" IsCompact="True" VerticalAlignment="Center" >
<!-- Windows用のフライアウトの定義 -->
<win81:AppBarButton.Flyout>
<Flyout Placement="Right">
<DatePicker x:Name="DatePicker1" FontSize="32" />
</Flyout>
</win81:AppBarButton.Flyout>
<!-- Phone用のフライアウトの定義 -->
<wp81:AppBarButton.Flyout>
<wp81:DatePickerFlyout x:Name="DatePicker1" />
</wp81:AppBarButton.Flyout>
</AppBarButton>
<!-- 年月日を表示するTextBlockコントロール。「DatePicker1」にバインドしている -->
<TextBlock Grid.Column="1" FontSize="30" VerticalAlignment="Center" Margin="5,3,0,0" >
<Run Text="{Binding Date.DateTime.Year, ElementName=DatePicker1, Mode=OneWay}" />年
<Run Text="{Binding Date.DateTime.Month, ElementName=DatePicker1, Mode=OneWay}" />月
<Run Text="{Binding Date.DateTime.Day, ElementName=DatePicker1, Mode=OneWay}" />日
</TextBlock>
</StackPanel>
</Grid>
</UserControl>
入れ子になったタグでは、内側のタグには名前空間プレフィックスを付けなくてよい。ただし、WindowsとPhoneの一方にしかないコントロールの場合には、名前空間プレフィックスを付けていないとデザイン画面でビルドエラーが出る。それは精神衛生上よろしくないので、このコードではDatePickerFlyoutコントロールにも「wp81」プレフィックスを付けてある。
こうした作ったユーザーコントロールを、WindowsとPhoneの画面に貼りつけてビルドし、実行してみると次の画像のようになる。
作成したユーザーコントロールを使用している様子(Windows)
画面の一部分を切り取って示している。
ボタンを置かずにDatePickerコントロールだけを直に配置してもよいのだが、それでは常に同じ大きさのフォントで表示されることになる。このようにすることで表示時は小さいフォント/変更時は大きいフォントにできる(タッチ対応の画面デザインにおけるパターンの1つ)。
作成したユーザーコントロールを使用している様子(Phoneエミュレーター)
Phone専用のDatePickerFlyoutコントロールは、全画面で表示される。また、下端に閉じるためのボタンを持っている。
もしもWindowsと同じようにDatePickerコントロールをフライアウトの中に入れて作ったとすると、ボタンをタップしてフライアウトを開き、そこで日付部分をタッチするとDatePickerFlyoutに切り替わるという2段階の変化になる。
まとめ
XAML Conditional Compilationを利用すると、WindowsとPhoneでXAMLを切り分けられる。一部だけ別のコントロールを使ったり、部分的に属性を異なる値に設定したりできる。WindowsとPhoneでコントロールの配置を微調整することもできるだろう。
XAML Conditional Compilationは次のサイトでソースコードやドキュメントが公開されているので、参考にしてほしい。
Copyright© Digital Advantage Corp. All Rights Reserved.