連載
» 2013年09月05日 12時46分 UPDATE

WinRT/Metro TIPS:スナップ状態で画面レイアウトを切り替えるには?[Windows 8.1 ストア・アプリ開発]

Windows 8.1ではビュー状態のサポートがなくなったため、ビューの幅に応じて画面レイアウトを切り替える処理を自前で実装する必要がある。その方法を説明する。

[山本康彦,BluewaterSoft]
WinRT/Metro TIPS
業務アプリInsider/Insider.NET

powered by Insider.NET

「WinRT/Metro TIPS」のインデックス

連載目次

 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アプリでの「スナップ状態」

 Win 8.1アプリではビュー状態が廃止された*2ため、従来のスナップ状態もなくなった。ここでは、幅500px未満で表示されているアプリの状態を「スナップ状態」と呼ぶことにする。

 VS 2013でプロジェクトを作成すると、ビューの最小幅は既定値の500pxになっている。幅500px未満の「スナップ状態」を使うためには、マニフェスト・ファイルの設定を変更する。詳しくは前回の記事を参照してほしい。

画面レイアウトを切り替えるには?

 XAMLコードにVisualStateを従来と同様に定義し、コードビハインドでViewStateを切り替えるようにすればよい。コードビハインドでは、SizeChangedイベント・ハンドラの中で「スナップ状態」かどうかを判定できる。

 以下、ベースにするアプリの用意、サンプル・アプリの準備、VisualStateの定義、そしてコードビハインドの順に説明していく。

ベースにするアプリの用意

 「基本ページ」を持つアプリを作り、それを今後のベース(以降、ベース・アプリ)とする。

 それにはまず、VS 2013で新しくプロジェクトを作るときに[新しいアプリケーション(XAML)]を選ぶ(次の画像)。

[新しいアプリケーション(XAML)]プロジェクトを作成する [新しいアプリケーション(XAML)]プロジェクトを作成する

 これで自動生成される画面のコードビハインドは、ほとんど空っぽのものになる。そのままでは、画面遷移などを作り込むのに非常に苦労することになる。そこで、そのような作り込みがされた画面に置き換えておく。

 一度ビルドしてエラーが出ないことを確認してから、自動生成された「MainPage.xaml」ファイルをソリューション・エクスプローラで削除する。次に、プロジェクトを右クリックして[追加]−[新しい項目]を選ぶ。出てきた[新しい項目の追加]ダイアログで[基本ページ]を選び、名前を「MainPage.xaml」に変更し、[追加]ボタンをクリックする(次の画像)。

[基本ページ]を「MainPage.xaml」という名前で追加する [基本ページ]を「MainPage.xaml」という名前で追加する

 すると、「不足しているファイルを自動的に追加しますか?」という問い合わせダイアログが出てくるので、[はい]をクリックしてそれらのファイルを自動生成させる(次の画像)。

「不足しているファイルを自動的に追加しますか?」と問われる 「不足しているファイルを自動的に追加しますか?」と問われる

 ファイルが追加されるとXAMLエディタが開き、「無効なマークアップ」というエラーが表示されるが、リビルドすれば解消するはずだ。次の画像のように、タイトル付きの画面が「MainPage.xaml」ファイルとして追加されるほかに、プロジェクトに「Common」フォルダが作成され、いくつかのソース・ファイルが追加されている。

完成したベース・アプリのプロジェクト 完成したベース・アプリのプロジェクト
置き換えた「基本ページ」にはタイトルが付いている。また、ソリューション・エクスプローラを見ると、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>

画面に横スクロールが必要なコンテンツを配置する(XAML)
ScrollViewerコントロールは横スクロールするように設定し、その中のStackPanelコントロールはコンテンツが横方向に並ぶようにした。コンテンツとしては、色が付いた正方形を6個配置した。

 次に、マニフェスト・ファイルを開き、ビューの最小幅を320pxに変更しておく(詳しくは前回の記事を参照)。

 以上でビルドして動きを確認しておこう。ビューの幅を最小にしても、当然であるが横スクロールのままだ(次の画像)。

サンプル・アプリ サンプル・アプリ
横スクロールが必要な画面にした。この画像では、ビューの幅は最小の320pxになっているのだが、当然ながら横スクロールのままである。なお、この画面のサイズは、10.6インチの1024×768px。

VisualStateの定義

 それでは、サンプル・アプリに画面レイアウトの切り替え機能を追加していこう。

 まず、「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>

VisualStateの定義を追加する(XAML)
「Snapped」という名前を付けたVisualStateでは、コンテンツは縦スクロールになり、画面の左右中央に配置される。「NotSnapped」は何も変わらないVisualStateだが、「Snapped」から元のレイアウトに戻すときに使用する。

コードビハインド

 コードビハインドには、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

SizeChangedイベント・ハンドラの付け外し(上:C#、下:VB)

 先ほど[基本ページ]として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

SizeChangedイベント・ハンドラ(上:C#、下:VB)
ビューの幅を調べて、500px未満なら「スナップ状態」だとして「Snapped」VisualStateに切り替える。そうでなければ、「NotSnapped」VisualStateに切り替えて元のレイアウトに戻す。

実行結果

 以上で完成だ。実行してみよう(次の画像)。

「スナップ状態」で画面レイアウトが切り替る(上:フルスクリーン、下:「スナップ状態」)
「スナップ状態」で画面レイアウトが切り替る(上:フルスクリーン、下:「スナップ状態」) 「スナップ状態」で画面レイアウトが切り替る(上:フルスクリーン、下:「スナップ状態」)

 なお、今回はサンプルということで省略したが、タイトルや戻るボタンのレイアウトなども同時に切り替えるべきだろう。MSDNの新しいガイドライン(暫定版)によれば、ビューの最小幅を320pxに設定したときは画面レイアウトを切り替えるように推奨されている。以下に引用しておく。

幅が狭いとき (幅が 320 ピクセルから 500 ピクセルの場合) に次の方法でアプリが調整されるようにしておきます。

  • 縦表示を使う。
  • 小さい戻るボタン スタイルを使う。戻るボタンのサイズについて詳しくは、「Segoe UI Symbol アイコンの一覧」をご覧ください。
  • 左余白を 20 ピクセル幅にする。
  • アプリのヘッダー テキストに 20 ポイントのサイズを使う。
  • ページ切り替えアニメーションとコンテンツ切り替えアニメーションに使うオフセット値を小さくする。

まとめ

 Win 8.1アプリではビュー状態のサポートがなくなったため、ビューの幅に応じて画面レイアウトを切り替えるには、自前のコードで行う。

 XAMLコードにVisualStateを従来と同様に定義し、コードビハインドのSizeChangedイベント・ハンドラでViewStateを切り替えるようにすればよい。

「WinRT/Metro TIPS」のインデックス

WinRT/Metro TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

Focus

- PR -

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。