Windows 8.1ではビュー状態のサポートがなくなったため、ビューの幅に応じて画面レイアウトを切り替える処理を自前で実装する必要がある。その方法を説明する。
powered by Insider.NET
Windows 8.1(以降、Win 8.1)用のWindowsストア・アプリ(以降、Win 8.1アプリ)を作り始めると、さまざまな違いに悩むことになる。その1つが「スナップ状態」での画面レイアウトの切り替えだ。Visual Studio 2013(以降、VS 2013)のプロジェクト・テンプレートで自動生成されたソース・コードには、従来のLayoutAwarePageクラスが無くなっているので、どうやって画面のレイアウトを切り替えればよいのかと途方に暮れてしまうのだ。
前回の記事で紹介したように、Windows 8用のソース・コードをWin 8.1用に変換した場合はLayoutAwarePageクラスも変換されるので、画面のレイアウト切り替えも問題なく行えるのだが、VS 2013でアプリをゼロから作り始めた場合にはどうしたらよいのだろうか? 本稿では、その方法を解説する*1。本稿のサンプルは「Windows Store app samples:MetroTips #51(Windows 8版)」からダウンロードできる。
*1 本稿はPreview版に基づいて記述しているため、製品版では異なる可能性がある。あらかじめご承知おきいただきたい。
Win 8.1アプリを開発するには、Win 8.1とVisual Studio 2013(以降、VS 2013)が必要である。本稿執筆時点ではまだ製品版は一般公開されておらず、本稿ではOracle VirtualBox上で64bit版Windows 8.1 Pro PreviewとVisual Studio Express 2013 Preview for Windowsを使用している。これらを準備する方法や注意事項は、前回の記事をご参照いただきたい。
Win 8.1アプリではビュー状態が廃止された*2ため、従来のスナップ状態もなくなった。ここでは、幅500px未満で表示されているアプリの状態を「スナップ状態」と呼ぶことにする。
VS 2013でプロジェクトを作成すると、ビューの最小幅は既定値の500pxになっている。幅500px未満の「スナップ状態」を使うためには、マニフェスト・ファイルの設定を変更する。詳しくは前回の記事を参照してほしい。
*2 ビュー状態の廃止:詳しくは「特集:次期Windows 8.1&Visual Studio 2013 Preview概説(前編):大きく変わるWindowsストア・アプリ開発 〜 ビュー状態に関連する変更点」をご覧いただきたい。
XAMLコードにVisualStateを従来と同様に定義し、コードビハインドでViewStateを切り替えるようにすればよい。コードビハインドでは、SizeChangedイベント・ハンドラの中で「スナップ状態」かどうかを判定できる。
以下、ベースにするアプリの用意、サンプル・アプリの準備、VisualStateの定義、そしてコードビハインドの順に説明していく。
「基本ページ」を持つアプリを作り、それを今後のベース(以降、ベース・アプリ)とする。
それにはまず、VS 2013で新しくプロジェクトを作るときに[新しいアプリケーション(XAML)]を選ぶ(次の画像)。
これで自動生成される画面のコードビハインドは、ほとんど空っぽのものになる。そのままでは、画面遷移などを作り込むのに非常に苦労することになる。そこで、そのような作り込みがされた画面に置き換えておく。
一度ビルドしてエラーが出ないことを確認してから、自動生成された「MainPage.xaml」ファイルをソリューション・エクスプローラで削除する。次に、プロジェクトを右クリックして[追加]−[新しい項目]を選ぶ。出てきた[新しい項目の追加]ダイアログで[基本ページ]を選び、名前を「MainPage.xaml」に変更し、[追加]ボタンをクリックする(次の画像)。
すると、「不足しているファイルを自動的に追加しますか?」という問い合わせダイアログが出てくるので、[はい]をクリックしてそれらのファイルを自動生成させる(次の画像)。
ファイルが追加されるとXAMLエディタが開き、「無効なマークアップ」というエラーが表示されるが、リビルドすれば解消するはずだ。次の画像のように、タイトル付きの画面が「MainPage.xaml」ファイルとして追加されるほかに、プロジェクトに「Common」フォルダが作成され、いくつかのソース・ファイルが追加されている。
この状態を、今後のアプリのベースとする。
ベース・アプリにコードを追加して、横スクロールが必要な画面にする。後ほど、「スナップ状態」で縦スクロールに切り替わるようにしようというわけだ。
「MainPage.xaml」ファイルに、次のコードを挿入する。
……省略……
<TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" ……省略…… />
</Grid>
<!-- ここから -->
<ScrollViewer x:Name="scrollViewer1" Grid.Row="1"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Hidden"
>
<StackPanel x:Name="stackPanel1" Orientation="Horizontal"
Margin="120,40,80,0" VerticalAlignment="Top">
<Frame Width="250" Height="250" Background="Crimson" />
<Frame Width="250" Height="250" Background="Gold" />
<Frame Width="250" Height="250" Background="MediumSpringGreen" />
<Frame Width="250" Height="250" Background="DodgerBlue" />
<Frame Width="250" Height="250" Background="BlueViolet" />
<Frame Width="250" Height="250" Background="White" />
</StackPanel>
</ScrollViewer>
<!-- ここまで -->
</Grid>
</Page>
次に、マニフェスト・ファイルを開き、ビューの最小幅を320pxに変更しておく(詳しくは前回の記事を参照)。
以上でビルドして動きを確認しておこう。ビューの幅を最小にしても、当然であるが横スクロールのままだ(次の画像)。
それでは、サンプル・アプリに画面レイアウトの切り替え機能を追加していこう。
まず、「MainPage.xaml」ファイルに、VisualStateの定義を追加する(次のコード)。Visual Studio 2012では「状態記録の有効化」によりビジュアルに作業することもできた(「WinRT/Metro TIPS:ビューの切り替えを実装するには?[Win 8]」を参照)のだが、VS 2013ではそのサポートは無いようでXAMLコードを直接入力することになる。
……省略……
</ScrollViewer>
<!-- ここから -->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ApplicationViewStates">
<!-- アプリに必要なVisualState(今回はスナップ以外とスナップ)を定義 -->
<VisualState x:Name="NotSnapped"/>
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="stackPanel1"
Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="stackPanel1"
Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0" Value="0,40,0,0"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="stackPanel1"
Storyboard.TargetProperty="HorizontalAlignment">
<DiscreteObjectKeyFrame KeyTime="0" Value="Center"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="scrollViewer1"
Storyboard.TargetProperty="HorizontalScrollBarVisibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Hidden"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="scrollViewer1"
Storyboard.TargetProperty="VerticalScrollBarVisibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!-- ここまで -->
</Grid>
</Page>
コードビハインドには、SizeChangedイベント・ハンドラの付け外しと、ハンドラの中で「スナップ状態」かどうかを判定してViewStateを切り替えるコードを追加する。前述したように、ビューの幅が500px未満のときに「スナップ状態」だとする。
まず、イベント・ハンドラの付け外しは、navigationHelper_LoadStateメソッドとnavigationHelper_SaveStateメソッド*3で行う(次のコード)。
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
Window.Current.SizeChanged += this.WindowSizeChanged; // ←追加
}
private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
Window.Current.SizeChanged -= this.WindowSizeChanged; // ←追加
}
Private Sub NavigationHelper_LoadState(sender As Object, e As Common.LoadStateEventArgs)
AddHandler Window.Current.SizeChanged, AddressOf Me.WindowSizeChanged ' ←追加
End Sub
Private Sub NavigationHelper_SaveState(sender As Object, e As Common.SaveStateEventArgs)
RemoveHandler Window.Current.SizeChanged, AddressOf Me.WindowSizeChanged ' ←追加
End Sub
先ほど[基本ページ]としてMainPage.xamlファイルを置き換えたが、このときに自動生成されたファイルの1つでNavigationHelperクラスが定義されている(NavigationHelper.csファイル)。このクラスはページ間のナビゲーションやアプリの状態情報、ライフ・タイム管理などを司るもので、従来のLayoutAwarePageクラスの機能の一部を引き継いでいる。MainPageクラスのコンストラクタでNavigationHelperクラスのインスタンスが生成され、そのLoadStateイベントとSaveStateイベントに上記の2つのイベント・ハンドラが登録されているのだ。
これらのイベントはページ・ナビゲーションが行われると発生し、ページの状態を復元したり、保存したりするのに使われるが、このアプリの場合、ページ・ナビゲーションがないため結果的に、アプリの起動時と終了時にページの状態の復元と保存を行う目的でこれらのイベント・ハンドラが呼び出される。よって、アプリの起動時と終了時にSizeChangedイベント・ハンドラの付け外しが行われるようになっている。
*3 Preview版のnavigationHelper_SaveStateメソッドは、アプリの中断時に呼び出されないようである。そのため、中断から再開したときもイベント・ハンドラは有効のままになっている。製品版では、アプリの中断時に呼び出されるように修正される可能性があるが、その場合には、再開時にイベント・ハンドラを再び結び付けるコードも必要になる。 。
SizeChangedイベント・ハンドラ自体は、次のコードのようになる。
private void WindowSizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
if(e.Size.Width < 500.0)
VisualStateManager.GoToState(this, "Snapped", true);
else
VisualStateManager.GoToState(this, "NotSnapped", true);
}
Private Sub WindowSizeChanged(sender As Object, e As Windows.UI.Core.WindowSizeChangedEventArgs)
If (e.Size.Width < 500.0) Then
VisualStateManager.GoToState(Me, "Snapped", True)
Else
VisualStateManager.GoToState(Me, "NotSnapped", True)
End If
End Sub
以上で完成だ。実行してみよう(次の画像)。
なお、今回はサンプルということで省略したが、タイトルや戻るボタンのレイアウトなども同時に切り替えるべきだろう。MSDNの新しいガイドライン(暫定版)によれば、ビューの最小幅を320pxに設定したときは画面レイアウトを切り替えるように推奨されている。以下に引用しておく。
幅が狭いとき (幅が 320 ピクセルから 500 ピクセルの場合) に次の方法でアプリが調整されるようにしておきます。
- 縦表示を使う。
- 小さい戻るボタン スタイルを使う。戻るボタンのサイズについて詳しくは、「Segoe UI Symbol アイコンの一覧」をご覧ください。
- 左余白を 20 ピクセル幅にする。
- アプリのヘッダー テキストに 20 ポイントのサイズを使う。
- ページ切り替えアニメーションとコンテンツ切り替えアニメーションに使うオフセット値を小さくする。
Win 8.1アプリではビュー状態のサポートがなくなったため、ビューの幅に応じて画面レイアウトを切り替えるには、自前のコードで行う。
XAMLコードにVisualStateを従来と同様に定義し、コードビハインドのSizeChangedイベント・ハンドラでViewStateを切り替えるようにすればよい。
Copyright© Digital Advantage Corp. All Rights Reserved.