XDev 2012 で講演しました 「ライブコーディングで学ぶ、Windows 8の機能を活かしたWindows ストア アプリの開発」
2012/11/7(水) 16:40過ぎからタイトルのセッションを行いました。フォローアップとして、記事を残しておきます。
セッション資料は こちら。ライブコーディングで作成したアプリと同様のサンプルを添付ファイルに掲載しておきます。
※ 違いは、HTML/JavaScript側は ms-gridの設定をきちんとしたバージョン。XAML/C#側は、検索の機能を加えています。
関連資料として、 https://bit.ly/w8devstart も合わせてご覧ください。
内容としては、Windows 8 の新しいプラットフォームを利用した Windows ストア アプリの開発を、ライブコーディングを通じて紹介するものでした。
セッション時間が短かったため、細かく解説するよりも、ツールでどんどんアプリが出来ていく過程を見ていただく、という形式でお話しました。
イントロ
タッチファーストのOSとして生まれ変わったWindows 8を簡単にご紹介し、従来通り、マウスやキーボードでも操作できる話として、ショートカットキーの話も交えました。
[Windows]+[q] : 検索チャーム
[Windows]+[i] : 設定チャーム
[Windows}+[c] : チャーム
[Windows]+[x] : 管理系などのメニュー表示
[Windows]+[b] または [d] : デスクトップへの切り替え (一度操作した後は、[Windows]キーで切り替え)
Windows ストア アプリの開発環境にはWindows 8とVisual Studio 2012が必要になります。
お名前.com デスクトップクラウド for Windows アプリもご紹介しました。
HTML + JavaScript による Windows ストア アプリのライブコーディング
取り上げた内容:
・Blend for Visual Studioによる画面作成とVisual Studio 2012によるJavaScriptコーディング
・要素IDからスタイルルールの作成
・カラーピッカーの「色スポイト」による色の抽出
・CSS Transitionを利用して画像からボタンを作成
・Webカメラからのビデオキャプチャー (ブラウザーのJavaScriptではないので、Windowsランタイムからネイティブの機能を利用できます)
・キャプチャーしたビデオの再生
・リモート再生 (PlayTo)による、キャプチャーしたビデオを別デバイスで再生 (HTML <video> <audio>は自動的にPlayToに対応します)
HTMLの内容: スタイルは CSSに
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta charset="utf-8" />
<title>App18</title>
<!-- WinJS 参照 -->
<link href="https://blogs.msdn.com//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
<script src="https://blogs.msdn.com//Microsoft.WinJS.1.0/js/base.js"></script>
<script src="https://blogs.msdn.com//Microsoft.WinJS.1.0/js/ui.js"></script>
<!-- App18 参照 -->
<link href="https://blogs.msdn.com/css/default.css" rel="stylesheet" />
<script src="https://blogs.msdn.com/js/default.js"></script>
<link href="https://blogs.msdn.com/css/Microsoft.VisualStudio.Blend.Html.Intrinsic.Grid.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="ms-grid">
<div id="myTitle">XDevデモ
</div>
<input id="txtInput" type="text">
<button id="btnHello" type="button">Hello</button>
<div id="result"></div>
<img id="btnCamera" alt="xdevlogo" src="https://blogs.msdn.com/images/xdevlogo.jpg">
<video id="myVideo" controls></video>
</div>
</body>
</html>
CSSの内容
Microsoft.VisualStudio.Blend.Html.Intrinsic.Grid.css:
-ms-gridを使って、グリッドレイアウトを設定しています。
.ms-grid
{
display: -ms-grid;
-ms-grid-rows: 100px 30px 92fr 68fr 160fr 319fr;
-ms-grid-columns: 120px 519fr 189fr 538fr;
width: auto;
height: inherit;
}
default.css:
セッション中は、スナップ画面の作成について触れていませんが、メディアクエリーを使えば、いろんな画面モードに対応できます。
コツとしては、いったんスタイルを作成し、それを @media screen で始まる画面モードに対応したスタイルとしてコピーすることです。
その後、デバイスパネルで画面モードを変更して、調整を加えましょう。
body {
}
@media screen and (-ms-view-state: fullscreen-landscape) {
#txtInput {
font-size: 42pt;
width: 496.9px;
height: 79.12px;
-ms-grid-row: 3;
-ms-grid-column: 2;
}
#btnHello {
font-size: 42pt;
color: #742894;
-ms-grid-column: 3;
-ms-grid-row: 3;
}
#result {
font-size: 20pt;
-ms-grid-row: 4;
-ms-grid-column: 2;
}
#myTitle {
font-size: 42pt;
-ms-grid-column: 2;
}
#btnCamera:active {
transform: scale(0.8, 0.8);
}
#btnCamera {
width: 242.39px;
height: 163.13px;
transform: scale(1, 1);
transition-property: transform;
transition-duration: 0.16s;
transition-timing-function: ease-in;
-ms-grid-column: 3;
-ms-grid-row: 5;
}
#myVideo {
width: 640px;
height: 470px;
-ms-grid-column: 2;
-ms-grid-row: 5;
-ms-grid-column-span: 2;
-ms-grid-row-span: 2;
margin: 0px 68px 8.25px 0px;
}
}
@media screen and (-ms-view-state: filled) {
}
@media screen and (-ms-view-state: snapped) {
#txtInput {
font-size: 42pt;
width: auto;
height: 79.12px;
-ms-grid-row: 3;
-ms-grid-column: 1;
-ms-grid-column-span: 3;
margin: 4px 5.41px 4.74px 0px;
}
#btnHello {
font-size: 12pt;
color: #742894;
-ms-grid-column: 4;
-ms-grid-row: 3;
margin: 0px 75.36px 0px 0px;
}
#result {
font-size: 20pt;
-ms-grid-column: 1;
-ms-grid-column-span: 4;
-ms-grid-row: 4;
}
#myTitle {
font-size: 42pt;
-ms-grid-column: 1;
-ms-grid-column-span: 4;
}
#btnCamera:active {
transform: scale(0.8, 0.8);
}
#btnCamera {
width: auto;
height: 163.13px;
transform: scale(1, 1);
transition-property: transform;
transition-duration: 0.16s;
transition-timing-function: ease-in;
-ms-grid-column: 3;
-ms-grid-row: 5;
-ms-grid-column-span: 2;
}
#myVideo {
width: 198px;
height: 156px;
-ms-grid-column: 1;
-ms-grid-row: 5;
-ms-grid-column-span: 2;
-ms-grid-row-span: 1;
margin: 0px 5.31px 3.75px 0px;
}
}
@media screen and (-ms-view-state: fullscreen-portrait) {
}
JavaScriptの内容
// 空白のテンプレートの概要については、次のドキュメントを参照してください:
// https://go.microsoft.com/fwlink/?LinkId=232509
(function () {
"use strict";
WinJS.Binding.optimizeBindingReferences = true;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
function onHello() {
// 入力された内容のエコーバック
result.innerHTML = txtInput.value;
};
function onTick() {
// 現在の日付と時刻の取得
var d = new Date();
result.innerHTML = d.toLocaleString();
};
function onCamera() {
// Webカメラとマイクにアクセスして、ビデオをキャプチャー
var c = new Windows.Media.Capture.CameraCaptureUI();
c.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.video).then(
function (f) {
if (f) {
var u = URL.createObjectURL(f);
myVideo.src = u;
}
}
);
}
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
// TODO: このアプリケーションは新しく起動しました。ここでアプリケーションを
// 初期化します。
} else {
// TODO: このアプリケーションは中断状態から再度アクティブ化されました。
// ここでアプリケーションの状態を復元します。
}
btnHello.addEventListener("click", onHello);
setInterval(onTick, 1000);
btnCamera.addEventListener("click", onCamera);
args.setPromise(WinJS.UI.processAll());
}
};
app.oncheckpoint = function (args) {
// TODO: このアプリケーションは中断しようとしています。ここで中断中に
// 維持する必要のある状態を保存します。中断中に自動的に保存され、
// 復元される WinJS.Application.sessionState オブジェクトを使用
// できます。アプリケーションを中断する前に非同期操作を完了する
// 必要がある場合は、args.setPromise() を呼び出して
// ください。
};
app.start();
})();
-------------
XAML / C# による Windows 8固有の機能を使ったアプリ開発
すべてをタイプする時間がないので、コードスニペットをコピペする形式で行いました。
・グリッドアプリケーション
・RSSフィードの取得 (ITProのフィードを利用しました)
・アプリバーの作成
・共有ソースの設定
・共有ターゲットコントラクトの追加
・中断状態のデバッグ
・ライブタイルの作成
コードスニペット
RSSフィードの取得 (SampleDataSource.cs内へ)
private async void GetFeedAsync()
{
var imgPath = "https://itpro.nikkeibp.co.jp/images/itpro/2009/theme/develop/xdev/right/media/itpro.jpg";
var client = new SyndicationClient();
var uri = new Uri("https://itpro.nikkeibp.co.jp/rss/ITpro.rdf");
var feed = await client.RetrieveFeedAsync(uri);
var group = new SampleDataGroup("itpro", "ITPro総合", "", imgPath, "");
foreach (var item in feed.Items)
{
var i = new SampleDataItem(item.Id, item.Title.Text, item.Summary.Text, imgPath, "", item.NodeValue, group);
group.Items.Add(i);
}
this.AllGroups.Add(group);
}
アプリバーのボタンからブラウザー起動 (GroupedItemsPage.xaml.cs内へ)
private async void btnBrowser_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
var item = this.itemListView.SelectedItem as SampleDataItem;
var u = new Uri(item.UniqueId);
await Launcher.LaunchUriAsync(u);
}
共有ソース (GroupedItemsPage.xaml.cs内へ)
LoadStateメソッドへ追加
var dtm = DataTransferManager.GetForCurrentView();
dtm.DataRequested += dtm_DataRequested;
共有時のイベントハンドラー
void dtm_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
DataRequest request = args.Request;
SampleDataItem selectedItem = this.itemListView.SelectedItem as SampleDataItem;
if (selectedItem == null)
{
return;
}
request.Data.Properties.Title = selectedItem.Title;
request.Data.Properties.Description = selectedItem.Subtitle;
if (selectedItem.UniqueId != null)
{
request.Data.SetUri(new Uri(selectedItem.UniqueId));
}
}
SaveStateメソッドを作成して追加
var dtm = DataTransferManager.GetForCurrentView();
dtm.DataRequested -= dtm_DataRequested;
(セッションでは省略しましたが・・・)検索コントラクトのコード ( 検索コントラクトを追加して SearchResultsPage1.xaml.cs へ)
クラスメンバー
private Dictionary<string, List<SampleDataItem>> _results = new Dictionary<string, List<SampleDataItem>>();
LoadState
var groups = SampleDataSource.GetGroups("AllGroups");
var all = new List<SampleDataItem>();
string query = queryText.ToLower();
_results.Add("All", all);
foreach (var group in groups)
{
var items = new List<SampleDataItem>();
var title = group.Title;
_results.Add(title, items);
foreach (var item in group.Items)
{
if (item.Title.ToLower().Contains(query)) {
all.Add(item);
items.Add(item);
}
}
if (items.Count>0)
{
filterList.Add(new Filter(group.Title, items.Count, false));
}
}
filterList[0].Count = all.Count;
Filter_SelectionChangedメソッド内へ
// TODO: this.DefaultViewModel["Results"] をバインド可能な Image、Title、および Subtitle の
// バインドできる Image、Title、Subtitle、および Description プロパティを持つアイテムのコレクションに設定します
this.DefaultViewModel["Results"] = _results[selectedFilter.Name];
検索結果を選択した時の処理 (SearchResultsPage1.xamlのresultGridViewにItemClickイベントハンドラーを追加)
private void resultsGridView_ItemClick(object sender, ItemClickEventArgs e)
{
Uri u = new Uri(((SampleDataItem)e.ClickedItem).UniqueId);
Launcher.LaunchUriAsync(u);
}
ライブタイルの設定 (SampleDataSource.csへ追加)
※ どこかから SampleDataSource.UpdateLiveTiles()を呼び出すと、通知されます。例: GroupedItemsPage.xaml.cs の LoadStateなど
// タイルを更新する
public static void UpdateLiveTiles()
{
TileUpdateManager.CreateTileUpdaterForApplication().EnableNotificationQueue(true);
SampleDataGroup g1 = SampleDataSource.GetGroup("itpro");
if (g1 != null)
{
if (g1.Items.Count > 0)
{
for (int i = 0; i < 5 ; i++)
{
if (i >= g1.Items.Count) break;
SampleDataItem item = g1.Items[i];
XmlDocument tileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileWideText09);
tileXml.GetElementsByTagName("text").Item(0).InnerText = item.Title;
tileXml.GetElementsByTagName("text").Item(1).InnerText = item.Subtitle;
TileNotification tileNotification = new TileNotification(tileXml);
tileNotification.Tag = g1.UniqueId+i.ToString();
tileNotification.ExpirationTime = DateTimeOffset.Now.AddHours(6);
TileUpdateManager.CreateTileUpdaterForApplication().Update(tileNotification);
}
}
}
}
まとまった動きは添付ファイルからご覧ください。
最後に流したCMを見たい方向けに
ご参加いただいた皆様、ありがとうございます。
・・・
番外編: セッションの裏側を少しだけ。
プレゼンテーションの準備に用意したシナリオです。想定通りの時間に収まらないことを踏まえて、直前まであれこれ試行錯誤します。