最新のアプリ

Windows プラットフォーム向けユニバーサル アプリのビルド

Rachel Appel

Rachel AppelWindows プラットフォーム向けにアプリを開発する場合、ユニバーサル アプリを作成することにより、1 つの共有コードベースで Windows 8.1 と Windows Phone 8.1 の両方をターゲットにするアプリを作成できるようになります。ユニバーサル アプリは Windows にとっては新しい考え方ですが、ユニバーサル アプリを使用すれば、JavaScript、C#/Visual Basic、および C++ のコードの重要な部分を Windows エコシステム全体で共有できるようになります。

Build 2014 カンファレンスで、マイクロソフトは、ユニバーサル アプリの概念を将来 Xbox まで広げる予定であることを発表しました (bit.ly/1p19O7O、英語)。Xbox 向け開発に今すぐ取り掛かることはできますが、Visual Studio 2012 といくつか回避策が必要です。これは、ゲームやメディア アプリをビルドする開発者には特に魅力的なニュースです。現時点では、さまざまな Windows プラットフォーム向けにアプリをビルドすることから始めて、その後、Xbox プロジェクトを追加することになります。今回は、Windows と Windows Phone に目を向けます。

Windows プラットフォームや Xbox プラットフォーム以外では、ユニバーサル アプリを使用して、HTML や JavaScript でクロスプラットフォーム アプリを作成できます。厳密には HTML5 を記述できます。iOS や Android 向けの JavaScript 用 Windows ライブラリ (WinJS) を使用しないですみます。Windows エコシステムのネイティブ機能に限定して WinJS を使用することもできます。XAML と C# を使用している場合は、Xamarin のようなツールを使用して、主要プラットフォームすべてに発行できます。

ユニバーサル アプリへの着手

ユニバーサル アプリは、Windows 8.1 に Visual Studio 2013 Update 2 をインストールし、JavaScript、C#/Visual Basic、および C++ 用プロジェクト テンプレートを使用してビルドします。つまり、Windows の最新版で好みの言語を使用してアプリをビルドできます。

新しいユニバーサル アプリの構造は、少なくとも 3 つのプロジェクト (Windows、Windows Phone、および共有) を含む Visual Studio ソリューションです。また、ユニバーサル アプリのプロジェクトの種類は、空白のテンプレート、ハブ テンプレート、およびナビゲーション テンプレートの 3 種類から選びます。Visual Studio の [新しいプロジェクト] ダイアログ ボックスで、これら 3 つの新しいテンプレートのいずれかを使用してユニバーサル アプリを作成します。

Visual Studio は 3 つのプロジェクトを含むソリューションを作成するため、当然ながら、共有コードを含めるのは共有プロジェクトになります。ここにはできるだけ多くのコードを含めます。コードの共有と再利用には、メンテナンスが容易になる、バグ修正のコストが削減されるなど、多くのメリットがあります。もちろん、過ぎたるは及ばざるがごとしです。すべてを共有しようとすると、最終的には、if ステートメントや switch ステートメントなど、コード内の分岐構造が多くなりすぎ、手に負えなくなります。経験から言って、多くの場所で使用するコードをコピーして貼り付けていると感じたら、そのコードは個別のプロジェクトに分離すべきです。この考え方を DRY (Don't Repeat Yourself、同じことを繰り返さない) と呼びます。コードは DRY を多く取り入れるほど良くなります。

共有プロジェクトは、.shproj 拡張子の付いたプロジェクト ファイルです。共有プロジェクトの動作は通常のプロジェクトと変わりませんが、コンパイルの結果として出力やパッケージが作成されません。共有プロジェクトは、参照、またはコピーと貼り付けを行うことなく複数のプロジェクト間でコードを使用できるようにする手段にすぎません。Windows 8.1 では Windows ランタイムが両方の OS 間に拡張されるため、このようにコードを共有できます。API のメンバーによってはプラットフォーム間で互換性がないものもありますが、互換性のないメンバーの呼び出しは対応するプロジェクトに対して行うことができます。

図 1 は、シンプルなユニバーサル アプリのアーキテクチャを示しています。このアプリは、Countdown という名前で、ユーザーが入力したイベントの日付までの日数と、さまざまなイベントまでの日数を表示します。

Windows 8.1 プロジェクト、Windows Phone 8.1 プロジェクト、および共有プロジェクトを並べて表示
図 1 Windows 8.1 プロジェクト、Windows Phone 8.1 プロジェクト、および共有プロジェクトを並べて表示

図 1 から分かるように、各プロジェクトがそれぞれ独自の UI コードを含みます。共有プロジェクトは、いくつかの JavaScript ファイルと共有ホーム ページを含みます。

プロジェクト間でのコードの共有

各プラットフォーム プロジェクトには、default.html と、アプリの開始点としての役割を果たす .js ファイルがあります。これらは、対応するプラットフォーム固有の JavaScript ファイルを参照します。今回の例では、ライフサイクル管理に特別なコードは必要ありません。ただし、特別なライフサイクル管理を行う場合は、アプリに対するニーズがどの程度 OS 固有であるかに応じて、これらのファイルを分離する必要があります。

Windows の default.html への参照は次のとおりです。

<script src="//Microsoft.WinJS.2.0/js/base.js"></script>
<script src="//Microsoft.WinJS.2.0/js/ui.js"></script>

Windows Phone の default.html への参照は次のとおりです。

<script src="//Microsoft.Phone.WinJS.2.1/js/base.js"></script>
<script src="//Microsoft.Phone.WinJS.2.1/js/ui.js"></script>

つまり、Windows の場合は WinJS 2.0、Windows Phone の場合は WinJS 2.1 への参照になります。後者には、Windows Phone の違いを活かす JavaScript が含まれています。

Countdown サンプル アプリでは、各プラットフォーム プロジェクトにページ ディレクトリがあります。各ディレクトリには、ページごとにサブディレクトリがあり、そのサブディレクトリは、ページ自体と関連項目 (CSS ファイルや JS ファイルなど) を含みます。図 1 では、ページが home、addEvent、および privacy であることがわかります。これらのフォルダーは、実行時にプラットフォームと共有プロジェクトの間でマージされます。一意名が付いたファイルは、各フォルダーに 1 つだけ含めることができます。たとえば、home.html ページは /pages/home/ ディレクトリに保存できますが、保存できるのは 1 つのプロジェクトでのみです。今回の場合は、共有プロジェクト フォルダーに配置しています。

home ページは、カウントダウン イベントを表示する、アプリのメイン ページです。図 2 は、home.html の共有 UI コードを示します。対応する home.css ファイルがそれぞれのプロジェクトにあるのが分かります。つまり、複数のプロジェクトで同じ CSS クラス名を使用する場合は、同じ HTML を使用できます。ただし、今回の場合は図 2 に示すように、プラットフォームごとに異なる CSS を使用してスタイルを設定し直しています。ベースとなる HTML を 1 つ用意し、プラットフォーム プロジェクトごとに異なる CSS を使用してスタイルを変えることができます。

図 2 両方のプロジェクトのホーム ページを作成する HTML と CSS

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Countdown</title>
  <link href="/pages/home/home.css" rel="stylesheet" />
  <script src="/js/MobileServices.js"></script>
  <script src="/js/data.js"></script>
  <script src="/pages/home/home.js"></script>   
</head>
<body>
  <div id="contenthost">
    <h1 id="title" class="title">Countdown from</h1>
    <div id="maincontent">
      <div id="listViewTemplate" data-win-control="WinJS.Binding.Template"
        class="win-container">
        <div data-win-bind="style.background: color" class="win-item">
          <h1 class="h1" data-win-bind="textContent: daysToGo"></h1>
          <h2 class="h2"> days to go until</h2>
          <h2 class="h2" data-win-bind="textContent: eventTitle"></h2>
          <h2 class="h2" data-win-bind="textContent: eventDate"></h2>
        </div>
      </div>
      <div id="listView" data-win-control="WinJS.UI.ListView" 
           class="win-listview"
           data-win-options="{ selectionMode: 'single' }">
      </div>
    </div>
    <div id="addEventHTMLControl">
    </div>
  </div>
</body>
</html>
<!-- CSS for the Windows  ListView -->
#listView {
height:500px;
}
#listView .win-viewport {
  height:415px;
}
#listView .win-item {   
  width:300px;
  height:185px;
  padding: 0 0 0 5px;
}
.h1 {
  font-size:1.5em;     
}
.h2 {
  font-size:1em;       
}
<!-- CSS for the Windows.Phone ListView -->
#listView .win-item {   
  width:100%;
  height:125px;
  padding: 5px 0 0 5px;
}
.h1 {
  font-size:2em;     
}
.h2 {
  font-size:1.5em;       
}

図 3 は、図 2 の <div id="addEventHTMLControl"> 要素を Windows の WinJS.UI.HtmlControl に変換します。これは、イベントを追加するコードを addEvent.html ファイルに格納できることを意味します。

図 3 OS を特定して UI を構築する、Home.js の ready 関数の一部

var listView = document.querySelector("#listView").winControl;
var packageName = Windows.ApplicationModel.Package.current.id.name;
if (packageName === "Countdown.WindowsPhone") {
  document.querySelector("#privacyButton").addEventListener("click",
    this.navigateToPrivacyPage);
  document.querySelector("#addButton").addEventListener("click",
    this.navigateToaddEventPage);
  listView.layout = new WinJS.UI.ListLayout();
}
else if (packageName === "Countdown.Windows") {
  document.querySelector("#addButton").addEventListener("click",
    this.showAddEventFlyout);
  var htmlControl =
    new WinJS.UI.HtmlControl(document.querySelector("#addEventHTMLControl"),
    { uri: '/pages/addEvent/addEvent.html' });
  listView.layout = new WinJS.UI.GridLayout();
}

共有の home.js 内には、条件コードを記述する必要がある箇所がいくつかあります。条件コードにより、使用しているプラットフォームを判断し、適宜処理します。パッケージ名をチェックして、実行しているプラットフォームを判断します。パッケージ名は、package.appmanifest の [パッケージ化] タブにあります。既定では、これは GUID です。これを、コード内で使用するのに分かりやすい名前に変更します。変更したら、実行時に、その名前を照会して現在のプラットフォームを決定します。図 3 は、結び付けるイベントと使用する UI コンポーネントを判断する際にこのコードがどのようになるかのサンプルを示しています。

図 3 では、アプリ バーのボタンを、アプリの各種類固有の機能に結び付けています。イベントの追加とプライバシーの設定はどちらも、ポップアップ表示にもページ全体表示にもなります。これは Windows では簡単です。privacy ページは、設定チャームから起動されるアプリの設定の一部になります。チャームを設定するコードは、通常、activated イベントの default.js に置きます。Windows Phone にはチャームの概念がないため、図 3 に示すように、privacy ページに移動する手段としてアプリ バーを使用しています。

イベントを追加する最善の方法は、Windows のポップアップを使用することです。その後、さまざまなフォーム ファクターにより、Windows Phone で完全な HTML ページを作成します。携帯電話サイズのデバイスでは、ポップアップやダイアログは適切に機能しません。addEvent ポップアップまたはページは、ユーザーが新しいイベントの詳細を入力する場所です。addEvent ポップアップの HTML も図 3 に含まれています。

また、図 3 のコードでは、検出したプラットフォームに応じて、ListView のレイアウトをグリッド レイアウトとリスト レイアウトの間で切り替えています。必要なのは 図 2 の HTML ブロック 1 つだけなので、ListLayout プロパティを使用するのが手軽です。この HTML ブロックと、図 3 の共有コードを組み合わせて、レイアウトを切り替える処理を行います。

js フォルダーには、ナビゲーション テンプレートに基づいた任意のソリューションと同様に、data.js ファイルと navigator.js ファイルがあります。どちらのプラットフォームのアプリにも同じデータとナビゲーション手法が必要になるため、これらのファイルは共有プロジェクト内に保持します。以下のコードは、WinJS 開発者にはなじみのある、Data 名前空間の定義を示します。

var list = new WinJS.Binding.List();
WinJS.Namespace.define("Data", {
  loadEventData: loadEventData,
  saveEventItem: saveEventItem,
  deleteEventItem: deleteEventItem,
  items: list,     
});

どちらのプラットフォーム プロジェクトでも、WinJS.Binding.List オブジェクトに配置された JSON データによって、まったく同じ方法でデータを使用します。唯一の違いは、データのスタイルを設定する方法です。

ユニバーサル アプリを開発する際は、単体テストを作成し、共有コードベースに変更を加えます。つまり、ある時点で、プロジェクトを実行して結果を検証することが必要です。デバッグ時に、デバッグするプロジェクトを選択し、ソリューション エクスプローラーでそのプロジェクトを右クリックして、[スタートアップ プロジェクトに設定] を選択します。

これにより、プロジェクトがソリューション エクスプローラー内に太字で表示されるようになります。[実行] をクリックするか、F5 キーを押すか、またはプロジェクトを起動すると、スタートアップ プロジェクトが実行されます。必要に応じてプロジェクトを切り替えて、起動するプロジェクトに指定することができます。

共有プロジェクトをスタートアップ プロジェクトにすることはできません。特定のプロジェクトを実行すると、Visual Studio はそのプロジェクトのアセットと共有プロジェクトのアセットをパッケージ化し、配置して、読み込みます。その後、準備が整うと、対応するエミュレーターが起動されます (まだ起動されていない場合)。Windows ストア アプリのデバッグの詳細については、私のブログ (bit.ly/1hTpxHB、英語) を参照してください。

ネイティブ UI の作成

UI が共有するコードの量は最小限に抑えます。さまざまなフォーム ファクターによって、データの量と表示の両方が決まります。データは、HTML を使用して表示し、CSS を使用して見栄えを整えます。プラットフォームごとにデバイスの対応範囲を調べる CSS メディア クエリなど、異なる HTML や CSS の処理は個別のプロジェクトに分離すると作業が容易になります。

Countdown アプリの場合、Windows ではグリッド、携帯電話ではリスト (縦並び) を表示します。そのためには、home.html ファイルを共有プロジェクトに配置し、home.css ファイルを個々のプロジェクトに配置します。addEvent ページと privacy ページは、対応するプラットフォーム プロジェクトに追加します。このフォルダー構造を確認するには、もう一度図 1 を見てください。

Countdown.Windows アプリでは、ユーザーは、[appBar の追加] ボタンをタップまたはクリックしてイベントを追加します。これにより、ポップアップ コントロールを使用してダイアログがポップアップ表示されます。Countdown.WindowsPhone では、addEvent ページ全体を表示します。以下、Countdown.Windows でポップアップを設計するコードを示します。

<div id="eventFlyoutPanel" data-win-control="WinJS.UI.Flyout">
  <table width="100%" height="100%">
    <tr><td>Date:</td><td><span id="eventDate"
      data-win-control="WinJS.UI.DatePicker"></span></td></tr>
    <tr><td>Event:</td><td><input type="text" id="eventTitle" /></td></tr>
    <tr><td>&nbsp;</td><td><input type="button" id="confirmButton"
      value="Submit" /></td></tr>
  </table>
</div>

これは、DatePicker と TextBox を含む小さな表です。Windows Phone では、ポップアップも DatePicker も動作しません。このポップアップは、home.html で WinJS.UI.HtmlControl として構成しています。図 4 は、このコードで作成される画面の実行時の表示形式です。

Countdown アプリ ポップアップの実行時の表示
図 4 Countdown アプリ ポップアップの実行時の表示

Countdown.WindowsPhone プロジェクトはまったく異なります。ポップアップではなく、addEvent ページに移動する必要があります。DatePicker がなくても問題ありません。3 つの <select> 要素を作成するというモバイルの実証済みの手法によってイベントの日付を収集できます。これらの要素はそれぞれ、日付の各部分 (日/月/年) に対応します。図 5 は、Countdown.Windows addEvent HTML コードのサンプルを含みます。また、図 6 は、このサンプルが Windows Phone で実行時にどのように表示されるかを示します。

図 5 Countdown.Windows addEvent HTML コードのサンプル

<h1 id="title" class="title">
  Add event
  </h1> 
  <div>
  Month
    <select id="eventMonth">
    <option value="--Select--">--Select--</option>
    <option value="01">January</option>
    <option value="02">February</option>
    <option value="03">March</option>
    <option value="04">April</option>
    <option value="05">May</option>
    <!-- options 6-11, cut for brevity -->
    <option value="12">December</option>
  </select>
  Day
  <select name="day" id="day">
    <option value="--Select--">--Select--</option>
    <option value="01">01</option>
    <option value="02">02</option>
    <option value="03">03</option>
    <option value="04">04</option>
    <option value="05">05</option>
    <!-- options 6-30, cut for brevity -->
    <option value="31">31</option>
  </select>
  Year<select id="eventYear">
    <option value="--Select--">--Select--</option>
    <!-- all other options created via client script -->
    </select>
  </div>
  <div>Event<input type="text" id="eventTitle" /></div>
  <div>
    <input type="button" id="backButton" value="Back" />
    <input type="button" id="confirmButton" value="Submit" />
  </div>
<script type="text/javascript">
  (function () {
    var yearSelect = document.querySelector("#eventYear");
    for (var i = 2014; i < 2075; i++) {
      yearSelect.options[i] = new Option(i, i);
    }
  })();
</script>

使用するコントロールが OS 間で利用できることを確認する必要があります。ハブ、ポップアップ、DatePicker、および TimePicker コントロールは Windows Phone では使用できません。ピボット コントロールは Windows では使用できません。さいわいなことに、一般的なハブ コントロールやピボット コントロールを使用している場合は、通常、置き換えが可能です。そのため、Windows ではハブ、Windows Phone ではピボットを使用すると、同じ処理を行うことができます。単純なドロップダウン コントロールは日付の取得に便利です。図 6 は、これが Windows Phone でどのように表示されるかを示します。

Windows Phone で生成された Countdown ページと Add event ページ
図 6 Windows Phone で生成された Countdown ページと Add event ページ

コードを調べ、OS 固有のタスクを対象とするコードを分離します。このような種類のタスクが増えた場合は、タスクを独自のプロジェクトに分離することを検討します。ブロックがごく少数しかない場合は、共有プロジェクトに残しておいて問題ありません。

コードを共有するその他の方法

コードを整理、管理、および共有する方法として、モデル - ビュー - ビューモデル (MVVM: Model-View-ViewModel) やモデル - ビュー - コントローラー (MVC: Model-View-Controller) などのアーキテクチャ パターンを適用できます。Countdown アプリの場合、共有プロジェクトでコードを共有するだけでうまく機能します。

Build カンファレンスにおけるもう 1 つの魅力的なお知らせとして、Windows ランタイムでは任意のサードパーティ製の JavaScript フレームワークがサポートされるようになったというのがありました。つまり、Knockout、Angular、Breeze.js などの有名なパッケージを使用して、これらのパターンを実装できます。

共有プロジェクト内のモデルとビューモデルはほとんど変更を加えることなく、他のプロジェクトでてシームレスに動作します。そのため、複製またはカスタマイズが必要になる可能性のあるコードだけがビューに残ります。場合によってはこれは回避できないことがあります。考慮すべきデバイス サイズやフォーム ファクターは多種多様です。また、タッチ、キーボード、マウス、音声など、入力デバイスにも幅広い種類があります。このようなさまざまな種類に対処するコードは、多くの場合、分離する必要があります。

JavaScript アプリでは、プロセス ライフサイクル管理コードを、個別のプロジェクトの default.js に配置できます。また、そこには、モデル、ビューモデル、および任意のアプリ ロジックを格納することもできます。OS は、プロトコルのアクティブ化を異なる方法で処理します。ただし、ほとんどのプロジェクトには、共有プロジェクトのスクリプトに追加すべき汎用のユーティリティ コードがあります。

プロジェクト間でコードを共有する方法は他にもいくつかあります。

  • 条件付きコンパイル: コード ブロックをセグメント化し、OS ごとに個別にコンパイルします。これは、C#/Visual Basic および C++ に当てはまります。WinJS では、通常の if ステートメントを使用します。詳細については、https://msdn.microsoft.com/ja-jp/library/ie/121hztk3%28v=vs.94%29.aspx を参照してください。
  • ランタイム コンポーネント: Windows ランタイムを使ってビルドしたコンポーネントは、Windows アプリと Windows Phone アプリのどちらにも公開でき、コードを共有できるようにします。
  • リンクの追加: 別のプロジェクト内のファイルへのポインターを、現在のプロジェクトに含まれているかのように処理できます。
  • ポータブル クラス ライブラリ (PCL): これは、Microsoft .NET Framework がコードを共有する方法です。Windows ランタイム (WinRT) ではこのようなレガシ コンポーネントがサポートされるため、これらのコンポーネントをアプリで使用できます。既存の PCL が既にある場合は、使用することをお勧めします。新しいコンポーネントを作成する場合は、WinRT コンポーネントを追加することをお勧めします。

共有プロジェクトがコードを共有する唯一の方法でないことは明らかです。既に存在する .NET コードを再利用できます。たとえば、C++ の支援が必要なライブ アクション ゲームを作成している場合はおそらく多くのパフォーマンス要件があります。このような場合は、WinRT コンポーネントの使用がコード共有の優れた方法になります。

可能な限り、フラグメントを使用します。フラグメントとは、単純に、両方のプロジェクトで使用できる HTML のスニペットです。アプリ バーやポップアップなどの UI コンポーネントは、フォーム ファクターが変わるとうまく機能しなくなるため、それぞれのプロジェクトで保持することになります。汎用のアプリ設定などを設定するコードは、共有プロジェクトに含めます。

まとめ

ユニバーサル アプリは、「一度記述すれば、魔法のようにあらゆる場所で動作する」というわけではありませんが、あと一歩まで近づいています。いくつか API の変更点はありますが、アプリで単一の Windows ランタイムを使用すると、Windows エコシステム全体で動作するアプリに取り組むことが非常に簡単になります。Xamarin が提供する Visual Studio 拡張機能により、マイクロソフト以外のプラットフォームでアプリを動作させることもできます。また、Build で約束したとおり、今後は、Xbox One の実績、チャレンジ、OneGuide などもサポートします。マイクロソフトのアプリ開発者にとってわくわくすることが起きています。


Rachel Appelは 20 年を超える IT 業界での経験を持つマイクロソフトの元社員で、コンサルティング、執筆活動、および指導を行っています。彼女は Visual Studio Live!、DevConnections、MIX など、業界トップ クラスのカンファレンスで講演しています。専門分野は、マイクロソフトの各種開発ツールやオープン Web を重視したテクノロジとビジネスを連携させるソリューションの開発です。彼女のことをもっと知りたい方は、彼女の Web サイト rachelappel.com (英語) をご覧ください。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Frank La Vigne に心より感謝いたします。