特集:先取りMetro開発体験

Windows 8 RP版でMetroスタイル・アプリ開発を試してみた

デジタルアドバンテージ 一色 政彦
2012/06/07
Page1 Page2

Metroスタイル・アプリのお試し開発(実践編)

プロジェクトの新規作成

 [新しいプロジェクト]ダイアログで[Visual C#]の「Split App (XAML)」テンプレートを選択して、「AtmarkitReader」という[名前]でプロジェクトを新規作成する。これにより、ひな型のファイル群が自動生成され、次の画面のようにApp.xaml.csファイルが開かれる。

分割アプリのひな型ファイル群が自動生成された直後のVS 2012

ひな型状態の「分割アプリ」の挙動を確認する

 取りあえず、ひな型のMetroスタイル・アプリの挙動を確かめておこう。

 ソリューションのビルドと実行を行うと、次の画面のような「グループ項目の選択ページ(Item Page)」が表示される。

グループ項目の選択ページ(Item Page)のひな型

 上記の画面で、グループ項目(=これを@ITのフォーラム項目に変える)の中から[Group Title: 3]をクリックすると、次の画面のような「各グループ項目の詳細ページ(Split Page)」が表示される。

各グループ項目の詳細ページ(Split Page)のひな型

 上記の画面で、左側の詳細項目(=これをフォーラム内のの各記事項目に変える)の1つを選択すると、右側の詳細表示(=これをWebページ表示に変える)が切り替わる。詳細表示部分はスクロールして全体を見ることが可能だ。

App.xamlファイルを理解する

 それでは最初のApp.xaml.csファイルの内容をざっと確認しておこう。

using AtmarkitReader.Common;

using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace AtmarkitReader
{
  sealed partial class App : Application
  {
    public App()
    {
      this.InitializeComponent();
      this.Suspending += OnSuspending;
    }

    // アプリがエンド・ユーザーにより正常に起動されたときに呼び出される。
    // 【パラメータ】args: 起動要求とプロセスの詳細を表すオブジェクト。
    protected override async void OnLaunched(LaunchActivatedEventArgs args)
    {
      // アプリがすでに実行中の場合は、アプリの初期化を繰り返さずに、
      // ウィンドウのアクティブ化のみを行う
      if (args.PreviousExecutionState == ApplicationExecutionState.Running)
      {
        Window.Current.Activate();
        return;
      }

      // ナビゲーション・コンテキストとして動作させるフレームを作成。
      // SuspensionManagerキー「AppFrame」とフレームを関連付ける
      var rootFrame = new Frame();
      SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

      // アプリが終了していた場合は、保存されていたセッション状態を復元する
      if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
      {
        await SuspensionManager.RestoreAsync();
      }

      // ナビゲーション・スタックが復元されないときは、初期ページに遷移する
      if (rootFrame.Content == null)
      {
        if (!rootFrame.Navigate(typeof(ItemsPage), "AllGroups"))
        {
          throw new Exception("初期ページの作成に失敗しました。");
        }
      }

      // フレームを現在のウィンドウに配置し、アクティブ化する
      Window.Current.Content = rootFrame;
      Window.Current.Activate();
    }

    // アプリの実行が中断されたときに呼び出される。
    // 【パラメータ】
    //  sender: 中断要求の送信元。
    //  e: 中断要求の詳細。
    private async void OnSuspending(object sender, SuspendingEventArgs e)
    {
      // 中断を保留してからセッション状態を保存し、最後に保留を解除する
      var deferral = e.SuspendingOperation.GetDeferral();
      await SuspensionManager.SaveAsync();
      deferral.Complete();
    }
  }
}
アプリ全体の動作を実装するコード(App.xaml.cs)
コメント内容など、一部を改変している。

 コードの意味は、コード中のコメントを参考にしてほしい。ここでは、注目すべきポイントだけを説明する。

 このAppクラスには、次の2つのオーバーライド・メソッドがある。

  • OnLaunchedメソッド: アプリが起動されるときの処理を記述する。
  • OnSuspendingメソッド: アプリが中断されるときの処理を記述する。

 いずれもasync/awaitキーワードが使われており、非同期の処理を含んでいることが分かる。OnSuspendingメソッドで保存したセッション状態は、OnLaunchedメソッドで復元されるように実装されている。

 このコードで注目したいのは、「rootFrame.Navigate(typeof(ItemsPage), "AllGroups")」というコード部分だ。「ItemsPage」は、ItemsPage.xamlファイルおよびそのコードビハインド・ファイル(ItemsPage.xaml.cs)で定義されているクラスである。つまり、フレームのNavigateメソッドを呼び出すことで、“AllGroups”というナビゲーション・パラメータ値とともに「ItemsPage」というページに遷移している。

ItemsPage.xaml.csファイルを理解する

 それでは、遷移先のItemsPage.xaml.csファイルを見てみよう。

using AtmarkitReader.Data;

using System;
using System.Collections.Generic;
using Windows.UI.Xaml.Controls;

namespace AtmarkitReader
{
  // グループ項目を一覧表示し、その1つを選択するためのページ
  public sealed partial class ItemsPage : AtmarkitReader.Common.LayoutAwarePage
  {
    public ItemsPage()
    {
      this.InitializeComponent();
    }

    // OnNavigatedToメソッドから呼び出される。遷移後のページ生成を行う。
    // 【パラメータ】
    //  navigationParameter: フレームのNavigateメソッドで渡されたナビゲーション・パラメータ値。
    //  pageState: 前回のセッションでこのページにより保存された状態のディクショナリ。初回は当然、null。
    protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
    {
      // TODO: 以下のサンプル・データを置き換えて適切なデータ・モデルを指定する
      var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);
      this.DefaultViewModel["Items"] = sampleDataGroups;
    }

    // グループ項目がクリックされたときに呼び出される。
    // 【パラメータ】
    // sender: クリックされたグループ項目を表示しているコントロール。
    // e: クリックされたグループ項目を説明するイベント・データ。
    void ItemView_ItemClick(object sender, ItemClickEventArgs e)
    {
      // クリックされたグループ項目の詳細ページ(Split Page)に遷移する。
      // このとき、必要な情報をナビゲーション・パラメータとして渡す
      var groupId = ((SampleDataGroup)e.ClickedItem).UniqueId;
      this.Frame.Navigate(typeof(SplitPage), groupId);
    }
  }
}
グループ項目の選択ページのコード(ItemsPage.xaml.cs)
コメント内容など、一部を改変している。コードの意味は、コード中のコメントを参考にしてほしい。

 このItemsPageクラスには、次の1つのオーバーライド・メソッドと1つのイベント・ハンドラがある。

  • LoadStateメソッド: 状態(つまりデータ)をロードして、それを既定のViewModelに指定する処理を記述する。これにより、指定されたデータがページ上に表示される。
  • ItemView_ItemClickメソッド: グループ項目がクリックされたときの処理(=イベント・ハンドラ)を記述する。

 このページに遷移前のAppクラスで、フレームのNavigateメソッドを呼び出した。それによって、遷移先ページ(つまりこのページ)のOnNavigatedToメソッドが呼び出される。ItemsPageクラスは、LayoutAwarePageクラス(AtmarkitReader.Common名前空間)を継承しており、このLayoutAwarePageクラスのOnNavigatedToメソッド内で、上記のコードのLoadStateメソッドが呼び出される。

 ItemView_ItemClickメソッドでは、Appクラスの遷移処理と同じように、フレーム(ここでは「this.Frame」)のNavigateメソッドを呼び出す。つまり、グループ項目がクリックされると、ナビゲーション・パラメータ値(=groupId変数に格納されたID値)とともに「SplitPage」というページに遷移する。

 SplitPageクラスの説明に入る前に、サンプル・データの部分を、@ITのフォーラム・リストのデータに変更しよう。まずはひな型コードで、どのようにサンプル・データが指定されているのかを調べてみる。

サンプル・データはどのように指定されているのか

 上記のコードを見ると、SampleDataSourceクラスのGetGroupsメソッドを呼び出してサンプル・データを取得している。そこで、Visual Studioのコード・エディタで「SampleDataSource」という文字列上に入力カーソル(=キャレット)を置いて[F12]キーを押し、クラス定義に移動する(これにより「DataModel」フォルダのSampleDataSource.csファイルが開かれる)。

 そこからスクロールダウンしてコードを読み進めると、GetGroupsメソッドの中ではAllGroupsプロパティ値がそのまま戻り値として返されているのが分かる(ちなみに“AllGroups”というナビゲーション・パラメータ値を渡していたが、これ以外の値を渡すと例外が発生するようになっている)。AllGroupsプロパティの型は「IEnumerable<SampleDataGroup>」で、つまりSampleDataGroup型のコレクションである。そのAllGroupsプロパティの値は、コンストラクタで次の画面のような形で、ハード・コーディングで指定されている。

サンプル・データのハード・コーディング
ちなみにVisual Studio 2012では、[ソリューション エクスプローラー]でソース・ファイル項目の左端にある右向きの△をクリックして展開すると、そのファイル内のオブジェクト構造も閲覧できる。この機能はメソッド/プロパティ/フィールド変数を探すときに便利だ。

 上記のコードを見ると、SampleDataGroupオブジェクト(=グループ項目)を作成して、さらにそのオブジェクトのItems.Addメソッドにより、多数のSampleDataItemオブジェクト(=詳細項目)がSampleDataGroupオブジェクト内の項目として追加されている。その後、IEnumerable<SampleDataGroup>オブジェクトのAddメソッドにより、そのSampleDataGroupオブジェクトがSampleDataGroup型コレクションに追加されている。

 ということで、ひな型コードにおけるデータの追加方法は分かったので、このコンストラクタの中身を全てコメントアウトして、@ITの各フォーラムのRSSフィード情報を設定することにしよう。なお、本来なら適切なデータ・モデルに置き換えるべきだが、今回はコード内容を簡単にするために、SampleDataSource/SampleDataGroup/SampleDataItemクラスはそのままの形で再利用する。

サンプル・データをフォーラム・データに置き換える

 SampleDataSourceクラスのコンストラクタでデータを作成しない代わりに、GetGroupsメソッド呼び出しの直前で、詳細項目(=各記事情報)を含むグループ項目(=各フォーラム情報)のデータをRSSフィードから自動生成することにしよう。具体的には、下記のようにSampleDataSourceクラスにGetAllFeedsAsync静的メソッドを追加する。

protected override async void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
  // TODO: 以下のサンプル・データに置き換えて適切なデータ・モデルを作成する
  if (SampleDataSource.NeedAllFeeds)
  {
    await SampleDataSource.GetAllFeedsAsync();
  }
  var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);
  this.DefaultViewModel["Items"] = sampleDataGroups;
}
RSSフィードからデータを生成する非同期メソッドの追加(ItemsPage.xaml.cs)

 LoadStateメソッド定義の「void」の前に「async」キーワードを付けて、追加したGetAllFeedsAsyncメソッドの前に「await」キーワードを付けていることから分かるように、GetAllFeedsAsyncメソッドは非同期に処理を実行する。呼び出し元の後続の処理は、非同期処理の実行完了後に再開するようになっている。

 SampleDataSourceクラスのNeedAllFeeds静的プロパティがtrueか/falseかで、GetAllFeedsAsyncメソッドを呼び出してデータを生成するか/しないかが決まっている。これは次のコードのように、データがまだ格納されていないか/すでに格納されているかで判断している。

public static bool NeedAllFeeds
{
  get
  {
    return _sampleDataSource.AllGroups.Count <= 0;
  }
}
RSSフィードからデータを生成する必要があるかどうかを示すNeedAllFeedsプロパティ(SampleDataSource.cs)
変数「_sampleDataSource」は、SampleDataSourceクラス内の静的なフィールド変数で、SampleDataSource型のオブジェクトである。

 GetAllFeedsAsyncメソッドのコード内容は、次のようになっている。

public static async Task GetAllFeedsAsync()
{
  _sampleDataSource.AllGroups.Clear();

  var group1 = _sampleDataSource.GetGroupFeedAsync("Forum-1",
    "Insider.NETフォーラム",
    ".NETソリューションのための技術情報フォーラム",
    "http://www.atmarkit.co.jp/fdotnet/images/fdotnet_m.gif",
    "http://rss.rssad.jp/rss/itm/fdotnet/rss.xml");

  var group2 = _sampleDataSource.GetGroupFeedAsync("Forum-2",
    "Windows Server Insiderフォーラム",
    "企業情報システムの構築・運用管理を支援する技術情報フォーラム",
    "http://www.atmarkit.co.jp/fwin2k/images/server_insider_title.gif",
    "http://rss.rssad.jp/rss/itm/fwin2k/rss.xml");

  var group3 = _sampleDataSource.GetGroupFeedAsync("Forum-3",
    "Smart & Socialフォーラム",
    "スマートでソーシャルなアプリ開発のための総合技術情報フォーラム",
    "http://www.atmarkit.co.jp/parts/images/logo_fsmart.gif",
    "http://rss.rssad.jp/rss/itmatmarkit/fsmart/rss.xml");

  var group4 = _sampleDataSource.GetGroupFeedAsync("Forum-4",
    "リッチクライアント&帳票フォーラム",
    "リッチクライアント、帳票テクノロジの総合情報フォーラム",
    "http://www.atmarkit.co.jp/fwcr/images/new_logo.gif",
    "http://rss.rssad.jp/rss/itmatmarkit/fwcr/rss.xml");

  _sampleDataSource.AllGroups.Add(await group1);
  _sampleDataSource.AllGroups.Add(await group2);
  _sampleDataSource.AllGroups.Add(await group3);
  _sampleDataSource.AllGroups.Add(await group4);
}
4つのグループ項目を作成する処理が実装されたGetAllFeedsAsyncメソッド(SampleDataSource.cs)
@ITの中から4つのフォーラムをグループ項目としてデータに追加している。

 上記のメソッドでは、GetGroupFeedAsync非同期メソッドが呼ばれている。awaitキーワードのところで各非同期処理の実行が完了するまで待機される。

 ここで呼び出されているGetGroupFeedAsyncメソッドは次のようなコードになっており、このメソッドでRSSフィードから全記事情報を抽出して1つのグループ項目にまとめている。まとまったグループ項目(=フォーラム情報)が、先ほどのGetAllFeedsAsyncメソッド内の「sampleDataSource.AllGroups.Add(await groupX)」というコードにより、データに追加されているというわけだ。

using Windows.Web.Syndication;
using System.Text.RegularExpressions;

private async Task<SampleDataGroup> GetGroupFeedAsync(string uniqueId, string title, string subtitle, string imagePath, string feedUrl)
{
  var group = new SampleDataGroup(uniqueId, title, subtitle, imagePath, feedUrl);

  SyndicationClient client = new SyndicationClient();
  Uri feedUri = new Uri(feedUrl);
  try
  {
    SyndicationFeed feed = await client.RetrieveFeedAsync(feedUri);

    int i = 0;
    foreach (var feedItem in feed.Items)
    {
      i++;

      // 記事へのURLを取得
      string articleUrl = "";
      if (feedItem.Links.Count >= 1)
      {
        articleUrl = feedItem.Links[0].Uri.AbsoluteUri;
      }

      // 記事紹介文からHTMLタグを削除
      Regex re = new Regex("<.*?>", RegexOptions.Singleline);
      string summary = re.Replace(feedItem.Summary.Text, "");

      // 詳細項目を作成
      SampleDataItem item = new SampleDataItem(
        String.Format("{0}-Article-{1}", uniqueId, i),
        feedItem.Title.Text,
        summary,
        imagePath,
        summary,
        articleUrl,
        group);

      group.Items.Add(item);
    }

    return group;
  }
  catch (Exception)
  {
    return null;
  }
}
RSSフィードから全ての記事情報を抽出してグループ項目にまとめる処理が実装されたGetGroupFeedAsyncメソッド(SampleDataSource.cs)
@ITの中から4つのフォーラムをグループ項目としてデータに追加している。

 以上でデータ作成部分の実装は完了だが、コード内で指定している画像ファイルのURLがプロジェクト内からインターネット上に変更となるので、SampleDataCommonクラス(SampleDataSource.csファイル)にある、

this._image = new BitmapImage(new Uri(SampleDataCommon._baseUri, this._imagePath));

という記述を、

this._image = new BitmapImage(new Uri(this._imagePath));

に書き換える必要がある。

 この状態でビルド&デバッグ実行すると、次のように一通り動作する形になっている。

[Insider.NETフォーラム]をクリック
RSSフィードから記事リストを表示できるようになったサンプル・アプリ

 上の画面を見ると、各記事を閲覧するための詳細ページ(Split Page)の右側の部分にURLだけが表示されている。後は、このURL表示をWebページの表示に変更すれば、今回のアプリは完成だ。この処理を実装していこう。

詳細ページへのWebViewコントロールの追加

 これからSplitPageクラスを見ていくわけだが、ここではWebページの表示を行うためのコントロール「WebView」を追加したい。そこで、SplitPage.xamlファイルを開こう。

 すると、次の画面のように表示され、URLがテキスト表示されている部分は「TextBlock」コントロールになっていることが分かる。

SplitPage.xamlファイルを開いたところ

 このコントロールをWebViewコントロールに置き換える。具体的には、次のコードに書き換える。

<!--TextBlock Grid.Row="2" Grid.ColumnSpan="2" Margin="0,20,0,0" Text="{Binding Content}" Style="{StaticResource BodyTextStyle}"/-->
<WebView Grid.Row="2" Grid.ColumnSpan="2" Margin="0,0,0,0" Source="{Binding Content}" />
TextBlockコントロールをWebViewコントロールに置き換える例(SplitPage.xaml)

 <WebView>要素のSource属性で、「Content」プロパティにデータ・バインディングしている。TextBlockコントロールにURLがテキスト表示されていたように、このContentプロパティにURLの文字列が設定されている。厳密には、これはSampleDataItemオブジェクトのContentプロパティである。

 いずれにしてもこれで、記事項目の選択を切り替えれば、それに対応するURLがWebViewコントロールで開かれるはずだ。

選択中の記事項目のWebページ表示の確認

 最後にビルド&デバッグ実行して確かめてみよう。試しにInsider.NETフォーラムの記事項目を選択してみた結果が次の画面だ。

完成したMetroスタイル・アプリの実行例

 以上で、今回の目的どおりのMetroスタイル・アプリが完成した。ソース・コードは下記のリンクからダウンロードできる。

 本稿では、「疑似開発体験」という趣旨で、一連のMetroスタイル・アプリ開発の流れを記事にした。「Metroスタイル・アプリ開発はこういう感じか」というイメージはつかんでいただけただろうか?

 今回の記事で、Metroスタイル・アプリ開発に興味を持った方は、ぜひ下記の記事にも目を通していただけるとうれしい。

 また、Windows 8エンジニアリング・チームのブログも日本語で提供されているので、こちらも参考にしてほしい。end of article

 

 INDEX
  特集:先取りMetro開発体験
  Windows 8 RP版でMetroスタイル・アプリ開発を試してみた
    1.Visual Studio 2012とMetro開発の準備
  2.Metroスタイル・アプリのお試し開発(実践編)


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH