Quantcast
Channel: 荒井省三のBlog
Viewing all 163 articles
Browse latest View live

Windowsストアアプリにおける グリッドアプリケーションについて(5)

$
0
0

前回まででGroupedItemPageの説明が終了したので、今回はGroupDetailPageの説明を行います。最初に、GroupedItemsPage.HeaderClickで説明したナビゲーションで呼び出されるLoadStateメソッドを見てみましょう。

protected override void LoadState(Object navigationParameter, 
                       Dictionary<String, Object> pageState)
{
    // TODO: 問題のドメインでサンプル データを置き換えるのに適したデータ モデルを作成します
    var group = SampleDataSource.GetGroup((String)navigationParameter);
    this.DefaultViewModel["Group"] = group;
    this.DefaultViewModel["Items"] = group.Items;
}

       
navigationParameterに、SampleDataGroup.UniqueIdが格納されていて、SampleDataSourceの静的メソッドであるGetGroupメソッドを呼び出しています。このメソッドにより、目的のSampleDataGroupのインスタンスを取得して、DefaultViewModelのGroupキーとItemsキーを設定しています。今までの説明を読んできた人には理解できると思いますが、GroupキーとItemsキーの設定を確認するために、GroupDetailPage.xamlのCollectionViewSourceとGridViewを見ていきます。最初に、CollectionViewSourceを以下に示します。

<CollectionViewSource
    x:Name="itemsViewSource"
    Source="{Binding Items}"
    d:Source=
      "{Binding AllGroups[0].Items, 
                Source={d:DesignInstance 
                Type=data:SampleDataSource, 
                IsDesignTimeCreatable=True}}"/>


Source属性に記述されている「Items」がDefaultViewModelのキーであり、d:Source属性に記述されているSampleDataSource.AllGroups[0]がデザイン時のデータソースになります。デザイン時は、AllGroupsプロパティが返すコレクションの最初の要素をバインドしています。次に、GridViewとListViewの定義を以下に示します。

<Grid
    Style='{StaticResource LayoutRootStyle}'
    DataContext='{Binding Group}'
    d:DataContext='{Binding AllGroups[0], 
                    Source={d:DesignInstance 
                    Type=data:SampleDataSource, 
                    IsDesignTimeCreatable=True}}'><Grid.RowDefinitions><RowDefinition Height='140'/><RowDefinition Height='*'/></Grid.RowDefinitions><!-- ほとんどのビューステートで使用される水平スクロール グリッド--><GridView
        x:Name='itemGridView'
        AutomationProperties.AutomationId='ItemGridView'
        AutomationProperties.Name='Items In Group'
        TabIndex='1'
        Grid.RowSpan='2'
        Padding='120,126,120,50'
        ItemsSource='{Binding Source={StaticResource itemsViewSource}}'
        ItemTemplate='{StaticResource Standard500x130ItemTemplate}'
        SelectionMode='None'
        IsSwipeEnabled='false'
        IsItemClickEnabled='True'
        ItemClick='ItemView_ItemClick'><GridView.Header><StackPanel Width='480' Margin='0,4,14,0'><TextBlock Text='{Binding Subtitle}' 
                    Margin='0,0,18,20' 
                    Style='{StaticResource SubheaderTextStyle}' MaxHeight='60'/><!-- 以下省略 --></StackPanel></GridView.Header><GridView.ItemContainerStyle><Style TargetType='FrameworkElement'><Setter Property='Margin' Value='52,0,0,10'/></Style></GridView.ItemContainerStyle></GridView><!-- スナップの場合のみ使用される垂直スクロール リスト --><ListView
        x:Name='itemListView'
        AutomationProperties.AutomationId='ItemListView'
        AutomationProperties.Name='Items In Group'
        TabIndex='1'
        Grid.Row='1'
        Visibility='Collapsed'
        Padding='10,0,0,60'
        ItemsSource='{Binding Source={StaticResource itemsViewSource}}'
        ItemTemplate='{StaticResource Standard80ItemTemplate}'
        SelectionMode='None'
        IsSwipeEnabled='false'
        IsItemClickEnabled='True'
        ItemClick='ItemView_ItemClick'><ListView.Header><StackPanel><TextBlock Text='{Binding Subtitle}' 
                   Margin='10,0,18,20' 
                   Style='{StaticResource TitleTextStyle}' MaxHeight='60'/><!-- 以下省略 --></StackPanel></ListView.Header></ListView>


GridのDataContext属性の指定している「Group」がDefaultViewModelのキーであり、GridViewやListViewのHeaderにバインドされるデータソースになっています。また、Gridのd:DataContext属性にCollectionViewSourceと同じ値を設定していることにも注意してください。これもデザイン用のデータソースの指定です。
GridViewのItemsSource属性に「{Binding Source={StaticResource itemsViewSource}}」が指定されており、CollectionViewSourceがデータソースとしてバインディングされていることが解ります。ListViewもGridViewと同じ値がItemSourceに指定されていることも解ることでしょう。
GridViewのItemTemplate属性に「{StaticResource Standard500x130ItemTemplate}」が指定されており、ListViewのItemTemplateには「{StaticResource Standard80ItemTemplate}」が指定されています。ItemTemplateは、StandardStyle.xamlで定義されているDataTemplateとなり、カスタマイズする場合はStandardStyle.xamlからApp.xamlへコピーして編集を行うことで、独自のスタイルだったり、追加したプロパティなどを表示する項目を作成したりすることができます。

GroupDetailPageもGroupedItemPageと同じで、フル表示をGridViewで行い、スナップ表示をListViewで行っています。これが、ListviewのVisibility属性が「Collapsed」と指定されている意味であり、VisualStateManagerの定義もGroupedItemPageと同じパターンで定義されています。

GroupDetailPageのアイテムをクリックすると、ItemDetailPageへナビゲーションしますので、このイベントハンドラーを以下に示します。

void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
    // 適切な移動先のページに移動し、新しいページを構成します。
    // このとき、必要な情報をナビゲーション パラメーターとして渡します
    var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;
    this.Frame.Navigate(typeof(ItemDetailPage), itemId);
}

このコードは、GroupedItemPageで示したイベントハンドラーと同じであり、クリックされたClickedItemがSampleDataItemであり、そのUniqueIdをパラメータとしてItemDetailPageへナビゲーションしています。

今度は、トップページであるGroupedDetailPageへ戻るイベントハンドラーを確認してみましょう。このイベントハンドラーは、LayoutAwarePage.GoBackと定義されています。

/// <summary>
/// イベント ハンドラーとして呼び出され、このページの <see cref="Frame"/> に関連付けられた
/// ナビゲーション スタックで前に戻ります。
/// </summary>
/// <param name="sender">イベントをトリガーしたインスタンス。</param>
/// <param name="e">イベントが発生する条件を説明する
/// イベント データ。</param>
protected virtual void GoBack(object sender, RoutedEventArgs e)
{
    // ナビゲーション フレームを使用して前のページに戻ります
    if (this.Frame != null
        && this.Frame.CanGoBack) this.Frame.GoBack();
}

  
コードを見れば理解できると思いますが、Frameオブジェクトのナビゲーションメカニズムを使って直前のページに戻しているだけです。このことから理解できることは、独自に戻るボタンのイベントハンドラーをオーバーライドする場合は、最終的に基底クラスのGoBackを呼び出すようにすることで、GoBackの動作に変更があっても修正する箇所が基底クラスの一か所のみで良いということです。 次回は、ItemDetailPageの説明に入る予定です。


Windowsストアアプリにおける グリッドアプリケーションについて(6)

$
0
0

前回にGroupDetailPageの説明を行いました。今回は、最後のItemDetailPageの説明を行います。最初に、ItemDetailPageがナビゲーションを使って呼び出されることから、LoadStateメソッドを以下に示します。

protected override void LoadState(Object navigationParameter, 
                          Dictionary<String, Object> pageState)
{
    // 保存されたページの状態で、表示する最初のアイテムをオーバーライドすることを許可します
    if (pageState != null && pageState.ContainsKey("SelectedItem"))
    {
        navigationParameter = pageState["SelectedItem"];
    }

    // TODO: 問題のドメインでサンプル データを置き換えるのに適したデータ モデルを作成します
    var item = SampleDataSource.GetItem((String)navigationParameter);
    this.DefaultViewModel["Group"] = item.Group;
    this.DefaultViewModel["Items"] = item.Group.Items;
    this.flipView.SelectedItem = item;
}


navigationParameterに、SampleDataItem.UniqueIdが格納されていて、SampleDataSourceの静的メソッドであるGetItemメソッドを呼び出しています。このメソッドにより、目的のSampleDataItemのインスタンスを取得して、DefaultViewModelのGroupキーとItemsキー、FilvepViewのSelectedItemを設定しています。CollectionViewSourceの定義は、GroupDetailPage.xamlと同一になっており、レイアウトのルートとなるGridに対するDataContextやd:DataContextもGroupDetailPage.xamlと同-です。 異なるのは、詳細情報をFlipViewコントロールで行っていることです。
FlipView

FlipViewコントロールは、コレクションの要素を右端と左端の矢印によってナビゲーションすることができます。従って、FlipViewコントロールを使うのはコレクションの要素を列挙するような用途に向いているということになります。それでは、ItemDetailPage.xamlのFlipViewの定義を以下に示します。

<!--このページの残りは 1 つの大きな FlipView です。ここには、一度に
     1 つのアイテムの詳細が表示され、ユーザーは選択されたグループ内のすべてのアイテムを見ることが
    できます
--><FlipView
    x:Name="flipView"
    AutomationProperties.AutomationId="ItemsFlipView"
    AutomationProperties.Name="Item Details"
    TabIndex="1"
    Grid.RowSpan="2"
    ItemsSource="{Binding 
           Source={StaticResource itemsViewSource}}"><FlipView.ItemContainerStyle><Style TargetType="FlipViewItem"><Setter Property="Margin" Value="0,137,0,0"/></Style></FlipView.ItemContainerStyle><FlipView.ItemTemplate><DataTemplate><!--表示状態管理をサポートしているため、テンプレート化されたアイテムとして選択された UserControl
                読み込まれた/アンロードされたイベントが、ページからのビューステートの更新を明示的に定期受信します
            --><UserControl 
                  Loaded="StartLayoutUpdates" 
                  Unloaded="StopLayoutUpdates"><ScrollViewer x:Name="scrollViewer" 
                     Style='{StaticResource HorizontalScrollViewerStyle}'
                     Grid.Row='1'><!-- コンテンツは、必要な数の列をフローできます --><common:RichTextColumns x:Name='richTextColumns'
                            Margin='117,0,117,47'><RichTextBlock x:Name='richTextBlock'
                             Width='560' 
                             Style='{StaticResource ItemRichTextStyle}' 
                             IsTextSelectionEnabled='False'><Paragraph><Run 
                                   FontSize='26.667' 
                                   FontWeight='Light' 
                                   Text='{Binding Title}'/><LineBreak/><LineBreak/><Run 
                                   FontWeight='Normal' 
                                   Text='{Binding Subtitle}'/></Paragraph><Paragraph LineStackingStrategy='MaxHeight'><InlineUIContainer><Image x:Name='image' 
                                        MaxHeight='480' Margin='0,20,0,10' 
                                        Stretch='Uniform' 
                                        Source='{Binding Image}' 
                                        AutomationProperties.Name='{Binding Title}'/></InlineUIContainer></Paragraph><!-- 以下省略 --></common:RichTextColumns><VisualStateManager.VisualStateGroups><!-- 表示状態には、FlipView 内のアプリケーションのビューステートが反映され��す --><VisualStateGroup x:Name='ApplicationViewStates'><VisualState x:Name='FullScreenLandscape'/><VisualState x:Name='Filled'/><!-- 縦方向に対して、より狭い 100 ピクセルの余白の規則を優先します --><VisualState x:Name='FullScreenPortrait'><Storyboard><!-- 以下省略 --></Storyboard></VisualState><!-- スナップの場合、コンテンツは再フォーマットされ、垂直にスクロールします --><VisualState x:Name='Snapped'><Storyboard><ObjectAnimationUsingKeyFrames 
                                      Storyboard.TargetName='richTextColumns' 
                                      Storyboard.TargetProperty='Margin'><DiscreteObjectKeyFrame KeyTime='0' 
                                                   Value='17,0,17,57'/></ObjectAnimationUsingKeyFrames><ObjectAnimationUsingKeyFrames 
                                        Storyboard.TargetName='scrollViewer' 
                                        Storyboard.TargetProperty='Style'><DiscreteObjectKeyFrame KeyTime='0' 
                                           Value='{StaticResource VerticalScrollViewerStyle}'/></ObjectAnimationUsingKeyFrames><!-- 以下省略 --></Storyboard></VisualState></VisualStateGroup></VisualStateManager.VisualStateGroups></ScrollViewer></UserControl></DataTemplate></FlipView.ItemTemplate></FlipView>

 
FlipViewのItemSource属性に、CollectionViewSourceである ItemViewSourceを指定しています(データバインディンの設定)。次に説明することは、FlipView.ItemTemplateの定義になります。

  • UserControl:ここでのポイントは、LoadedとUnloadedイベントにLayoutAwarePageのStartLayoutUpdates、StopLayoutUpdatesイベントハンドラーを指定していることです。既に説明したように、LayoutAwarePage.StartLayoutUpdatesイベントハンドラーはControlオブジェクトに対してVisualStateManagerを使ってビューの切り替えを行います。つまり、ItemTemplateに対してビューの切り替えを行いたいためにItemTemplateのルート要素としてUserControlを定義しているのです。
  • ScrollViewer:水平スクロールビューアー スタイルが指定されていることに注意してください。フルなどのビューにおいて、水平スクロールバーを表示してコンテンツをスクロールさせます。
  • common:RichTextColumns:これはカスタムコントロールで、Common\RichTextColumn.csで定義されています。イメージ的には、リッチテキストボックスに近いコントロールですが、異なるのは追加の列をColumnTemplateで生成できることでしょう。
  • VisualStateManager:UserControlに対するVisualStateManagerの定義で、ポートレイトとスナップ時の定義を行っています。細かな定義を読めば理解できますが、単純に説明すればMaginやWidth、Heightなどをビューによって設定しています。

  そしてItemDetailPageに対するVisualStateManagerの定義が行われています。この定義は、ポートレイトとスナップにおけるタイトルなどの設定で、これまでに説明してきたGroupedItemPage.xamlやGroupDetailPage.xamlと同じものとなります。

ItemDetailPage固有のロジックとしては、SaveSateメソッドがあります。

/// <summary>
/// アプリケーションが中断される場合、またはページがナビゲーション キャッシュから破棄される場合、
/// このページに関連付けられた状態を保存します。値は、
/// <see cref="SuspensionManager.SessionState"/> のシリアル化の要件に準拠する必要があります。
/// </summary>
/// <param name="pageState">シリアル化可能な状態で作成される空のディクショナリ。</param>
protected override void SaveState(Dictionary<String, Object> pageState)
{
    var selectedItem = (SampleDataItem)this.flipView.SelectedItem;
    pageState["SelectedItem"] = selectedItem.UniqueId;
}

     
SaveSateメソッドのコメントで、何を行うものかは理解できることでしょう。問題は、どのタイミングで呼び出されるのかという点になります。それでは、LayoutAwarePage.OnNavigatedFromメソッドを以下に示します。

/// <summary>
/// このページがフレームに表示されなくなるときに呼び出されます。
/// </summary>
/// <param name="e">このページにどのように到達したかを説明するイベント データ。Parameter 
/// プロパティは、表示するグループを示します。</param>
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
    var pageState = new Dictionary<String, Object>();
    this.SaveState(pageState);
    frameState[_pageKey] = pageState;
}


フレームが表示されなくなるタイミングでOnNavigateFromイベントハンドラーが呼びされます。このコードでSuspensionManagerのSuspensionStateforFrameメソッドが呼び出されていることに注意してください。簡単に説明するなら、SusupensionManagerを使ってこのページへナビゲートしてきた状態を保存するということです。この機能が定義されていて、App.xaml.csのOnSuspendingイベントハンドラーを見てみましょう。

private async void OnSuspending(object sender, SuspendingEventArgs e)
{
    var deferral = e.SuspendingOperation.GetDeferral();
    await SuspensionManager.SaveAsync();
    deferral.Complete();
}


OnSupendingイベントハンドラーで、「SuspensionManager.SaveAsync()」と記述して、Frameオブジェクトによる状態を保存しています。このメソッドで、pageState["SelectedItem"] に設定したナビゲーション パラメーターも一緒に保存されます。つまり、Windowsストアアプリにおける一時停止とFrameオブジェクトのイベントハンドラーは以下のような関係にあります。

  1. Frame.OnNavigateFromイベントハンドラーが発生する。
    Frameが表示されなくなる
  2. OnSuspendingイベントハンドラーが発生する。

このような動きをすることで、Windows ストアアプリケーション固有の動きである、「一時停止から強制終了」、次に起動したタイミングで「状態を復旧」(最後に表示していあたページへ復帰させる)を実現しています。また、SampleDataSourceの説明で行ったようにSampleDataGroupとSampleDataItemは循環参照を持っています。この循環参照持っていることで、LoadStateメソッドに記述されている「this.DefaultViewModel["Group"] = item.Group」が成り立つようになっています。
ここまで説明したなら、LayoutAwarePage.OnNavigatedToイベントハンドラーも解説すべきでしょう。

/// <summary>
/// このページがフレームに表示されるときに呼び出されます。
/// </summary>
/// <param name="e">このページにどのように到達したかを説明するイベント データ。Parameter 
/// プロパティは、表示するグループを示します。</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    // ナビゲーションを通じてキャッシュ ページに戻るときに、状態の読み込みが発生しないようにします
    if (this._pageKey != null) return;

    var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
    this._pageKey = "Page-" + this.Frame.BackStackDepth;

    if (e.NavigationMode == NavigationMode.New)
    {
        // 新しいページをナビゲーション スタックに追加するとき、次に進むナビゲーションの
        // 既存の状態をクリアします
        var nextPageKey = this._pageKey;
        int nextPageIndex = this.Frame.BackStackDepth;
        while (frameState.Remove(nextPageKey))
        {
            nextPageIndex++;
            nextPageKey = "Page-" + nextPageIndex;
        }

        // ナビゲーション パラメーターを新しいページに渡します
        this.LoadState(e.Parameter, null);
    }
    else
    {
        // ナビゲーション パラメーターおよび保存されたページの状態をページに渡します。
        // このとき、中断状態の読み込みや、キャッシュから破棄されたページの再作成と同じ対策を
        // 使用します
        this.LoadState(e.Parameter, 
            (Dictionary<String, Object>)frameState[this._pageKey]);
    }
}

OnNavigatedToイベントハンドラーでは、ナビゲーションに発生するナビゲーションパラメーターなどを設定したり、状態をクリアーしたりしています。このようにページの状態を保持する仕組みを用意していることで、GoBackイベントハンドラーで自由に戻るというナビゲーションを実現しています。

このようにグリッドアプリケーション テンプレートは、単純なテンプレートではありません。むしろ、Windows ストアアプリケーション開発が容易になるように作り込まれたサンプルであると説明した方が適切でしょう。 これが、グリッドアプリケーション テンプレートの解説を記述しようと考えたきっかけです。 このエントリーは、後1回位を予定しています。次回は、カスタマイズする場合のポイントを取り上げる予定です。

Windowsストアアプリにおける グリッドアプリケーションについて(7)

$
0
0

少し時間があきましたが、今回はグリッドアプリケーションを使った場合にどのようにカスタマイズするかということを説明します。最初に、開発体験テンプレートをご存じな方も多いことと思います。このテンプレート集のNewReaderのReadme.txtにカスタマイズのポイントが記載されているので、その中から抜粋したものを以下に示します。

  • (0) アプリの名前
  • (2) ブランディング
  • (3) プライバシーポリシー
  • (4) package.appxmanifest

最初にブランディングを説明します。ブランディングに関係する事項として、以下のようなことを考慮する必要があります。

  • バックグラウンド
    背景色や画像など
  • アイコン デザイン
    ロゴ(150x150)、ワイドロゴ(310x150)、小さいロゴ(30x30)、バッジロゴ(24x24)、スプラッシュスクリーン(620x300)、ストアロゴ(50x50)

特にブランディングは重要で、作成したアプリが容易に識別できるようにするためにも必須となります。また、アプリ内の各種のページ設計などを含めて統一感を持ってデザインすることが重要となり、これらの統一がアプリの印象を利用者に認識してもらうのに役立ちます。
このブランディングを考える上で、最初に取り組むのが背景色の変更だと思います。Windows ストア アプリのテンプレートでは、テーマという機能が含まれておりデフォルトのテーマは「Dark」となっています。これをSDKサンプルのように白を基調としたテーマにするには、App.xamlに「RequestedTheme」属性を記述します。

<Application・・・・・
	RequestedTheme = "Light"
	・・・・・   >

 
アプリの名前を変更するのも忘れないようにしましょう。アプリの名前は、App.xamlに「AppName」というキーを持つリソースとして定義されています。

プライバシーポリシーは、ネットワーク通信を行うアプリでは必須となります。Windows 8 アプリの認定の要件 (Windows)の要件4.1.1に「ユーザーの IP アドレスなどの個人情報をアプリで収集または送信する場合は、」とあり、収集するかしないは別として通信を行う場合は通信インフラストラクチャーがIPアドレスを送信します。この理由から、プライバシーポリシーを用意することが必須となります。特に、Visual Studioで作成した新規プロジェクトではデフォルトで「インターネット」が有効になっているので、注意しましょう。用意するプライバシーポリシーは、以下の2ヶ所への明示が必須となります。

  • アプリ内でプライバシーポリシーを表示できるようにする。
    アプリで表示するページでも良いですし、Webサイトへのリンクでも構いません。
  • ストアのアプリ紹介ページからプライバシーポリシーを表示できるようにする。
    プライバシーポリシーを掲示しているWebサイトのリンクを用意する必要があります。

Package.appmanifestは、特に以下の点を正しく設定します。

  • アプリケーション UI タブ
    表示名:必ずデフォルトの名前から変更します。アプリケーションの名前で、タイルなどに表示されます。長い場合は、短い名前も適切に設定します。
    説明
    各種ロゴと背景色。特にスプラッシュスクリーンの背景色は、アプリ全体の背景色と統一感を持って設定します。
  • パッケージ化 タブ
    パッケージ表示名:必ずデフォルトの名前から変更します。
    パッケージ名:変更しても良いでしょうし、デフォルトのGUIDにしておいても構いません。

パッケージ名やパッケージ表示名は、意図的にデフォルトの値にしておくこともあります。これは、セキュリティ上の理由です。Windows ストアアプリは、インストール自体がパッケージ名単位で行われます。逆を言えば、パッケージ名が理解しやすいと、カジュアルハックがし易いとも言えます。カジュアルハックと言っても、パッケージのローカルストア(ユーザー プロファイルの中にあります)をWindows エクスプローラーなどで覗いたりという意味です。パッケージ名を変更することは、一見しただけでアプリとの関連付けを連想させないためだけに、アプリと関係の無いパッケージ名やパッケージ表示名にしておくという程度の効果しかありません。この理由は、タスクマネージャーなどを使うことで、パッケージ名を利用者が知ることができるからです。

ここまで説明してきたのは、NewReaderテンプレートのReadme.txtに基づいています。新規のグリッドアプリケーションを自分で作成した場合は、データソースの変更が最初に行うステップとなります。以下に記載するのは、必ずではありませんが、このような考え方でNewsReaderはデータソースをカスタマイズしたと理解して頂ければ結構です。

  • Sampleで始まる名前をNewsにリネーム
  • NewsDataComon、NewsDataItemクラスへプロパティを追加
  • NewsDataGroupクラスへ、TopHighlitedItemsプロパティとNumberOfToken、ArticleCountプロパティを追加
    グリッドアプリケーションのHubページは、TopItemsプロパティをデータバインドしています。TopItemsとは、Itemsプロパティのコレクションに対する操作に連動して最初の12件を保持するコレクションです。
    NewsDataGroupでは、NumberOfToken(デフォルト4)を設定することで、設定した件数のNewsDataItemを返すTopHighlitedItemsプロパティを用意しています。このプロパティが、Hubページにデータバインドしています。
    セマンティックズームのズームアウトビューにデータバインドするために、件数を返すArticleCountプロパティを用意しています。
  • NewsDataSourceクラスへ以下を追加
    GetGroupメソッドとGetItemsメソッドのバグへの対処( == 1 を >= 1)。
    IsNetworkAvailable(オフライン判定)、IsInitialized(データの読み込み済みかどうか)プロパティを追加。
    コンストラクターで、DesignMode.DesignModeEnabledの場合にサンプルデータの作成。
    LoadRemoteDataAsyncメソッドを追加して、このメソッドで実際のデータを読み込んでいます。
    TwitterやRSSフィードや、ローカルへの保存や読み込みなどを行うメソッドを追加。

簡単に言えば、自分が作成するアプリが必要とするデータに応じてデータモデルを変更するということです。また、ズームアウトビューに対応させるために、必要なプロパティの実装を行ったり、オフラインサポートに応じて必要な機能を実装したりする必要があるということです。もちろん、オフラインサポートが必須というわけではありません。が、アプリを使用するユーザーにとってモバイルシーンでは必須とも言えるでしょう。

データソースを変更すれば、そのデータの表示方法も変更させる必要があります。具体的には、データテンプレートを自分のデータ用にカスタマイズする作業となります。この目的で、NewsReaderテンプレートは以下のようにApp.xamlでデータテンプレートを定義しています。

  • DefaultGridItemTemplate
    GroupedItemPageのGridViewのItemTemplateです。グリッドアプリケーションのデフォルトは、Standard250x250ItemTemplateになっています。
  • ItemGridTemplate
    GrpupDetailPageのGridViewのItemTemplateです。グリッドアプリケーションのデフォルトは、Standard500x130ItemTemplateになっています。
  • ZoomedOutItem
    NewsReaderテンプレートのセマンティックズーム サポートで使用しているズームアウトビュー用のデータテンプレートです。

NewsReaderテンプレートでは、アプリバーなどを導入しており、そのために必要となるスタイルをApp.xamlにリソースとして定義しています。これ以外に、行うカスタマイズとしては、次に説明するようなものがあります。

設定コントラクト

Visual Sudioではテンプレートの用意がありませんので、NewsReaderテンプレートなどのApp.xaml.csから必要なコードを引用してください。特に、プライバシーポリシーの明示を行うには必須となる作業です。

検索コントラクトや共有ターゲットコントラクト

Visual Studioを使ってプロジェクトへ[追加]-[新しい項目]で表示されるウィザードを使って、検索コントラクト、あるいは共有ターゲットコントラクトを追加して雛形を作成します。追加した後に、以下の作業を行います。

  • 追加されたページが、LayoutAwarePageを継承していない場合は継承するように変更します。
  • ロジックを作成します。ロジックの参考例としては、開発体験テンプレートを参照してください。
  • 検索コントラクトや共有ターゲットコントラクトを追加すると、ウィザードは以下の作業を行っています。
    App.xaml.csにコントラクト用のアクティベーション コードを追加。
    該当ページ.xamlを追加。

15秒以内にアクティブ化を完了する

Windows ストアアプリでは、タイルをタップしてから起動が完了するまでに15秒を超えるとシステムがアプリを強制終了する仕組みを備えています。たとえばNewsReaderテンプレートなどは、BlogやTwitterをネットワークを使って読み込んでデータソースを作成しますから、容易に15秒という時間を超えてしまいます。仮に、15秒を超えていればシステムに強制終了させられるため、アプリが起動することはなくなってしまいます。この症状を避けるためにNewsReaderテンプレートが行っている方法は、以下のようなものです。

  • LoadStateメソッドを非同期とする

この対策を行っても複数のフィードを行うとビューが表示されるまでに、時間がかかることになってしまいます。この理由から、NewsReaderテンプレートでは、1)1画面に収まるデータの取得、2)残りのデータを取得の2段階に分類しており、後者を非同期に処理しています。つまり、ビューが表示された後も、データの読み込みを続けるので徐々にデータが増えていくという動作を行うことで、ユーザーがビューを操作できるようになる時間の短縮を行っています。
このような手法には、画一的なアプローチはなく、取り扱うアプリのコンセプトやデータによって最適と思われる方法を使っていく必要があります。そのためには、作成されたアプリのボトルネックが何処に存在するかを調査して、その結果をもとに解決策を考えていきます。ちなみに要件3.8では、「5秒以内に起動すること」とあり、起動時にかけられる制限は厳しくなっています。つまり、データの初期化を同期的に行うのではなく、非同期処理を適切に組み合わせる必要があるということになります。

5秒以内に一時停止を完了する

Windows ストアアプリでは、アプリがバックグラウンドに移行するとシステム側がシステムリソースとの関係で一時停止させたり、強制終了させたりする場合があります。この時の状態遷移は、「一時停止」-[復帰]、[一時停止]-[強制終了]の2種類となります。一時停止に伴い、Suspendイベントがアプリに通知されるため、Suspendイベントの処理は「5秒以内に完了」する必要があります。仮に、5秒を超えるとシステムがアプリを強制終了させます。
一般的に一時停止イベントで行う処理は、以下のようなものです。

  • アプリのセッションデータを保存します。
    セッションデータとは、動作している間の一時的なデータであり、強制終了後の起動で元の状態に戻すために必要とするデータです。

この動作は、強制終了された後にアプリを起動した時に、強制終了させられた状態にアプリを復帰させる場合は実装する必要があります。既に説明したようにグリッドアプリケーションは、SuspensionManagerによってFramのナビゲーション履歴を保存するようになっています。従って、この機能を有効にしている限りは、セッションデータの保存と復帰は必須となります。問題は、セッションデータの保存と復帰の実装コードを何処に記述するかという点になります。

  • App.xaml.cs
    SusupensionManagerが、保存や復帰を行っているので統一性が取れて簡単です。が、扱うデータが大きい場合は、アクティブ化や一時停止の時間制限が問題にならないかと検討しましょう。
  • LoadStateメソッド
    NewsReaderテンプレートで採用した方法です。LoadStateを非同期メソッドにすることで、アクティブ化の時間制限を回避することができます。
    問題になるのは、どのタイミングで保存するかということです。

NewsReaderテンプレートでは、RSSなどのフィードの読み込みが完了した時にデータを保存するようにしています。この手法の根底にある考え方は、データモデルに変化があったタイミングで保存するというものです。この当たりの考え方も統一的なものはありませんので、作成されるアプリに応じて考えるものとなります。ちなみに要件3.8では、「2秒以内に中断を実行できること」とあり、かなり厳しくなっています。要件3.8が厳しくなっている理由は、非力なPCのサポートを前提に置いていると考えると良いでしょう。つまり、サポートCPUを限定すれば問題がなくなることも少なくないということになります。

ユーザーコントロールを作成するには

C#やVB、そしてC++でxamlを使って開発される場合は、ユーザーコントロールを作成したい場合もあることでしょう。ユーザーコントロールを作成する場合に、注意するのはデータバインドとVisualStateManagerによるビューをどのように切り替えるかという点になります。最初に、簡単なXAMLの定義を以下に示します。

<UserControl
    x:Class="App4.MyUserControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App4"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    x:Name="self"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400"><Grid><TextBox x:Name="txtInput" 
                 Width="{Binding Width, ElementName=self}"
                 Height="{Binding Height, ElementName=self}"
                 Text="{Binding Data, ElementName=self, Mode=TwoWay}"
                 /></Grid></UserControl>

 
ユーザーコントロールにTextBoxを置いただけのシンプルなxamlです。このxaml定義で、注意する必要があるのはUserControlのName属性とデータバインドの記述方法の2種類になります。

  • x:Name="self"
    UserControlにName属性を付与することは、次のデータバインドを要素内で完結させるために必須となります。名前は、理解しやすいように「self」としていますが、何でも構いません。
  • {Binding Data, ElementName=self, Mode=TwoWay} 
    最初の「Data」が要素名を表し、ElementNamaeが自分自身(作成しているユーザーコントロール)を指します。つまり、MyUserControl1.Dataプロパティを指していることになります。最後のModeがバインディングを双方向にしています。これは、TextBoxコントロールが入力を受け取る性格から設定しています。実際には、一方向でも問題はありません。要は、作成されるコントロールが双方向バインディングをサポートするかどうかで判断すれば良いということです。

次にユーザーコントロールに実装したコードを以下に示します。

// 依存プロパティの定義
public static DependencyProperty DataProperty =
       DependencyProperty.Register("Data",
         typeof(string), typeof(MyUserControl1),
         new PropertyMetadata(string.Empty, 
             new PropertyChangedCallback(MyUserControl1.OnDataChanged)));
// プロパティ変更のイベントハンドラー
private static void OnDataChanged(DependencyObject obj,
                            DependencyPropertyChangedEventArgs args)
{
    var myUserControl1 = (MyUserControl1)obj;
    myUserControl1.Data = (string)args.NewValue;
}
// 依存プロパティの実装
public string Data
{
    get
    {
        return this.GetValue(DataProperty) as string;
    }
    set
    {
        this.SetValue(DataProperty, value);
    }
}

   
コードを見れば理解できると思いますが、DependencyPropertyを定義する必要があります。この定義で注意すべきことは、PropertyMetadataの定義だと思います。双方向バインディングを行う場合は、PropertyMetadataの第2引数にPropertyChangedCallbackを指定します。一方向バインディングであれば、第2引数はnullで構いません。この点が、データバインド可能なプロパティを実装する上での注意点となります。次に、このコントロールを使用するXAMLの定義を以下に示します。

<local:MyUserControl1 x:Name="myControl1" Data="{Binding Data, Mode=TwoWay}" 
                      Margin="95,127,0,0" Width="430" Height="65"/>


後は、myControl1のDataContextにデータソースを設定することで、データバインドは完了となります。 上記で説明したことは、データバインド可能なユーザーコントロールを作成する上での最低限の知識となります。次に、VisualStateManagerを定義する上で注意する点を以下に示します。

  • ユーザーコントロール側のXAMLにVisualStateManagerを記述する。
    記述することで、Visual Studioのデバイスタブでビューを切り替えてデザインの確認を行うことができます。
  • 利用する側のXAMLで、LoadedとUnloadedにLayoutAwarePageのイベントハンドラーを設定する。
    グリッドアプリケーションのVisualStateManagerを使ったビューの切り替えで説明したように、LayoutAwarePageのStartLayoutUpdatesメソッドとStopLayoutUpdateメソッドを使うことでビューの切り替えが行われるようになるためです。

上記に説明したのは、基本的な方法となります。もちろん、LayoutAwarePageを使用しない場合ではビューの切り替えを自分で考える必要があります。たとえば、ウィンドウのSize変更イベントなどを使って、ユーザーコントロール内で適切なビューに合わせて配置を変更するなどの方法も考えられます。この方法の欠点は、デザイン時のビューの確認をどうするかということです。デザイン時に確認したいとなれば、XAML内にVisualStateManagerを定義せざるを得ません。

Windowsストアアプリにおける グリッドアプリケーションについて(8)

$
0
0

前回まででグリッドアプリケーションの基本構造と良く行われるであろうカスタマイズ方法を説明しました。今回は、カスタマイズの具体例としてSomasegarのBlogで取り上げられていた「Build and End-toEnd Windows Store App」シリーズを補足します。

この記事を補足した方が良いと考えた理由は、作業が進むにつれて詳細を説明するのではなく、抜粋で解説しているからです。この理由から、正しく理解していないと、コードのコピー/ペーストでは動くものにならない可能性が高いので、初心者にはハードルが高いと考えたからです。以降で、それぞれのフェースに沿って補足していきます。

Getting Data

この解説で気になったのが、データソースを取得するコードなどが気になりましたので、少し詳細に解説します。
記載されていませんが、最初に考えないといけないのはサンプルデータの問題です。この記事では、SampleDataSourceのコンストラクタ内に記載されているサンプルデータを削除していると考えるべきです。サンプルデータを削除した場合の問題は、デザイナーでのデザインがし難いということです。この問題を回避するために、サンプルデータをデザイン時のみに作成するように変更すべきでしょう。具体的には、以下のようにします。

public SampleDataSource()
{if (Windows.ApplicationModel.DesignMode.DesignModeEnabled){CreateSampleData();}
}

void CreateSampleData()
{
       // ここにサンプルデータのコードをコピーします
}

                    
このようにすることで、デザイン用のサンプルデータを維持しながら、実行時にはサンプルデータを作成しなくなることは既に説明した通りです。
次に気になっているのが、以下のコードになります。

public static readonlyObservableCollection<SampleDataGroup> AllGroups =
    new ObservableCollection<SampleDataGroup>();

SampleDataSourceクラスは、もともとインスタンス プロパティとして「AllGrpoups」を持っており、AllGroupsフィールドを実装するのであれば、プロパティ名と同じになってしまうことから、インスタンスプロパティを削除した方が良いことになります。AllGroupsメソッドを追加しないで既存のプロパティやメソッドを使用する前提で、作成したのが次のAddGroupForFeedAsyncメソッドになります。

public static async Task<bool> AddGroupForFeedAsync(string feedUrl)
{
    if (SampleDataSource.GetGroup(feedUrl) != null) return false;   // 取得済みのチェック

     var feed = await new SyndicationClient().RetrieveFeedAsync(new Uri(feedUrl));
    var feedGroup = new SampleDataGroup(
        uniqueId: feedUrl,
        title: feed.Title != null ? feed.Title.Text : null,
        subtitle: feed.Subtitle != null ? feed.Subtitle.Text : null,
        imagePath: feed.ImageUri != null ? feed.ImageUri.ToString() : null,
        description: null
        );
    // フィードアイテムを処理します
     foreach (var i in feed.Items)
    {
        string imagePath = GetImageFromPostContents(i);
        if (imagePath != null && feedGroup.Image == null)
        {
            feedGroup.SetImage(imagePath);
        }
        feedGroup.Items.Add(new SampleDataItem(
            uniqueId: i.Id, title: i.Title.Text, subtitle: null, imagePath: imagePath,
            description: null, content: i.Summary.Text, group: feedGroup
            ));
    }SampleDataSource._sampleDataSource.AllGroups.Add(feedGroup);
    return true;
}


変更したのは、オリジナルをAllGroupsフィールドを、定義されている「_SampleDataSource」フィールドを使ってAllGroupsプロパティへと買い換えたことです。
次に変更するのは、GroupedItemPage.xaml.csのLoadStateメソッドになります。

protected override async void LoadState(
          Object navigationParameter, 
          Dictionary<String, Object> pageState)
{
    // TODO: 問題のドメインでサンプル データを置き換えるのに適したデータ モデルを作成します
     var sampleDataGroups = SampleDataSource .GetGroups((String)navigationParameter);this.DefaultViewModel["Groups"] = sampleDataGroups;
    await SampleDataSource
          .AddGroupForFeedAsync("http://blogs.msdn.com/b/somasegar/rss.aspx");
    await SampleDataSource
          .AddGroupForFeedAsync("http://blogs.msdn.com/b/jasonz/rss.aspx");
    await SampleDataSource
          .AddGroupForFeedAsync("http://blogs.msdn.com/b/visualstudio/rss.aspx");
}


もともとのコードでは、「this.DefaultViewModel["Groups"] = SampleDataSource.AllGroups;」となっていたものをテンプレートが生成したコードのままにしています。この理由は、AllGroupsフィールドを追加していないためです。

WebViewコントロールへの変更

この解説で気になったのが、数点あります。それは、 XAMLの記述とHtmlSource extension propertyの記述です。私が作成したXAMLを以下に示します。

<UserControl Loaded="StartLayoutUpdates" Unloaded="StopLayoutUpdates"><!--<ScrollViewer x:Name="scrollViewer" Style='{StaticResource HorizontalScrollViewerStyle}' Grid.Row='1'>--><Grid ><Grid.RowDefinitions><RowDefinition Height='Auto' /><RowDefinition Height='*' /></Grid.RowDefinitions><TextBlock x:Name='itemTitle' Margin="20,10,10,10"
                   Text="{Binding Title}" 
                   Style='{StaticResource SubheaderTextStyle}'
                   IsHitTestVisible='False' Grid.Row='0' /><WebView x:Name='itemWebView' 
            local:WebViewExtension.HtmlSource='{Binding Content}'
                     Grid.Row='1' Margin="20,0,10,20"
 /></Grid><!--</ScrollViewer>--><VisualStateManager.VisualStateGroups><VisualStateGroup x:Name="ApplicationViewStates"><VisualState x:Name="FullScreenLandscape" /><VisualState x:Name="Filled"/><VisualState x:Name="FullScreenPortrait"></VisualState><VisualState x:Name="Snapped"></VisualState></VisualStateGroup></VisualStateManager.VisualStateGroups></UserControl>

 
もとのコードと変更しているのは、以下の2点となります。

  • Margin設定
    独自に値を変更したり、WebViewへ追加しています。この理由は、FlipViewのItemTemplateとしてWebViewを定義しているために、FlipViewのナビゲーションが操作しやすくするためです。現実的なアプリにするには、余白に関するデザインガイドラインを順守するようにすべきです。
  • VisualStateManagerの定義
    元の記事には記載されていませんが、要素を大きく変更するので削除すべきです。

後、注意すべきなのがHtmlSource Extension Propertyの記述がXAML上は、「local:WebViewExtension.HtmlSource」となっていることです。つまり、HtmlSource Extension Propertyのソースコードである「WebViewExtension.cs」を追加してから、名前空間をCallistoより変更するということです。

User Interaction

最初にAppBarの作成方法の解説がありますが、これが非常に難しいと私は考えています。AppBarをVisual Studioで作成するには、デザイン画面(ここでは、GroupedItemPage.xamlを使用します)、ドキュメントアウトラインとツールボックスを使用します。具体的な手順を以下に示します。

  • [ドキュメントアウトライン]-[]pageRoot]-[BottomAppbar]のコンテキストメニューから「アクティブなコンテナーの固定」を選択します。
    CreateAppBar
    この作業で、アクティブなコンテナーが設定されます。
  • [ツールボックス]より[AppBar」を選択して、デザイン画面にドラッグ&ドロップします。
  • [ドキュメントアウトライン]-[]pageRoot]-[BottomAppbar]のコンテキストメニューから「アクティブなコンテナーの固定」を選択します。
    この作業でアクティブなコンテナーが解除されます。
  • ドキュメントアウトラインで、作成したAppBarの中のGrid、そしてStackPanelと選択するか、XAMLでStackPanelを選択すれば、後はボタンを追加したりできるようになります。

ここで説明した方法は、GUIを使ってAppBarを作成する方法です。アクティブなコンテナーを指定する理由は、Visual Studioのデザイナーでは不可視のAppBarをドラッグ&ドロップで作成する位置(上側や下側)を指定することができないからです。もちろん、AppBarのXAMLコードをXAMLエディタで記述しても構いません。また、Bled for Visual Studio でも操作方法の考え方は同じですが、アクティブなコンテナーを使う必要はありません。この理由は、オブジェクトとタイムラインでドキュメントツリーのコントロールをドラッグできるからです。

次に解説がない作業として、Common/StandardStyle.xamlより「AddAppBarButtonStyle」の定義をApp.xamlへコピーして下さい。

<Style x:Key="AddAppBarButtonStyle" 
          TargetType="ButtonBase" 
          BasedOn="{StaticResource AppBarButtonStyle}"><Setter 
          Property="AutomationProperties.AutomationId" 
          Value="AddAppBarButton"/><Setter Property="AutomationProperties.Name" Value="Add"/><Setter Property="Content" Value=""/></Style>


この作業ができてから、AppBar内の左側のStackPanelへTextBoxとButtonとProgressRingコントロールをツールボックスよりドラッグ&ドロップします。その後に、Buttonをマウスでクリックして、コンテキストメニューを使って[テンプレートの編集]-[リソースの適用]-[AddAppbarButtonStyle]をクリックして、スタイルを適用します。また、追加したコントロールの名前などを設定した結果のXAMLを以下に示します。また、ボタンをドラッグした場合は、Content属性を削除するから、プロパティウィンドウからコンテンツのリセットをして下さい。

<TextBox x:Name="txtUrl" 
            VerticalAlignment="Center" Width="500"
/><Button x:Name="btnAddFeed" 
           Style='{StaticResource AddAppBarButtonStyle}'
           Click='btnAddFeed_Click'/><ProgressRing x:Name='prAddFeed' IsActive='False' 
           Foreground='{StaticResource ApplicationForegroundThemeBrush}' />

オリジナルでは、TextBoxのWidthに言及はありませんが、500px程度に設定しないとURLを入力するにしても、幅が狭すぎるということになります。後は、ロジックとしてBtnAddFeed_Clickイベントハンドラー、AddFeedAsyncメソッドの実装になりますが、ここでは名前空間を使用するためにusing句を追加するから、フルネームで指定するかを行う必要があります。以下に、フルネームで指定したコードを示します。

private async void btnAddFeed_Click(object sender, RoutedEventArgs e)
{
    await AddFeedAsync(txtUrl.Text);
}

async System.Threading.Tasks.Task AddFeedAsync(string feed)
{
    txtUrl.IsEnabled = false;
    btnAddFeed.IsEnabled = false;
    prAddFeed.IsActive = true;
    try
    {
        await SampleDataSource.AddGroupForFeedAsync(feed);
    }
    catch (Exception exc)
    {
        var dlg = new Windows.UI.Popups.MessageDialog(exc.Message).ShowAsync();
    }
    finally
    {
        txtUrl.Text = string.Empty;
        txtUrl.IsEnabled = true;
        btnAddFeed.IsEnabled = true;
        prAddFeed.IsActive = false;
    }
}

 最後にフィードを追加している画像が以下のようにありますが、「http:///blogs.msdn.com/b/somasegar.rss.aspx」と指定してはいけません、
2.AddFeed
この理由は、somasegarのフィードは読み込んでいるので追加されることがないからです。

Enabling Live Tiles

ライブタイルを作成するコードは同じですが、注意する点は名前空間の指定となります。using句で指定するか、完全な名前で指定するようにします。

void UpdateTitle()
{
    var groups = SampleDataSource.GetGroups("AllGroups").ToList();

    var xml = new Windows.Data.Xml.Dom.XmlDocument();
    xml.LoadXml(
        string.Format(
        @"<?xml version=""1.0"" encoding=""utf-8"" ?><tile><visual branding=""none""><binding template=""TileWideText01""><text id=""1"">News by Soma</text><text id=""2"">{0}</text><text id=""3"">{1}</text><text id=""4"">{2}</text></binding><binding template=""TileSquarePeekImageAndText01""><image id=""1"" src=""ms-appx:///Assets/Logo.png"" alt=""alt text""/><text id=""1"">News by Soma</text><text id=""2"">{0}</text><text id=""3"">{1}</text><text id=""4"">{2}</text></binding></visual></tile>",
                groups.Count > 0 ? groups[0].Title : "",
                groups.Count > 1 ? groups[1].Title : "",
                groups.Count > 2 ? groups[2].Title : ""
        ));

    Windows.UI.Notifications.TileUpdateManager
        .CreateTileUpdaterForApplication()
        .Update(new Windows.UI.Notifications.TileNotification(xml));
}

            
タイルテンプレートのTileWideText01は、テキストを5つまで指定できますが、この例では4つしか指定していません。また、TileSquarePeekImageAndText01テンプレートを使って正方形のタイルに対するライブタイルを設定していることにも注意してください。タイルに正方形とワイドを指定した場合ですが、アプリケーションから正方形とワイドのどちらかが使われているかを知る方法はありません。タイルの種類は、ユーザーが選択するものだからです。アプリケーションができることは、両方のタイルを用意した場合に、両方のタイルに対してライブタイルを設定しなかればならないということです。
次に、ライブタイルを更新するためにGroupedItemPage.xaml.csのLoadStateメソッドを変更するという記事になりますが、ここでは AddFeedAsyncメソッドを追加してから、LoadStateメソッドを変更します。

// Live tile用に追加
async System.Threading.Tasks.Task<bool> 
             AddFeedAsync(string feed, bool tileUpdated)
{
    var added = await SampleDataSource.AddGroupForFeedAsync(feed);
    if (tileUpdated) return tileUpdated;
    return added;
}


AddFeedAsyncメソッドの目的は、フィードがある場合にライブタイルを更新できるようにするためです。次にLoadStateメソッドを以下に示します。

protected override async void LoadState(Object navigationParameter, Dictionary pageState)
{
    // TODO: 問題のドメインでサンプル データを置き換えるのに適したデータ モデルを作成します
    var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);
    this.DefaultViewModel["Groups"] = sampleDataGroups;

    //await SampleDataSource.AddGroupForFeedAsync("http://blogs.msdn.com/b/somasegar/rss.aspx");
    //await SampleDataSource.AddGroupForFeedAsync("http://blogs.msdn.com/b/jasonz/rss.aspx");
    //await SampleDataSource.AddGroupForFeedAsync("http://blogs.msdn.com/b/visualstudio/rss.aspx");

    var tileUpdated = false;
tileUpdated = await AddFeedAsync("http://blogs.msdn.com/b/somasegar/rss.aspx", tileUpdated);
tileUpdated = await AddFeedAsync("http://blogs.msdn.com/b/jasonz/rss.aspx", tileUpdated);tileUpdated = await AddFeedAsync("http://blogs.msdn.com/b/visualstudio/rss.aspx", tileUpdated);if (tileUpdated) {UpdateTitle();}


赤字にしているところが、オリジナルの記事から変更している箇所になります。このコードによって、3種類のフィードのいずれかが取得できればライブタイルが更新できるようになります。

Enabling Search

検索コントラクトを実装する説明です。Visual Studioでソリューションエクスプローラーから[追加]-[新しい項目]-[検索コントラクト]を行うことで作成したSearchResultPage.xaml.csのSearchResultsPageクラス内に、Filterインナークラスの定義があります。この定義をジェネリックに変更するのと、数か所のメンバーを変更します。変更したコードを以下に示します。

/// <summary>
/// 検索結果の表示に使用できるフィルターの 1 つを表すビュー モデルです。
/// // ジェネリックに変更します
/// </summary>
private sealed class Filter<T> : SomaNews.Common.BindableBase
{
    private String _name;

      // private int _count; // 削除

    private bool _active;private List<T> _result; // 追加
 
    // コンストラクタを定義します
    

      public Filter(string name, IEnumerable<T> result, bool active = false)
      {        
             this.Name = name;
            this.Active = active;
            this.Results = result.ToList();
      }
     public List<T> Results // ジェネリックに変更
     {
             get { return _result; }
             set {
                     if (this.SetProperty(ref _result, value))
                     {  
                          // プロパティ変更イベントを発生させます
                         
this.OnPropertyChanged("Description");
                          this.OnPropertyChanged("Count");

                   }
                }
    }

    public int Count
    {
        get 
        { 
            //return _count; //変更します
            return _result.Count;
        }   // セッターを削除します
        //set { if (this.SetProperty(ref _count, value)) this.OnPropertyChanged("Description"); }
    }

    public String Description
    {
        // 変更します
        //get { return String.Format("{0} ({1})", _name, _count); }
        get { return String.Format("{0} ({1})", _name, this.Count); }
    }
}

          変更箇所は、コンストラクタを変更して、Countプロパティを追加した_resultフィールドから値を算出するようにします。この変更に基づいて、関係する箇所を変更しています。このFilterクラスの役割は、検索結果の分類を表すことです。この例のRSSリーダーでは、読み込むRSSのサイト毎に検索結果を分類することです。
次に、LoadStateメソッドを以下に示します。

protected override async void LoadState(Object navigationParameter, 
                     Dictionary<String, Object> pageState)
{
    var queryText = navigationParameter as String;

    // TODO: アプリケーション固有の検索ロジックです。検索プロセスでは、
    //       結果カテゴリのリストを作成する必要があります。
    //
    //       filterList.Add(new Filter("<フィルター名>", <結果数>));
    //
    //       アクティブな状態で開始するには、3 番目の引数として true を渡すフィルターが最初
    //       のフィルター (通常は "All") のみであることが必要です。アクティブ フィルターの
    //       結果は以下の Filter_SelectionChanged で提供されます。

    //var filterList = new List<Filter>();
    //filterList.Add(new Filter("All", 0, true));

    // 検索コントラクトによる起動時のデータソースの初期化
    var targetGroups = SampleDataSource.GetGroups("AllGroups");if (targetGroups.Count() == 0) {await SampleDataSource.AddGroupForFeedAsync("http://blogs.msdn.com/b/somasegar/rss.aspx");await SampleDataSource.AddGroupForFeedAsync("http://blogs.msdn.com/b/jasonz/rss.aspx");await SampleDataSource.AddGroupForFeedAsync("http://blogs.msdn.com/b/visualstudio/rss.aspx");}

    var filterList = new List<Filter<SampleDataItem>>(targetGroups         .Select(feed => new Filter<SampleDataItem>(
            feed.Title, 
            feed.Items.Where(item => (item.Title != null &&
                                      item.Title.Contains(queryText)) ||
                                     (item.Content != null &&
                                      item.Content.Contains(queryText))),
            false
            ))
        );
    filterList.Insert(0,
        new Filter<SampleDataItem>("All", filterList.SelectMany(f => f.Results), true));
    // ビュー モデルを介して結果を通信します
    this.DefaultViewModel["QueryText"] = '\u201c' + queryText + '\u201d';
    this.DefaultViewModel["Filters"] = filterList;
    this.DefaultViewModel["ShowFilters"] = filterList.Count > 1;

}


オリジナルと違うのは、起動時のデーターソースの初期化コードです。元のコードでは、SampleDataSourceに追加した静的フィールドであるAllGroupsを使用するだけですが、このコードに問題があるためです。具体的には、検索コントラクトによって起動される場合にフィードが読み込まれないことで検索結果がゼロになるという問題です。この問題を避けるために、データソースの初期化コードを追加しています。
そして、Filter_SelectionChangedイベントハンドラーを記述します。

void Filter_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    // 選択されたフィルターを確認します
    //var selectedFilter = e.AddedItems.FirstOrDefault() as Filter;
    var selectedFilter = e.AddedItems.FirstOrDefault()
                           as Filter<SampleDataItem>;
    if (selectedFilter != null)
    {this.DefaultViewModel["Results"] = selectedFilter.Results; // 検索結果をバインドします
        // 対応する Filter オブジェクト内に結果をミラー化し、
        // いない場合に RadioButton 表現を使用して変更を反映できるようにします
        selectedFilter.Active = true;

        // TODO: this.DefaultViewModel["Results"] をバインド可能な Image、Title、および Subtitle の
        //       バインドできる Image、Title、Subtitle、および Description プロパティを持つアイテムのコレクションに設定します
        // 結果が見つかったことを確認します
        object results;
        ICollection resultsCollection;


データソースにデータをバインドするコードだけを追加しています。後は、オリジナルの記事にあるようにresultsListView_ItemClickイベントハンドラーを記述してから、 SearchResultsPage.xaml 内のAppNameリソースを削除します。AppNameリソースを削除する理由は、App.xamlでAppNameリソースが定義してあり、この定義を利用するためになります。

Enabling Sharing

共有ソースを実装する説明です。共有コントラクトは、共有データを提供する「共有ソースコントラクト」とデータを受け取る「共有ターゲットコントラクト」の2種類に分類されます。  共有ソースコントラクトを記述するには、Visual Studio の支援機能がないので記事の通りにコードを記述することになります。このコードは、共有データの本体を除けば、ほとんど同一のコードになることから以下に示します(ItemDetailPage.xaml.cs)。

#region 共有ソース
private DataTransferManager _dtm;

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);
    // イベントハンドラーを設定
    _dtm = DataTransferManager.GetForCurrentView();
    _dtm.DataRequested += _dtm_DataRequested;
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);
    // イベントハンドラーを解除
    _dtm.DataRequested -= _dtm_DataRequested;
    _dtm = null;
}

void _dtm_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
    var toShare = (SampleDataItem)flipView.SelectedItem;
    if (toShare != null)
    {
        args.Request.Data.Properties.Title = toShare.Title;
        args.Request.Data.Properties.Description = string.Empty;
        args.Request.Data.SetHtmlFormat(HtmlFormatHelper.CreateHtmlFormat(toShare.Content));
    }
}

#endregion

 
共有するデータ が異なれば、「args.Request.Data.SetHtmlFormat...」の記述を変更します。詳細は、ドキュメントを読んでください。上記のコードの注意点は、OnNavigatedFromイベントハンドラーで、DataRequestedのイベントハンドラーを忘れずに解除することです。こtれを忘れると、メモリーリークの原因になります。

Enabling Roaming

ローミングを有効するために、静的クラスとしてPersistedStateを追加しています(PersistedState.cs)。このコードを以下に示します。

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using SomaNews.Data;
using Windows.Storage;

namespace SomaNews
{
    internal static class PersistedState
    {
        internal static void SaveFeedUrls()
        {
            var serializer = new DataContractSerializer(typeof(List<string>));var feeds = SampleDataSource.GetGroups("AllGroups").Select(f => f.UniqueId).ToList();
            using (var tmpStream = new MemoryStream())
            {
                serializer.WriteObject(tmpStream, feeds);
                ApplicationData.Current.RoamingSettings.Values["feeds"] = tmpStream.ToArray();
            }
        }

        internal static IEnumerable<string> LoadFeedUrls()
        {
            if (!ApplicationData.Current.RoamingSettings.Values.ContainsKey("feeds"))
            {
                return Enumerable.Empty<string>();
            }

            var serializer = new DataContractSerializer(typeof(List<string>));
            using (var tmpStream = new MemoryStream(
                        (byte[])ApplicationData.Current.RoamingSettings.Values["feeds"]))
            {
                return (List<string>)serializer.ReadObject(tmpStream);
            }
        }
    }
}

   
オリジナルから変更しているのは、SaveFeedUrlsメソッドのデータソースの扱い方だけです。これは、AllGroups静的フィールドを追加していないためです。ローミングは、Windows ストアアプリでは標準機能として用意されているので、このコードのように簡単に記述することができます。注意点としては、データ容量に制限があることです。この理由から、この例ではフィードのURLだけを保存するようにしています。
次にフィードを保存するのと、読み込むコードを追加することです。このためにGroupedItemPage.xaml.csのLoadStateメソッドとbtnAddFeed_Clickイベントハンドラーにコードを追加します。

protected override async void LoadState(Object navigationParameter, 
                   Dictionary<String, Object> pageState)
{
    // TODO: 問題のドメインでサンプル データを置き換えるのに適したデータ モデルを作成します
    var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);
    this.DefaultViewModel["Groups"] = sampleDataGroups;

    var tileUpdated = false;
    if (sampleDataGroups.Count() == 0) 
{
foreach (var feed in PersistedState.LoadFeedUrls())
{
tileUpdated = await AddFeedAsync(feed, tileUpdated);
}
}
if (sampleDataGroups.Count() == 0)
{
tileUpdated = await AddFeedAsync("http://blogs.msdn.com/b/somasegar/rss.aspx", tileUpdated);
tileUpdated = await AddFeedAsync("http://blogs.msdn.com/b/jasonz/rss.aspx", tileUpdated);
tileUpdated = await AddFeedAsync("http://blogs.msdn.com/b/visualstudio/rss.aspx", tileUpdated); }
    if (tileUpdated)
    {
        UpdateTitle();
    }PersistedState.SaveFeedUrls(); // フィードの保存
}


private async void btnAddFeed_Click(object sender, RoutedEventArgs e)
{
    await AddFeedAsync(txtUrl.Text);
    PersistedState.SaveFeedUrls(); // フィードの保存
}

赤字を見れば理解できることでしょう。この段階になれば、LoadStateメソッドで文字列でフィードを追加している所を削除しても良いかも知れません。何故なら、フィードの追加ができますし、追加したフィードをローミングデータとして保存することができるようになっているからです。

記事では、Windows Azure モバイルサービスを使って通知サービスの機能を作り込むことが解説されています。こちらに関しては、Virtuoso - Shotaro Suzuki's Blogが詳しいのでこちらを参照してください。

Windowsストアアプリにおける グリッドアプリケーションについて

$
0
0

Windowsストアアプリにおける グリッドアプリケーションについて」という記事を8回に渡って記載してきました。このページは、各記事に対する索引として記載します。

  1. Windowsストアアプリにおける グリッドアプリケーションについて(1)
    この記事では、ページ・ナビゲーションの基本構造とDefaultViewModel、LoadStateメソッドなどを解説しています。
  2. Windowsストアアプリにおける グリッドアプリケーションについて(2)
    この記事は、1回目の続きでGroupedItemPageの構造を取り扱っています。GridViewやListViewコントロールでレイアウトが構成されている点と、ビューの切り替えをVisualStateManagerを使っていることを解説しています。
  3. Windowsストアアプリにおける グリッドアプリケーションについて(3)
    この記事では、データバインドの理解を深めるためにデータソースを解説しています。
  4. Windowsストアアプリにおける グリッドアプリケーションについて(4)
    この記事では、データソースが理解できた上でGroupedItemPageの残りの解説を行っています。この4回までを理解することで、GroupedItemPageの構造を完全に把握したと言えるでしょう。
  5. Windowsストアアプリにおける グリッドアプリケーションについて(5)
    この記事では、セクションに相当するGroupDetailPageを解説しています。
  6. Windowsストアアプリにおける グリッドアプリケーションについて(6)
    この記事では、詳細に相当するItemDetailPageを解説しています。
  7. Windowsストアアプリにおける グリッドアプリケーションについて(7)
    この記事では、グリッドアプリケーションのカスタマイズする時に注意すべき事項を解説しています。
  8. Windowsストアアプリにおける グリッドアプリケーションについて(8)
    この記事は、このシリーズの最終回としてS.Somasegarの記事である「Building an End-to-End Windows Store App」を題材にして、具体的にどのようにグリッドアプリケーションをカスタマイズしてRSSリーダーを作成するかということを解説しています。

Visual Studioが提供するWindows ストア アプリのテンプレートは、空のアプリケーション以外は理解するまでに時間がかかります。この理由は、テンプレートと言いながら、サンプルアプリケーションを如何に効率良く作れるかという観点も含めたサンプル アプリケーションになっているからです。この記事は、これらのテンプレートの理解を助けることを目的として記載しました。

是非、皆さんもWindows ストアアプリにチャレンジしてみて下さい。

GridViewのスクロール位置を復元するとある方法

$
0
0

@ITのGridViewのスクロール位置を復元するには?の記事を読んでいて、うーむ正攻法だなぁと感心していました。GetTemplateChildメソッドがprotectedなので、正攻法ではGridViewを継承したコントロールを作らざるを得ないのも事実です。ちょっとしたアプリに、新しいコントロールを作りたくない私の場合は、別の方策を考えてみます。最初に考えたのが、何はなくともReflectionです。リフレクションは、Windowsストアアプリだとprotectedメンバーを残念ながら取得することができません。じゃあ、どうしようかと考えていてXAMLのVisualTreeを辿れれば、何とかなるのではなかろいうかという考えです。試しにGridViewコントロールに対して、VisualTreeを調べてみるとScrollViewerコントロールのインスタンスが存在しています。後は、このインスタンスを取り出せば、何とかなるだろうと考えて作成したのが、以下のコードになります。

static class VisualTreeExtension
{
  // 指定したChildオブジェト型のインスタンスを返します
  public static T GetChildObject<T>(DependencyObject start)
  {
    var children = GetDescendants(start);
    var x = children.OfType<T>().ToList();
    var i = x.FirstOrDefault();
    return i;
  }
  // Childコレクションを作成します
  internal static IEnumerable<DependencyObject> GetDescendants(
                  DependencyObject start)
  {
    var queue = new Queue<DependencyObject>();
    var count = VisualTreeHelper.GetChildrenCount(start);
    for (int i = 0; i < count; i++) // 1レベルの子要素を取得します
    {
        var child = VisualTreeHelper.GetChild(start, i);
        yield return child;
        queue.Enqueue(child);
    }
    while (queue.Count > 0)
    {
        var parent = queue.Dequeue();
        var childCount = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < childCount; i++)    // 2レベル以降の子要素を取得します
        {
            var child = VisualTreeHelper.GetChild(parent, i);
            yield return child;
            queue.Enqueue(child);
        }
    }
  }
}


このGetChildObjectメソッドをGetChild<ScrollViewer>(itemGridViewer)のように呼び出せば、ScrollViewerのインスタンスを取得することができます。  それでは、GroupedItemsPage.xaml.csにどのように組み込むかを説明します。
基本的な考え方は、他のページへ遷移するタイミングでScrollViewer.HorizontalOffsetを保存しておいて、GroupedItemsPageへ戻ってきた時に保存したHorizontalOffsetを読みだして、移動するというものです。このために、メンバー変数を追加し、とItemGridViewのLoadedイベントとSizeChangedイベントに次のコードを記述します。

ScrollViewer _sv;
double? _position;

// 起動時に移動させるロジック
private void itemGridView_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (_position.HasValue)
    {
        _sv = VisualTreeExtension.GetChildObject<ScrollViewer>(itemGridView);
        _sv.ScrollToHorizontalOffset(_position.Value);
        _position = null;
    }

}
// SaveStateで移動量を取得するためにScrollViewerのインスタンスを取得
private void itemGridView_Loaded(object sender, RoutedEventArgs e)
{
    _sv = VisualTreeExtension.GetChildObject<ScrollViewer>(itemGridView);
}


メンバー変数(_sv)にLoadSateメソッドではなく、LoadedイベントでScrollViewerのインスタンスを設定していることに注意してください。この理由は、OnNavigatedToイベントハンドラより呼び出されるLoadSateメソッドのタイミングでは、ScrollViewerなどのインスタンスが作成されていない場合があるためです。次に、ScrollViewerの移動量を保存するコードをSaveStateメソッドに記述し、LoadStateメソッドで保存した移動量を読みだすコードを記述します。 

protected override void LoadState(Object navigationParameter, 
                        Dictionary<String, Object> pageState)
{
    // TODO: 問題のドメインでサンプル データを置き換えるのに適したデータ モデルを作成します
    var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);
    this.DefaultViewModel["Groups"] = sampleDataGroups;

    if (pageState != null)
    {
        if (pageState.ContainsKey("position"))
        {
            _position = pageState["position"] as double?;
        }
    }
}
protected override void SaveState(Dictionary<string, object> pageState)
{
    base.SaveState(pageState);
    if (_sv != null)
    {
        _position = _sv.HorizontalOffset;
        pageState["position"] = _position;
    }
}


これでScrollViewerの移動量を復元できるようになります。しかし、実際に色々と試した結果として、グリッドアプリケーションテンプレートはうまく動作しますが、NewsReaderテンプレートのようにデータモデルを非同期で読み込むパターンだと、データの読み込みが遅延している関係とUIの仮想化との組み合わせで、うまく動作しないことが多々あります。このような場合は、移動したいアイテムとなるデータオブジェクトのインスタンスを取得して、itemGridViewのSizeChangedイベントで、GridView.ScrollIntoView(アイテムオブジェクト)メソッドを使った方が良いでしょう。

また、VisualTreeを使ってオブジェクトを探すコードを自分で記述しない場合は、WinRT XAML Toolkitの GetFirstDescendantOfType<T>拡張メソッドを使うのも良いでしょう。

GridViewのアイテム・コンテナーをカスタマイズするには

$
0
0

XAML(C#やVB)で作成するWindowsストア アプリで良く使うであろうコントロールとして、GridViewやListViewコントロールがあります。Visual Studioに組み込まれてプロジェクト・テンプレートであるグリッド・アプリケーションでもこれらのコントロールを使用しています。これらのコントロールのメリットは、コレクションのデータをItemSourceとしてデータ・バインドできることです。しかし、デザインの一環で配色などを独自のものに設定していくと、GridViewコントロールが持つアイテムに対する標準の配色(青系)を変更したいことが良くあります。このような場合の方法としては、GridViewコントロールのアイテム・コンテナーをカスタマイズする必要があります。今回は、アイテム・コンテナーのカスタマイズ方法を説明します。

最初に、Visual Studioでストア・アプリのグリッド・アプリケーションを選択して作成したプロジェクトを用意します。そして、GroupdItemsPage.xamlをデザイナーで開きます。次に、GridViewを選択してから、コンテキストメニューを表示して、[追加テンプレートの編集]-[生成されたアイテム コンテナーの編集]-[コピーして編集]を選択します(コピー済みであれば、現在の編集を選択します)。
1.Edit_ItemContainer

 

そうすると、アイテム・コンテナーをコピーするダイアログが表示されます。[このドキュメント]を選択して、何らかの名前を指定してOKボタンをクリックします(Visual StudioやBlendでビジュアルにコンテナーを編集するには、[このドキュメント]を選択するのがポイントになります。アプリケーションなどを選択すると、ビジュアルな編集を行うことはできません)。
2.Copy_ItemContainer
こうすることで、アイテム・コンテナーの編集画面がデザイナーに表示されます。
3.Editor_ItemContainer

ドキュメント・アウトラインから、OuterContainer\ReorderHintContent\ContentContainer\InnerDragContent\SelectionBackgroundを選択して、プロパティ・ウィンドウでFillプロパティ(ブラシ)の色を変更します。こうすることで、選択されたアイテムの背景色をカスタマイズすることができます。
4.SelectionBackground

次に、ドキュメント・アウトラインから、OuterContainer\ReorderHintContent\ContentContainer\InnerDragContent\SelectedBorderを選択して、Strokeプロパティ(ブラシ)の色を変更します。これで、選択したアイテムの外周に表示される線の色を変更することができます。
5.SelectedBorder

次に、ドキュメント・アウトラインから、OuterContainer\ReorderHintContent\ContentContainer\InnerDragContent\SelectedCheckMarkOuter\SelectedEarmarkを選択して、Fillプロパティの色を変更します。これで、スワイプなどで選択した時の外周のに表示される線の色を変更することができます。
6.SelectedEarmark

今度は、OuterContainer\ReorderHintContent\ContentContainer\InnerDragContent\SelectedCheckMarkOuter\Pathを選択して、Strokeプロパティの色を変更します。これで、スワイプなどで選択した時に表示されるチェックマークの色を変更することができます。
7.Path

SelectedCheckMarkOuterとは、スワイプなどで選択した時に表示される外周などを定義しているGridとなっていますから、SelectedEarmarkとPathは同じ色にした方が良いでしょう。仮に、PathのDataを変更すればチェックマークの図形を変更することもできます。それでは、実行結果を以下に示します。
8.Running

この実行結果は、上記の変更だけでなくFocusVisualの変更も行っていますので、フォーカスが当たっているアイテムの外周が赤くなっています。また、SelectionBackgroundを変更していますが、画像が表示されていることによってSelectionBackgroundを変更した効果を確認できなくなっています。この点を解消するには、アイテム・テンプレートの画像定義のOpacityを変更するか、イメージを削除します。また、GridViewのIsSwipとSelectionModeを変更していることは言うまでもありません(変更しないとスワイプが使用できないからです)。

この例のコンテナーがどのような構造になっているかを、次に示します。
9.Structure

このコンテナーの構造に伴って、変更する可能性のある要素を次に示しておきます。

要素名説明
PointerOverBorderマウス・ポインターが入った時に表示する枠線
FocusVisualフォーカスが入った時に表示する枠線
SelectionBackground選択された時に表示する背景色
SelectedBorder選択された時に表示する枠線
SelectedCheckMarkOuter選択された時に表示するチェックマークと枠線


説明した手順を利用することで、標準で提供しているコントロールのスタイルをカスタマイズすることに応用することができます。今回は、アイテム・コンテナー(アイテムが表示される領域)ですが、個々のアイテムをカスタマイズするのであれば「生成されたアイテムの編集」を行えば良いわけです。

Winodwsストア アプリ開発者契約について

$
0
0

色々とお問い合わせを頂くことが多いので、Windows ストア アプリを公開する場合の注意事項を個人的な見解として記述します。最初に、Windows ストアに対して開発者アカウントを作成する場合に表示される、アプリ開発者契約を必ず読んでから登録作業を進めることをお願いします。アプリ開発者契約には、とても重要な内容が記述されています。幾つかを例として、以下に記載します。

  • 「1) Windows ストア アカウント」では、Windows ストア開発者アカウントを開設する必要があること、料金を支払う必要性があること、ストア アカウントを通して行う活動にはお客様が責任を持つこと、契約に違反した場合はストア アカウントを停止することがあることなどが記載されています。
  • 「5) アプリの価格、支払い、取引、および税。」では、c.支払条件で月額$200以上の売り上げがあった場合に支払いが行われることが記載されています。
  • 「7)機密保持」では、本契約を通して得た機密情報を契約完了から5年間は開示を行わないことが記載されています。
  • 「9) 免責、責任の制限、および要求に対する防御。」では、特にa.保障の免責にご注意下さい。この条項には、「Windows ストアおよびダッシュボードを、「現状有姿」、「無保証」、かつ「現状渡し」で提供するものであり、Windows ストアおよび開発者ポータルの使用に関する危険はお客様が負担するものとします。」と記載されています。つまり、ダッシュボードでアプリを提出した後に表示されている、前処理、セキュリティ・コンプライアンスなどの作業に関する時間は目安であって、SLAで保障しているものではないということになります。
  • また免責の「b.責任の制限」では、「直接損害を被った場合に限り、Microsoft またはその関連会社から最大でもアカウント料金と同等の金額 (アカウント料金が無料の場合は 1 ドル) の補償を受けることができます。 お客様は、派生的損害、逸失利益、特別損害、間接損害、懲罰的損害、および付随的損害を含むがこれらに限定されない、その他のいかなる損失や損害の補償を求めず、その権利を放棄することに同意するものとします。」と記載されており、直接損害に関してはアカウント料金と同等の金額の補償を行い、それ以外の損害に関しては補償を求めないで、その権利を放棄することになります。
  • 「10) 期間および契約の終了。」では、b.アプリの保持に記載されているように、契約が終了したり、アプリを削除しても、提出されたアプリを利用しているユーザーのために保持することが記載されています。

アプリ開発者契約に記載されているように、Windowsストアとダッシュボードは現状渡しで提供されているので、サービス・レベル・アグリーメント(SLA)のようなものが何も無いことに同意した上で、開発者アカウントを登録していることになります。もちろん、審査状況であるとか、ダッシュボードの健全性を示す何らかの伝達手段があればと私は考えておりますが、現時点では何も無いという状態になっています。この点を、理解してくださるようにお願いします。

たとえばですが、AndroidのGoogle PlayではGoogle Play Developer Program PoliciesDeveloper Distribution Agreementが該当すると考えていますが、現状渡し(AS IS)であるという点においては同じです。異なるのは、機密保持の条項がない点でしょうか。後は、Appleさんの契約であるREGISTERED APPLE DEVELOPER AGREEMENTですが、こちらも基本的に現状渡し(AS IS)であり、Windowsストアと同じように機密保持の条項も記載されています。従って、Microsoft、Google、Appleの契約においては、大差がないというのが私の認識となります。

次にアカウント料金に関する私の個人的な考えを示します。Windows ストア アプリは、Appxというパッケージ形式での配布が条件になっており、Appxはコード・サイニングが必須となっています。つまり、コード署名用の証明書が必須だということです。Windowsストアでは、提出されたアプリが認定された後に、公開準備作業があり、この作業を通して登録された開発者アカウントを発行者とした証明書を使ってAppxに対する署名作業が行われます。この時に使用した証明書は、Windowsストア側で管理しており、開発者へお渡しすることはありません。つまり、Windowsストアそのものにセキュリティ事故が発生しない限り、証明書の漏洩が起こらないようになっています。もちろん、簡単に漏洩しないように、厳密な管理配下で証明書を管理していることは言うまでもありません。また、証明書を管理していることから、有効期限の管理も行っています。証明書の再発行をスムーズに行うのが、開発者アカウントの自動更新の設定になります。開発者アカウントの自動更新を設定しておくことで、証明書のアイデンティティの一意性が保たれることになるわけです。このように私は考えていますので、現在の料金体系はコード・サイニング用の証明書を取得することを考えると、非常に安価であると考えています。高価であると考えている方がいらっしゃるのであれば、コード・サイニングの証明書の価格と有効期間を調べてみて下さい。

それでは、Windowsストアのダッシュボードの状況に関して不満を持ったらどうしたら良いのかという場合に関して説明します。この場合は、必ずサポートへ連絡フォームを使ってコンタクトを取ります。サポート・ページには、技術サポート開発者アカウント サポートの2種類があります。審査結果に関する対応だったり、審査基準に対する疑問などは、開発者アカウント サポート技術サポートへ連絡する必要があります。技術サポートは、機能をどのように実装したら良いかなどについて問い合わせを行いますが有料となります。ストアの開発者アカウントをお持ちの場合は2回/年のテクニカル・サポート・インシデントを持っていますので、このインシデントを使って技術サポートを受けることができます。では、3回目の技術サポートが必要になったとしたら、どうしたら良いかと言えば、技術サポートを必要であれば有料でサポートを依頼することになります。
一方の開発者アカウント サポートでは、開発者アカウントの作成に伴う質問であったり、アプリの販売やアプリの削除などの主にダッシュボード上における操作や事務手続きを依頼することができます。仮に、審査状況であったり審査内容に関する質問を行ったとしても、サポート対象範囲外であることから、仮に受け付けて貰えたりしたとしても、求める回答が得られるわけではないことに注意して下さい(赤字と取り消し線が訂正と追記です)。

サポートに関して追記します
問題のタイプを選択します。
type_of_problem
赤枠で囲った個所の中から、「アプリの送信と認定」を選択すると次のようなカテゴリーを選択するようになります。

type_of_problem2
赤枠で囲っているのは、Rejectされたしまったアプリに関する質問事項です。これらを選択して、問い合わせ内容を入力して問い合わせを行うようになります。

問題のタイプから「Windows ストアとWindows Phoneアプリ開発のテクニカル サポート」を選択してから、カテゴリを選択した後に連絡手段が表示されますので、連絡手段を選択して技術サポートに問い合わせる形式となります。問題のタイプで赤枠に囲ったものが現時点では開発者アカウント サポートに対する連絡手段が表示されますので、連絡手段を選択して連絡する形式になります。既に説明したように、開発者アカウント サポートでは、ダッシュボードのシステム上の問題に関しては満足のいく回答を得ることができると思います。残念ながらRejectとなった理由に関しては、認定ポリシーに沿った回答になってしまいます。この理由は、申請していただいたアプリを見ることができるのは審査員であり、開発者アカウント サポートではないからです。
====ここまでを追記しました。
追記しながら、認定されなかった場合の調べ方にどのような方法があるかを考えていました。現時点では、Windows 8 Clinicの特別診療科を活用するのが、良いのではないかと思います。この理由は、再審査に向けての相談を受け付けているからです。

サポートへ依頼する時の注意事項は、可能な限り同じ質問でコンタクト・フォームを使って何度も質問をしないようにすることです。回答があってから、その回答に対する返信方法を使って疑問を解消するようにして下さい。この理由は、サポート側は受け付けた質問ごとに依頼を管理しており、依頼が完了するまで状況を管理していることから、異なる質問が新規依頼で行われると、結果的に以前の依頼が遅れることにも繋がるからです。

何度も記載しますが、ストアの審査自体は完全に独立性を保つようになっており、これはお客様に限らず社員であっても同じです。この理由は、他のアプリ・ストアを見ていただければ理解できると思いますが、審査の基準をすり抜けようとする不正行為を防ぐためです。この意味では、どのようにアプリを提出するかという戦略を立ててから、アプリを提出した方が結果的に良い結果になることが多いということです。

最後に認定要件ですが、必ず変更が行われていきます。このため、変更��行われていないかを常に確認するようにして下さい。以前に認定されたアプリと同じだから、今度も認定されるとは限りません。もちろん、他の人と比較しても意味はありません。何故なら、申請した時点においてストア上のアプリは常に変化しており、極端な言い方をすればより競争が激しくなっているからです。よって、カテゴリー・キラーを目指す位の気持ちがないと、いけないということです。リジェクトに落胆することんなく、ストア・アプリを作っていってください。


Blog の調子が悪いです...

$
0
0

最近ですが、Blogの調子がおかしいです。
下書き保存した記事が公開できない状態が続いています。私がBlogを書くときは、以下のような方法です。

  1. Live Writer で下書きを保存。
  2. Blogのダッシュボードでタイトルを調整してから、公開。

Live Writerを利用している理由は、画像の埋め込みが簡単だかたです。Blogダッシュボードで記事を書いてから、公開としても、下書き保存したものを公開に設定しても公開されない症状が出ています。この状態でLive Writerから投稿を行うと公開されます。うーん。理由が、よくわかんないです。もしかすると、容量が上限に達したのかもしれませんが。。。。記事は不定期なので、あまり気にしないでください。もしかすろ、他へBlog を移動するかも知れません。

テスト用の証明書の有効期限を更新するには

$
0
0

Silverlight の XAP や ClickOnce で使用する自己署名用の証明書の有効期限が切れた場合に、makecert.exeを使って新しい証明書を作成して使用すると証明書の不一致が発生します。こうなると、以前のアプリケーションをアンインストールしてから新しいアプリケーションをインストールする必要があります。この問題を避けるには、証明書の更新要求を行ってから、証明書を証明機関(CA)から発行してもらう必要があります。一般的に商用のコード署名用の証明書の有効期間は、1年か、長いものでも3年になります。テスト用の自己署名用の証明書の場合は、証明書の更新をmakecer.exeはサポートしていません。この問題に対する記事としては、以下のものがあります。

どちらの記事も Visual Studio 2005 のものになりますし、作成したプログラムの実行には VC 2005のランタイムが必要になります。VC 2005 のランタイムをインストールしていない環境でそうしようかと考えて、証明書の作成を C# で行うサンプルがないかと探していたら、以下の記事がありました。

この記事に添付されている Certificate.cs と 先に提示した記事を参考にしながら、RenewCert というプログラムを作成しました。このプログラムは、コマンドラインで使用するもので、以下のような使い方をします。

  • RenewCert.exe 既存の証明書.pfx 新しい証明書.pfx CN="発行機関" "パスワード" 有効年数
    このコマンドで今日から指定した有効年数が期限の証明書を作成します。
  • RenewCert.exe -c 新しい証明書.pfx CN="発行機関" "パスワード" 有効年数
    makecert.exeと同じように新しい証明書を作成します。

makecert.exe で作成した証明書との違いは、拡張プロパティのAuthority Key Identifier(機関キー識別子)が無いことです。証明書の発行日は、コマンドを実行した日になっており、指定した年数が有効期間になり、更新の場合は元の証明書データを使用しますので public keyは同じものになります。添付したプログラムは、Visual Studio 2010で作成して、Windows 7 と Windiows Server 2008 R2 で動作確認しています。

追記:書き忘れていましたが、鍵長は2048に設定してありますので、1024で試される方はコードを修正して下さい。

実践F# 関数型プログラミング入門の正誤表の補足

$
0
0

技術評論社さんで「実践F# 関数型プログラミング入門」の正誤表がやっと公開されました。正誤表の公開が遅れまして、本当に申し訳ございませんでした。本当なら、9月には公開されないといけなかったのですが、諸般の事情で遅れてしまいました。公開されて正誤表には掲載できなかった箇所がございますので、以下に追加を掲載させていただきます。分量が多いのですが、ご容赦ください。また、諸般の事情から公開が遅れまして、本当に申し訳ございませんでした。

P14

筆者が考える本書の読み方は、1章から読み進めることも、あるいは興味のある章から読んで、必要に応じて他の章を読むというものです。
本書の読み方ですが、1章から順に読み進めてもよいし、興味のある章を読みながら、必要に応じて他の章を読むのもよいでしょう。

P17

参照透過性(参照透明性とも呼ばれます)
参照透明性(参照透過性とも呼ばれます)

P19  1

アルゴリズム実装には関数型プログラミングに影響を受けて
アルゴリズムは関数型プログラミングに影響を受けて

P20  1.1

プログラムング言語
プログラミング言語

P27  図1-4

高級水準言語
高級言語

P27  1.4.1

適材適所で言語が存在するのかということです。
適材適所で言語が存在するのでしょうか。

P28  1.4.2

「解くべき問題」ということです。
「解くべき問題」です。

P28  1.4.2

解き方をモデル化したのがプログラミング言語であるということです。
解き方をモデル化したのがプログラミング言語です

P29  Column

「Abstract Art」
「Abstract art」

P30  1.5.1

メタ言語として開発された。
メタ言語として開発されたものである。

P30  1.5.1

ラムダ計算で証明できるというものです。
ラムダ計算を使って証明できるというものです。

P32  1.5.3

「不遇の関数型言語」と呼んでいます。
筆者は「不遇の関数型言語」と呼んでいます。

P33  1.6.1

回路以外に流れることで他のトランジスタ回路へ影響を及ぼす
回路以外に流れることでトランジスタ回路の動作に影響を及ぼす

P36  1.7.1

しかしながら、Webの世界では
我々が利用しているWebの世界では

P39  1.7.4

不変性(英語でImmutable)という特徴があります。不変性とは、変数に値を代入すると(関数型言語の世界では、代入ではなく束縛と呼びます)値を変更できなくる性質のことです。
不変(英語でImmutable)な変数という特徴があります。不変な変数とは、変数に値を代入すると(関数型言語の世界では、代入ではなく束縛と呼びます)値を変更できないものです。

P54  表2-7

(1行でも複数行)
(1行でも複数行でも可

P81  4.4

bool型に係る演算
bool型に関わる演算

P115  リスト5-1

x |> f
=> f x
 y |> g => (|>) y g // 演算子の関数化 
=> (fun x f -> f x) y g // インライン展開 
=> let x = y in let f = g in f x // インライン展開 
=> g y // 極めて単純なインライン関数でのみ行われる特殊な変換 

P116  表5-1

±1.5×10-45 ~ ±3.4×1038(7桁)
±1.5×10-45~ ±3.4×1038(7桁)

P116  表5-1

±5.0×10-324 ~ ±1.7×10308(15~16桁)
±5.0×10-324~ ±1.7×10308(15~16桁)

P116  表5-1

±1.0×10-28 ~ ±7.9×1028(28~29桁)
±1.0×10-28~ ±7.9×1028(28~29桁)

P129  リスト5-29

> printfn "%6.2d" 123.45;;
12345
val it : unit = ()> printfn "%+6d" 123; printfn "%+6d" -123;;
  +123
  -123
> printfn "%+6d" 123; printfn "%+6d" -123;; +123 -123 

P139  リスト5-47

when句によるガートと網羅性
when句によるガードと網羅性

P141  5.14

全体を全体を{ ... }
全体を{ ... }

P168  5.25.1

failewithで例外を発生させる
failwithで例外を発生させる

P176  図6-1

let func n =
    let f n = seq { 1..n }
              | > Seq.filter (fun n -> 
                   n%3=0 || n%5=0)
    n | > f | > Seq.sum
let func n =
    let f n = seq { 1..n }|> Seq.filter (fun n -> 
                   n%3=0 || n%5=0)
    n |> f |> Seq.sum

P176  図6-2

let func n =
    let f n = seq { 1..n }
              | > Seq.filter (fun n -> 
                   n%3=0 || n%5=0)
    n | > f | > Seq.sum
let func n =
    let f n = seq { 1..n }|> Seq.filter (fun n -> 
                   n%3=0 || n%5=0)
    n |> f |> Seq.sum

P181  6.4

ちなみにF#には、いわゆるbreakやcontinueなど、ループ内で使用する専用の制御文はありません。
脚注と重複した内容のため本文から削除させていただきます

P188  6.7

「(unit -> int)」
(unit -> int)

P193  6.11

束縛に束縛されます
束縛されます

P202  6.17.1

Recourceful.txt
Resourceful.txt

P210  図7-2

7と8のノードの配置について
7と8のノードを入れ替えます

P212  7.3

Queu<'T>クラス
Queue<'T>クラス

P213  7.4

insertSort
挿入ソートは英語でinsertion sortであり、関数名をinsertSortでなくinsertionSortとするのがより適切です。

P240  リスト7-65

Seq.empty;;
> Seq.empty;;

P254  7.19.1

union、diffrence、intersect(集合演算)
union、difference、intersect(集合演算)

P273  8.6.2

オプショナル引数(optional argument)と呼ぶ
オプショナル引数(optional argument)と言う

P284  8.10

リスト8-47のような
リスト8-48のような

P302  8.19

型拡張には、内在的拡張(intrinsic extension)と任意的拡張(optional extension)の2種類があり
本書で参照させていただいた「F#ドキュメント翻訳向上委員会」の最新のご提案では、intrinsic extensionの訳語は固有拡張、optional extensionの訳語は任意拡張となっています。

P306  8.20

単行演算子
演算子

P311  9

.NET Frameworkのライブラリ使い方を何度も前章までで説明してきました。
.NET Frameworkのライブラリを前章までで何度も使用してきました。

P311  9

Framewrokのライブラリは、アセンブリ(物理的な実行ファイルのことで、拡張子がDLLかEXEになります)
Frameworkのライブラリは、アセンブリ(物理的な実行ファイルのことで、拡張子がDLLかEXEになります)

P315  9.2.1

リスト9-1に示した
リスト9-3に示した

P317  Column

リスト9-1とリスト9-2に示した
リスト9-3とリスト9-4に示したに示した

P322  9.3

丸括弧を付けるのが当たり前と説明しまし。
丸括弧を付けるのが当たり前と説明しました。

P323  Column

機能の1として含まれています。
機能の1つとして含まれています。

P333  10

公開する仕様を宣言宣言するものです。
公開する仕様を宣言するものです。

P344  10.1.3

「モジュール宣言が必要」が必要
モジュール宣言」が必要

P346  表10-2

暗黙的にモジュール名をファイル名として使用できる
暗黙的にファイル名をモジュール名として使用できる

P362  図10-7

実装コードとシグネチャの説明文について
実装コードとシグネチャの説明文を入れ替えます

P372  表11-3の補助説明

pati
pati

P384  11.2.1

提供しますから、非同期プログラミングモデルを説明する前に同期プログラミングを振り返ってみましょう。
提供しますが、非同期プログラミングモデルを説明する前に同期プログラミングを見直してみましょう。

P387  11.2.1

.NET Frameworkが提供する非同期プログラミングとは、「BeginとEndという名前で始まるメソッド」を組み合わせることで(Begin/End パターン)実現しますから、複数の非同期プログラミングを組み合わせた場合を考えてみましょう。
.NET Frameworkが提供する非同期プログラミングは、「BeginとEndという名前で始まるメソッド」を組み合わせることで(Begin/End パターン)実現します。では、複数の非同期プログラミングを組み合わせた場合を考えてみましょう。

P395  11.2.3

表11-9に示したWebClient.AsyncDownloadString
11-10に示したWebClient.AsyncDownloadString

P406  11.3

エージェントと呼ばれるます
エージェントと呼ばれます

P420  図11-10

OL
0L

P420  図11-11

OL
0L

P429  12.2

表12-3に示したquery関数
12-4に示したquery関数

P452  表C-1

Val
Var

P459  索引

counBy
countBy

能楽堂 1.2.0 の assets:precompile コマンドについて

$
0
0

能楽堂 1.2.0が2011年12月にリリースされていまして、Rails のバージョンが3.1.2になっています。が、production環境で運用するために必要な「rake assets:recompile」コマンドがエラーとなります。この問題は、rails側にあります。Railsの最新版は、3.2.0なのですが、artonさんが試した限りはassets:precompileコマンドのエラーは解消されていなかったそうです。そこで、artonさんがパッチを作成してくれたのですが、このパッチがRails本体に取り込まれていました。このパッチを使って、railsのバージョンは異なりますが 能楽堂 1.2.0の rails 3.1.2にパッチを当ててみました。結果は、それまでの assets:precompileコマンドとコンソール出力が異なりますが、問題なくアセットのプリコンパイルができて、production環境で動作させることができるようになりました。

  • Rails 3.1.1までは、プリコンパイルのフェーズ毎にコンソール出力がありました。
  • パッチ環境は、コンソール出力はありませんが、publicにassetsディレクトリと結果が正常に出力されます。

パッチは、actionpack/lib/sprockets/assets.rake に投入します。artonさん、有難うございました。

WDD で、アプリのライフサイクルと実行環境の解説を担当します

$
0
0

4/24-25 に開催される Windows Developer Daysで、「アプリのライフサイクルと実行環境の解説」を担当します。このセッションの準備をしている最中ですが、このセッションで取り上げる話題として、以下のものを考えています。

  • アプリの状態遷移(実行中、一時停止、復帰、強制終了など)
  • アプリが起動される仕組みについて
  • アプリの状態の取り扱い
  • バックグラウンド タスク
  • etc

アプリが起動される仕組みについては、Build Windowsの「Windows Runtime internals: understanding "Hello World"」をモチーフにして取り扱うことを考えています。この説明を通して、Windows Runtime でのアプリの起動メカニズムを解きほぐせることでしょう。

早期登録も 4月18日に延長されたことですし、この機会にご参加ください。また、スペシャルトラックでは、1セッションのコーディネーターをしていまして、Azure での Ruby 活用を取り扱います。こちらは、私が話すのではなく、外部の方にお願いしていますが、Ruby の活用事例としても有益になることを目指して、一昨日に3時間の打ち合わせをしていましたので、ご期待ください。

WDD でお話するまでの経緯とデモ内容のフォロー

$
0
0

WDDのアプリのライフサイクルと実行環境に、多くの方が参加していただきまして有難うございました。少し、時間が延びましたが、本当に有難うございました。今回は、この時にお話しできなかった話題と準備作業の舞台裏を記載します。
資料の作成を始めたのは3月からなのですが、デモの準備をしながら資料の見直しやサンプルの作り直しなどを何度も行っていました。大体の準備ができたのが4/13で、翌週に最終チェックと環境整備を行っていました。私がデモで使用したPCは、自前のHP TouchSmart tx2なのですが、この時に結局3回ほどインストールを行っていました。この理由は、マルチタッチのドライバーです。タッチパネルがN-Trig製なのですが、HPから提供されているDuoSenseはWindows 8にインストールすることができません。ドライバーインストールを様々な方法で試した結果、シングルタッチでは動作していましたが、やはりマルチタッチにしたいということから再インストールを実施したのです。このPC特有かも知れませんが、DuoSenseのインストール方法を以下に記載します。

  1. Windows 7 環境でマルチタッチを有効にする(DuoSenseをインストール)。
  2. Windows 8 CPをアップグレードインストールします。
  3. N-Trigが公開しているベータードライバーをインストールします。
  4. マルチタッチが動作しない場合は、再起動してみる。

上記の方法で私の環境では動作していますが、デバイスマネージャーには「N-Trig device Not Win8 supported upgrade it」と表示されています。この表示は、N-TrigのNtrigDigi.infファイルに記載されていまして、ドライバーインストール時に何かの条件に一致するとこのようになるようです。

WDDでお見せしたデモアプリが、どのような動きをしていたかを以下に記載します。

  • アクティベーションのデモ
    アプリの状態には、実行していない(Not Running)、実行中(Running)、一時停止(Suspending)があります。また、以前の実行状態には、終了(Not Running)と強制終了(Terminat)があります。この状態に応じて、タイルのバッジにグリフ(アイコンのような図形です)を表示するというものでした。強制終了(OSが終了させる)した場合に、入力した内容を保存して、次に起動した場合に読み込むという動作もお見せしました。これは、強制終了する場合に、必ずSuspendingイベントが発生することを確認するためのものでした。
  • ライブタイルのデモ
    タイルを更新するためのタイルAPIを使って、更新することと、ローカルキューを介した更新をお見せしました。また、バッジを数字(1から99)で更新することもお見せしました。ここで説明が不足していたのは、タイルを更新してからアプリが終了した場合のタイルがどうなるかという点です。このケースでは、更新したタイルの中身(タイル自体やバッジ)は残ります。つまり、ライブタイルで更新した内容はアプリのインスタンスとは別にシステム側で管理されているということになります。残ったタイルの更新内容を消去するには、タイルAPIを使って更新内容を空にするなどの操作が必要になります。従って、ライブタイルを使用する場合は、有効期限を忘れずに設定するのが良いと私は考えています。
  • スプラッシュスクリーンのデモ
    スプラッシュ スクリーンは、パッケージマニフェストに指定した画像がシステムによって自動的に表示されます。アプリの初期化に15秒以上かかる場合に、強制終了となります。このような場合に行うのが、自前でスプラッシュスクリーンを表示することです。アプリの初期化の終了とは、CPの場合はActivateイベントを抜けるタイミングとなります。スプラッシュ スクリーンAPIを使用することで、スプラッシュスクリーンに指定した画像の表示位置やサイズなどの情報を取得できます。取得した情報を使って、自前のスプラッシュスクリーンとして表示する画像などの位置を調整します。デモでは、意図的に背景色を変えていました。これは、指定した画像ではなく自前で用意したことを明示するための処置でした。実際のアプリで、このようなことをしてはいけません。
  • バックグラウンド オーディオのデモ
    バックグラウンド タスクの利用例として、バックグラウンド オーディオを動作させるデモを行いました。バックグラウンド オーディオを実現するには、パッケージ マニフェストへのバックグラウンド タスクの定義(タスクタイプにオーディオ、スタートページを設定)とコントロールのAudioCategoryの設定とWindows.Media.MediaControlに対するイベントハンドラーの実装を行う必要があります。

バックグラウンド タスクには、タスクを動作させるためのホストプログラムが必要になります。ホストプログラムには、アプリ自身とbackgroundTaskHost.exeの2種類があります。バックグラウンド オーディオは、アプリ自身をホストとしてSystem Control Device の通知を処理するという種類のバックグラウンド タスクになります。そして、バックグラウンド タスクのSDKサンプルからも理解することができますが、backgroundTaskHost.exeがホストできるタスクは、C#、VBやC++で作成したWindowsランタイム コンポーネントだけになります。つまり、JavaScriptで記述したバックグラウンド タスクをホストするには、アプリ自身がホストするしかないということです。これが、SDKサンプルにJavaScript + C# というサンプルが含まれている理由となります。この辺りを時間切れで、WDDでお話しすることはできませんでした。

Windowsストアアプリにおける グリッドアプリケーションについて(1)

$
0
0

久し振りのエントリーになります。これから、何回かに渡ってVisual Studio 2012に含まれる Windows ストア アプリケーションのグリッド アプリケーション テンプレートを紐解いて行きます。

グリッド アプリケーションのナビゲーションは、以下に示すように3階層になっています。
Grid Application Navigation

ナビゲーションに含まるXAMLページは、以下のようになっています。

  • GroupedItemsPage.xaml
    ハブとなるページで、GridViewコントロールとListViewコントロールを持ちます。
  • GroupDetailPage.xaml
    セクションとなるページで、GridViewコントロールとListViewコントロールを持ちます。
  • ItemDetailPage.xaml
    詳細となるページで、FlipViewコントロールを持ちます。

また、各ページとLayoutAwarePageクラスの関係は、以下のようになっています。
Page UML

各ページは、LayoutAwarePageという抽象クラスを継承した構造になっています。このことから、明確になることが1つあります。それは、新しいページを作成した場合は、必ずLayoutAwarePageクラスを継承するようにした方が良いということです。具体的には、検索コントラクトで追加されるSearchResultsPage.xamlなどです。この理由は、LayoutAwarePageを説明していくことで解説をしていきます。
それでは、GroupedItemPage.xaml にある、LayoutAwrePageの定義を見ていきます。

<common:LayoutAwarePage
    x:Name="pageRoot"
    x:Class="App6.GroupedItemsPage"
    DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App6"
    xmlns:data="using:App6.Data"
    xmlns:common="using:App6.Common"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"><Page.Resources><!--このページで表示されるグループ化されたアイテムのコレクションは、グループ内のアイテムを
            仮想化できないため、完全なアイテム リストのサブセットにバインドされます
        --><CollectionViewSource
            x:Name="groupedItemsViewSource"
            Source="{Binding Groups}"
            IsSourceGrouped="true"
            ItemsPath="TopItems"
            d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/></Page.Resources>

この定義で注目したいのが、「DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"」 です。ここで指定されているDefaultViewModel が何処で定義されているかといえば、LayoutAwarePage クラスになります。その定義を以下に示します。

/// <summary>
/// <see cref="DefaultViewModel"/> 依存関係プロパティを識別します。
/// </summary>
public static readonly DependencyProperty DefaultViewModelProperty =
         DependencyProperty.Register("DefaultViewModel", 
           typeof(IObservableMap<String, Object>),
           typeof(LayoutAwarePage), null);



/// <summary>
/// <see cref="IObservableMap<String, Object>"/> の実装です。これは、
/// 単純なビュー モデルとして使用されるように設計されています。
/// </summary>
protected IObservableMap<String, Object> DefaultViewModel
{
    get
    {
       return this.GetValue(DefaultViewModelProperty)
                 as IObservableMap<String, Object>;
    }

    set
    {
       this.SetValue(DefaultViewModelProperty, value);
    }
}


この定義をみれば理解できるように、DefaultViewModelは依存プロパティとして、IObservableMap<string, Object> 型として定義されています。そして、IObservableMap<string, Object>の実装として ObservableDictionary<K, V>クラスを定義しています。この型を簡単に説明するとすれば、ディクショナリー型と同じであり、キーに文字列でデータソースの名前を指定して、値にオブジェクトのコレクションを設定して使用します。DefaultViewModelが、Dictionary<string, object>を使って実装していない理由は、ディクショナリーに変更があった場合の変更通知にあります。変更通知を実装するために、 IObservableMap<string, Object>を継承してObservableDictionary<K, V>クラス を定義しています。GroupedItemPage.xamlのCollectionViewSource定義のSource属性に「{Binding Groups}」と定義されていたことを思い出してください。ここで定義されている「Groups」と文字列が、ObservableDictionary<K, V>クラスのキーになっています。このことから、理解できることは以下のようになります。

  • DataContextで定義しているDefaultViewModel は、依存プロパティであり、LayoutAwarePageクラスで定義している。
  • CollectionViewSourceのSource属性に、IObservableMap<string, Object>型のキーを指定する。
  • ページ定義XAMLのコードビハインドで、DefaultViewModel ["キー"] に対してオブジェクトコレクションを設定する必要がある

このように理解することができれば、次にLayoutAwarePageクラスのOnNavigatedToメソッドを読み解いてみましょう。このメソッドは、Frameを使ったナビゲーションによって呼び出されるメソッドになります。

/// <summary>
/// このページがフレームに表示されるときに呼び出されます。
/// </summary>
/// <param name="e">このページにどのように到達したかを説明するイベント データ。Parameter 
/// プロパティは、表示するグループを示します。</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    // ナビゲーションを通じてキャッシュ ページに戻るときに、状態の読み込みが発生しないようにします
    if (this._pageKey != null) return;
    var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
    this._pageKey = "Page-" + this.Frame.BackStackDepth;

    if (e.NavigationMode == NavigationMode.New)
    {
        // 新しいページをナビゲーション スタックに追加するとき、次に進むナビゲーションの
        // 既存の状態をクリアします
        var nextPageKey = this._pageKey;
        int nextPageIndex = this.Frame.BackStackDepth;
        while (frameState.Remove(nextPageKey))
        {
            nextPageIndex++;
            nextPageKey = "Page-" + nextPageIndex;
        }
        // ナビゲーション パラメーターを新しいページに渡します
        this.LoadState(e.Parameter, null);
    }
    else
    {
        // ナビゲーション パラメーターおよび保存されたページの状態をページに渡します。
        // このとき、中断状態の読み込みや、キャッシュから破棄されたページの再作成と同じ対策を
        // 使用します
        this.LoadState(e.Parameter, 
            (Dictionary<String, Object>)frameState[this._pageKey]);
    }
}

/// <summary>
/// このページには、移動中に渡されるコンテンツを設定します。前のセッションからページを
/// 再作成する場合は、保存状態も指定されます。
/// </summary>
/// <param name="navigationParameter">このページが最初に要求されたときに
/// <see cref="Frame.Navigate(Type, Object)"/> に渡されたパラメーター値。
/// </param>
/// <param name="pageState">前のセッションでこのページによって保存された状態の
/// ディクショナリ。ページに初めてアクセスするとき、状態は null になります。</param>
protected virtual void LoadState(Object navigationParameter, 
                        Dictionary<String, Object> pageState)
{
}

OnNavigatedToメソッドで重要なことは、ページの状態をチェックすることとLoadStateメソッドを呼び出すことです。LoadStateメソッドの定義を見ると仮想メソッドとして定義されています。よって、LayoutAwarePageクラスを継承したクラスで実装する必要があります。GroupedItemPage.xaml.csのLoadStateメソッドを以下に示します。

/// <summary>
/// このページには、移動中に渡されるコンテンツを設定します。前のセッションからページを
/// 再作成する場合は、保存状態も指定されます。
/// </summary>
/// <param name="navigationParameter">このページが最初に要求されたときに
/// <see cref="Frame.Navigate(Type, Object)"/> に渡されたパラメーター値。
/// </param>
/// <param name="pageState">前のセッションでこのページによって保存された状態の
/// ディクショナリ。ページに初めてアクセスするとき、状態は null になります。</param>
protected override void LoadState(Object navigationParameter, 
                        Dictionary<String, Object> pageState)
{
    // TODO: 問題のドメインでサンプル データを置き換えるのに適したデータ モデルを作成します
    var sampleDataGroups = SampleDataSource.GetGroups((String)navigationParameter);
    this.DefaultViewModel["Groups"] = sampleDataGroups;
}

このコードでは、SampleDataSource.GetGroupsメソッドを使ってグループ コレクションを取得してから、DefaultViewModelにキーと値を設定することで、Groupsというキーのデータソースを設定していることを理解することができます。

  • LoadStateメソッドは、目的のページにおけるデータソースを作成する責務を持つ
  • OnNavigatedToメソッドをオーバーライドする場合は、LayoutAwarePageクラスのOnNavigatedToメソッドを呼び出すか、同等の処理を記述しなければならない

ここまでで、GroupedItemsPageにおけるXAMLとコードを使ったデータソースの設定に関することを説明しました。まだまだ、説明しきれていないことがありますので、その辺りは次回に説明する予定です。 


Windowsストアアプリにおける グリッドアプリケーションについて(2)

$
0
0

前回は、GroupedItemsPageのLoadStateメソッドまでを説明しました。今回は、GroupedItemsPageのビューに関して説明します。ビューとしては、前回に説明したようにGridViewコントロールとListViewコントロールの2つを用意しています。最初に、GridViewコントロールの定義とデザイン画面を以下に示します。

<!-- ほとんどのビューステートで使用される水平スクロール グリッド--><GridView
    x:Name="itemGridView"
    AutomationProperties.AutomationId="ItemGridView"
    AutomationProperties.Name="Grouped Items"
    Grid.RowSpan="2"
    Padding="116,137,40,46"
    ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
    ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
    SelectionMode="None"
    IsSwipeEnabled="false"
    IsItemClickEnabled="True"
    ItemClick="ItemView_ItemClick"><GridView.ItemsPanel><ItemsPanelTemplate>                        <VirtualizingStackPanel Orientation="Horizontal"/></ItemsPanelTemplate></GridView.ItemsPanel><GridView.GroupStyle><GroupStyle><GroupStyle.HeaderTemplate><DataTemplate><Grid Margin="1,0,0,6"><Button
                            AutomationProperties.Name="Group Title"
                            Click="Header_Click"
                            Style='{StaticResource TextPrimaryButtonStyle}' ><!-- 以下 省略 --></Grid></DataTemplate></GroupStyle.HeaderTemplate><GroupStyle.Panel><ItemsPanelTemplate><VariableSizedWrapGrid Orientation='Vertical' Margin='0,0,80,0'/></ItemsPanelTemplate></GroupStyle.Panel></GroupStyle></GridView.GroupStyle></GridView>


Grid Landscape

GridViewの定義で注目するのが、ItemsSourceとItemsTemplate属性の定義になります。

  • ItemSource : CollectionViewSourceの名前であるgroupedItemsViewSourceをデータソースとして指定しています。
  • ItemTemplate : StandardStyle.xamlで定義されているデータテンプレートである Standard250x250ItemTemplateを指定しています

今度は、ListViewコントロールの定義とデザインを以下に示します。

<!-- スナップの場合のみ使用される垂直スクロール リスト --><ListView
    x:Name="itemListView"
    AutomationProperties.AutomationId="ItemListView"
    AutomationProperties.Name="Grouped Items"
    Grid.Row="1"
    Visibility="Collapsed"
    Margin="0,-10,0,0"
    Padding="10,0,0,60"
    ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
    ItemTemplate="{StaticResource Standard80ItemTemplate}"
    SelectionMode="None"
    IsSwipeEnabled="false"
    IsItemClickEnabled="True"
    ItemClick="ItemView_ItemClick"><ListView.GroupStyle><GroupStyle><GroupStyle.HeaderTemplate><DataTemplate><Grid Margin="7,7,0,0"><Button
                            AutomationProperties.Name="Group Title"
                            Click="Header_Click"
                            Style='{StaticResource TextPrimaryButtonStyle}'><StackPanel Orientation='Horizontal'><TextBlock Text='{Binding Title}' Margin='3,-7,10,10' Style='{StaticResource GroupHeaderTextStyle}' /><!-- 以下省略 --/></StackPanel></Button></Grid></DataTemplate></GroupStyle.HeaderTemplate></GroupStyle></ListView.GroupStyle></ListView>


GridSnapped 

デザインを見れば理解できるようにListViewコントロールは、スナップビューとして使用するものになっています。従って、ListViewの定義で注目するのは、ItemSourceとItemTemplateとVisibility属性になります。

  • Visibility :Collapsedを指定して非表示にしています。
  • ItemSource :GridViewと同じgroupedItemsViewSourceをデータソースとして指定しています
  • ItemTemplate :StandardStyle.xamlで定義されているデータテンプレートである Standard80ItemTemplateを指定しています

GridViewコントロールとListViewコントロールに対して同じデータソースを指定して、ランドスケープとスナップにおいて表示・非表示を切り替えることで2つのレイアウトに対応しています。Visual Studio 2012 と Blend for Visual Studio のデバイスタブを使って、ランドスケープ、スナップなどを切り替える場合に重要な役割を持っているのが、VisualStateManagerになります。

<VisualStateManager.VisualStateGroups><!--表示状態には、アプリケーションのビューステートが反映されます --><VisualStateGroup x:Name="ApplicationViewStates"><VisualState x:Name="FullScreenLandscape"/><VisualState x:Name="Filled"/><!-- ページ全体では、縦方向に対して、より狭い 100 ピクセルの余白の規則を優先します --><VisualState x:Name="FullScreenPortrait"><Storyboard><!-- 省略 --></Storyboard></VisualState><!--スナップの場合、[戻る] ボタンとタイトルには異なるスタイルが使用され、
            他のすべてのビューステートで表示されるグリッドに対して一覧の表現が置き換えられます
        --><VisualState x:Name="Snapped"><Storyboard><!-- 省略 --><ObjectAnimationUsingKeyFrames 
                        Storyboard.TargetName="itemListView" 
                        Storyboard.TargetProperty="Visibility"><DiscreteObjectKeyFrame KeyTime="0" 
                        Value="Visible"/></ObjectAnimationUsingKeyFrames><ObjectAnimationUsingKeyFrames 
                        Storyboard.TargetName="itemGridView" 
                        Storyboard.TargetProperty="Visibility"><DiscreteObjectKeyFrame KeyTime="0" 
                        Value="Collapsed"/></ObjectAnimationUsingKeyFrames></Storyboard></VisualState></VisualStateGroup></VisualStateManager.VisualStateGroups>


VisualState の Snappedの定義をみれば、Visibilityを切り替えていることは明らかです。これらの表示状態に関わらずに一貫したデータを表示しているのが、同じデータソースにデータバインドして いる効果になります。実は、VisualStateManagerは Visual Studio 2012やBlend for Visual Studioのデザイナーがデバイスタブで使用しており、実行時にはユーザーコードでXAMLに定義した情報を呼び出す必要があります。この呼び出しを行っているのが、LayoutAwarePageクラスになります。LayoutAwarePageクラスのコンストラクタを以下に示します。

/// <summary>
/// <see cref="LayoutAwarePage"/> クラスの新しいインスタンスを初期化します。
/// </summary>
public LayoutAwarePage()
{
    if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) return;

    // 空の既定のビュー モデルを作成します
    this.DefaultViewModel = new ObservableDictionary<String, Object>();

    // このページがビジュアル ツリーの一部である場合、次の 2 つの変更を行います:
    // 1) アプリケーションのビューステートをページの表示状態にマップする
    // 2) キーボードおよびマウスのナビゲーション要求を処理する
    this.Loaded += (sender, e) =>
    {
        this.StartLayoutUpdates(sender, e);

        // キーボードおよびマウスのナビゲーションは、ウィンドウ全体を使用する場合のみ適用されます
        if (this.ActualHeight == Window.Current.Bounds.Height &&
            this.ActualWidth == Window.Current.Bounds.Width)
        {
            // ウィンドウで直接待機するため、フォーカスは不要です
            Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated +=
                CoreDispatcher_AcceleratorKeyActivated;
            Window.Current.CoreWindow.PointerPressed +=
                this.CoreWindow_PointerPressed;
        }
    };

    // ページが表示されない場合、同じ変更を元に戻します
    this.Unloaded += (sender, e) =>
    {
        this.StopLayoutUpdates(sender, e);
        Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -=
            CoreDispatcher_AcceleratorKeyActivated;
        Window.Current.CoreWindow.PointerPressed -=
            this.CoreWindow_PointerPressed;
    };
}


コンストラクタで Loadedイベントハンドラで StartLayoutUpdatesメソッドを呼び出しているのが確認できます。StartLayoutUpdatesメソッドを以下に示します。

/// <summary>
/// イベント ハンドラーとして呼び出されます。これは通常、ページ内の <see cref="Control"/> の
/// <see cref="FrameworkElement.Loaded"/> イベントで呼び出され、送信元が
/// アプリケーションのビューステートの変更に対応する表示状態管理の変更を受信開始する必要があることを
/// 示します。
/// </summary>
/// <param name="sender">ビューステートに対応する表示状態管理をサポートする 
/// <see cref="Control"/> のインスタンス。</param>
/// <param name="e">要求がどのように行われたかを説明するイベント データ。</param>
/// <remarks>現在のビューステートは、レイアウトの更新が要求されると、
/// 対応する表示状態を設定するためすぐに使用されます。対応する 
/// <see cref="FrameworkElement.Unloaded"/> イベント ハンドラーを
/// <see cref="StopLayoutUpdates"/> に接続しておくことを強くお勧めします。
/// <see cref="LayoutAwarePage"/> のインスタンスは、Loaded および Unloaded イベントでこれらのハンドラーを自動的に
/// 呼び出します。</remarks>
/// <seealso cref="DetermineVisualState"/>
/// <seealso cref="InvalidateVisualState"/>
public void StartLayoutUpdates(object sender, RoutedEventArgs e)
{
    var control = sender as Control;
    if (control == null) return;
    if (this._layoutAwareControls == null)
    {
        // 更新の対象となるコントロールがある場合、ビューステートの変更の待機を開始します
        Window.Current.SizeChanged += this.WindowSizeChanged;
        this._layoutAwareControls = new List<Control>();
    }
    this._layoutAwareControls.Add(control);

    // コントロールの最初の表示状態を設定します
    VisualStateManager.GoToState(
           control, 
           DetermineVisualState(ApplicationView.Value), 
           false);
}

private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs e)
{
    this.InvalidateVisualState();
}


/// <summary>
/// 適切な表示状態を使用した表示状態の変更を待機しているすべてのコントロールを更新し
/// ます。
/// </summary>
/// <remarks>
/// 通常、ビューステートが変更されていない場合でも、別の値が返される可能性がある事を知らせるために
/// <see cref="DetermineVisualState"/> をオーバーライドすることで
/// 使用されます。
/// </remarks>
public void InvalidateVisualState()
{
    if (this._layoutAwareControls != null)
    {
        string visualState = DetermineVisualState(ApplicationView.Value);
        foreach (var layoutAwareControl in this._layoutAwareControls)
        {
            VisualStateManager.GoToState(
                   layoutAwareControl, visualState, false);
        }
    }
}


StartLayoutUpdatesメソッドを見ることで、VisualStateManager.GoToStateメソッドを呼び出しているのが、このメソッドとWindowSizeChangedイベントハンドラだということが理解できます。ApplicationViewStateが、現在のビューの状態(Snappedなど)を表していま��。コメントにも記載されていますが、VisualStateManagerで切り替えるビューステートを変更したい場合は、DetermineVisualStateメソッドをオーバーライドして対象のビューステート名(VisualState のx:Name属性)を返すようにすれば良いということになります。ここまでの動きから、VisualStateManagerの動作をまとめると、以下のようになります。

  • デバイスタブは、XAML内に定義されたVisualStateManagerを使用する
  • 実行時は、自分でVisualStateManager.GoToStateメソッドを呼び出す必要がある。
  • LayoutAwarePagaeクラスは、Window.SizeChangedイベントハンドラーでVisualStateManager.GoToStateメソッドを呼び出す 
    (ポートレイトやスナップなどに切り替えると、Window.SizeChangedイベントが発生します)

このような仕組みで表示状態を切り替えていることから、グリッドアプリケーション テンプレートで作成したプロジェクトに新しいページ(xaml)を追加する時に「必ずLayoutAwarePageを継承してください」という説明をしていました。 当然のこととして、ここまでのビューの切替などの仕組みを理解して、自分でビューの切替を実装するのであれば、LayoutAwarePageクラスを継承する必要はありません。実際には、継承することを強くお勧めします。この理由は、次回以降で説明します。

最後に、VisualStateMamangerを使用したコントロールのカスタマイズに関して、簡単に説明します。VisualStateManagerは、最初にSilverlightに導入されて、WPF 4.0でデスクトップ環境にも導入されました。WPF4.0などのドキュメントを読めば理解することができますが、コントロールの外観をマウスポインターが入った時などをxamlだけでカスタマイズすることができます。コントロールの外観をカスタマイズするには、ControlTemplateを定義して、VisualStateManagerを定義する必要があります。またControlTemplate属性とは、Controlが持つプロパティなので、Controlを継承したコントロール(Buttonなど)がVisualStateManagerを使ったノンプログラミングの外観カスタマイズを行うことができます。これ以外のコントロール(たとえば、Gridなどのレイアウト系コントロール)は、何らかのコードを書かないと外観カスタマイズを行うことはできませんので、注意する必要があります。

グリッド アプリケーションの解説ドキュメント

$
0
0

Blogの更新もままならない状態でしたが、何とか更新していこうと考えています。もうじき、Windows 8.1 が一般公開される時点で何なんですが、Windows 8 のストア アプリのグリッド アプリケーション テンプレートのドキュメントを公開します。このドキュメントは、様々な理由があって作ったものの日の目を見ない状態になっていたものです。

Visual Studioが提供するプロジェクト テンプレートに賛否があるのは知っていますが、用意されているテンプレートの仕組みを知っておくとは有益だと私は考えています。テンプレートで提供されるSuspensionManagerクラスなどは、Visual Studio 2013でも利用されていますし、異なるのはLayoutAwareクラスが無くなってNavigationHelperクラスになったことが大きなポイントだと思います。でも、NavigationHelperクラスに記述されているロジックの大半は、LayoutAwareクラスと同じですから、この意味でもVisual Studio 2012のグリッド アプリケーションの構造を知っておくことは役に立つはずです。このように考えていますので、このエントリーにドキュメントを添付します。

執筆協力した書籍について

$
0
0

色々と忙しかった理由ですが、一番は書籍の執筆協力でした。具体的には、プログラミング Windows 第6版 と プログラミング .NET Framework 第4版に対して、Windows 8.1(.NET Framework 4.5.1)との差異を埋めるべく、作業をしていました。

pgm_windows6_Jpgm_windows6_Gpgm_netfw4

プログラミングWindowsでは、日本語版だけの特典としてWindows 8.1 / Visual Studio 2013 ベースのサンプルコードも用意しています。下巻が発売されるのに合わせて、日経BPさんのサイトからダウンロードできるようになる予定です。

プログラミング .NET Framework(原書では、CLR via C#)は、CLRなどを知りたい場合の定番ともいえる書籍です。現在は予約中ですが、まもなく発売される予定です。この書籍の執筆協力の依頼があったのが、6月の中旬でそれから訳3000ページを精査しながら、Windows 8.1との差分を脚注として起こしたり、追加原稿を起こしたりしていました。

また、HTML/JavaScriptでWindows ストア アプリを開発する場合に有益となるプログラミングWindowsストアアプリも絶賛発売中です。

pgm_StoreApli

この書籍は、マイクロソフト本社のプログラム マネージャが執筆した書籍の翻訳となります。この書籍も、お勧めの一冊となりますので、宜しければ書店で是非とも手に取ってください。

Windows 8.1 のグリッド アプリケーションについて

$
0
0

Visual Studio 2013で提供する Windows ストア アプリ用のプロジェクトテンプレートには、次の4種類があります。

  • 新しいアプリケーション
  • グ��ッド アプリケーション
  • ハブ アプリ
  • 分割アプリケーション

Visual Studio 2012のプロジェクトテンプレートと比較すると、ハブ アプリが新しく追加されています。ハブ アプリは、Windows 8.1 で追加された Hub コントロールを使用したアプリの作り方を示すものです。表面的には、グリッド アプリケーションと分割アプリケーションが提供する機能としては、スナップ時のレイアウトを別にすればVisual Studio 2012のものと同じと考えることができます。でも、内部的には、大きな変更がなされています。グリッド アプリケーションで作成されるページの構造を以下に示します(この構造は、ハブ アプリや分割アプリケーションでも同じです)。

GridApp Class

クラス図では、Pageクラスの継承関係と相互作用だけに焦点を当てています。ObservalDictionaryはIObservableMap<string, object> を継承していますし、NavigationHelperはDependencyObjectを継承しています(クラス図では、ステレオタイプで表現しています)。各ページが、Pageクラスを継承しているので、Visual Studio 2012の時のようにビルドしなくてもVisual Studioのデザインビューを表示することができるようになっています。プロジェクトのCommonフォルダーに作成されているコードファイルは、次のようになります。

ファイル名説 明
NavigationHelper.csVisual Studio 2012のLayoutAwareクラスで定義されていた機能が含まれています。
ObservableCollection.csVisual Studio 2012のLayoutAwareクラスのインナークラスとして定義されていたクラスです。
RelayCommand.cs新しく追加されたコードで、XAMLのコマンドパターンで使用します。具体的には、戻るボタンで使用しています。
SuspensionManager.csVisual Studio 2012 と同じものです。

最初にSuspensionManagerクラスは特に変更はありませんので、App.xaml.csのOnLaunchedイベントでRegisterFrameメソッドを使ってFrameオブジェクトのインスタンスを登録するのも同じです。RegisterFrameメソッドで登録しないと、ナビゲーション履歴やセッション情報が一時停止時に保存されませんのでご注意ください(言い換えれば、OnSuspendingイベントでSuspensionManagerのSaveAsyncメソッドを呼び出すということです)。

NavigationHelperクラスの使用方法

次に各ページで、NavigationHelperクラスをどのように使用しているかを説明します。ちなみに、NavigationHelperの先頭にコメントで、使用方法が記述されています。

  • フィールド定義
    navigationHelper と defaultViewModel を定義しています。
  • プロパティ定義
    NavigationHelper と DefaultViewModel を定義しています。NavigationHelperプロパティを使って、戻るボタンにコマンド パターンを使用している点に注意してください。
  • コンストラクタ
    NavigationHelperクラスのインスタンスを作成して、NavigationHelper.LoadStateデリゲートの登録を行います。ページ毎のセッションを保存する場合は、NavigationHelper.SaveStateデリゲートの登録を行う必要があります。
  • イベントハンドラー定義
    PageクラスのOnNavigatedFrom と OnNavigatedToイベントハンドラーで、NavigationHelperの OnNavigatedTo と OnNavigatedFromメソッドを呼び出します。このメソッド呼び出しを記述しないと、LoadStateとSaveStateデリゲートが呼び出されなくなるので必ず記述します。
  • デリゲート定義
    NavigationHelperのLoadState と SaveStateデリゲートに登録したメソッドを定義します。LoadStateデリゲートは、ビューモデルなどを使ってデータソースの初期化を行ったり、ページ毎のセッションを復元したりします。SaveStateデリゲートは、ページ毎のセッションを保存するロジックを記述します。

SaveStateデリゲートは、SuspensionManagerクラスにFrameオブジェクトを登録していないと呼び出されないことに注意してください。

NavigationHelperクラス

今度は、NavigationHelperクラスがどのようになっているかの説明を示します。

メソッド、プロパティ名説 明
コンストラクタキーボードとマウスを使ったナビゲーションをサポートするためのイベントハンドラーを登録しています。
CoreDispatcher_AcceleratorKeyActivatedキーボードを使ったナビゲーションを実装します。具体的には、ALTキーと矢印キーの組み合わせで、ページを進めたり戻ったりします。
CoreWindow_PointerPressedマウスのボタンを使ったナビゲーションを実装します。具体的には、マウスによってブラウザーの進むボタンや戻るボタンを使ったナビゲーションです。
GoBackCommandプロパティRelayCommandクラスを使用して、XAMLのコマンドパターンで使用するコマンドオブジェクトのインスタンスを返します。このプロパティが、戻るボタンで実行されるコマンドとなります。
GoForwardCommandプロパティRelayCommandクラスを使用して、XAMLのコマンドパターンで使用するコマンドオブジェクトのインスタンスを返します。このプロパティが、ページを進めるときに呼び出されるコマンドとなります。
CanGoBack
CanGoForward
GoBack
GoForward
Frameオブジェクトを使ったナビゲーションのためのメソッドです。
OnNavigatedTo
OnNavigatedFrom
プロセス継続時間管理として、Frameオブジェクトを使ったナビゲーションに応じてページ毎のセッションを保存したり、LoadStateデリゲートを呼び出すためのメソッドです。

NavigationHelperクラスの内容を見れば、Visual Studio 2012のLayoutAwareクラスのナビゲーション サポートとプロセス継続時間管理の機能を持っていることを理解できます。異なるのは、継承関係を使用しなくなったことと、XAMLのコマンドパターンを使用して戻るボタンのコマンドオブジェクトを用意していることになります。そして、NavigationHelperクラスがDependencyObjectを継承している理由は、コマンドオブジェクトをXAMLのデータバインディングで記述できるようにするためになります。

SampleDataSourceクラス

SampleDataSourceクラスも、Visual Studio 2013で大きく変更されたクラスになります。プロジェクトのDataフォルダーに作成されているファイルを示します。

ファイル名説明
SanpmeDataSource,csデータソースに必要となるクラスを定義するファイルです。
SampleDataSource.jsonデータソースのサンプルデータを定義するJSON形式のファイルです。

サンプルデータソースの構造を示します。
SampleDataSourceClass

Visual Studio 2012と比較すると、次に示すような箇所が変更になっています。

  • BindableBase クラスが削除された。
  • SampleDataGroup クラスとSampleDataItemの循環参照が無くなった。
  • SampleDataSourceクラスは、大幅な見直しが行われている。
    シングルトンパターンで実装されているのは一緒ですが、サンプルデータをjson形式のファイルから読み込んだりと提供するメソッドが、大幅に変更されています。

ビューモデルを作り易くするという観点で言えば、BindableBaseクラスが提供されなくなったのが残念だと私は考えます。もちろん、自前でINotifyPropertyChangedインターフェースを実装しやすくする仕組みを用意すれば構わないのです。特に、XAMLのバインディングを考えた場合に変更通知を実装するのは必須ですから、ObservableCollectionだけでなくINotifyPropertyChangedインターフェースも前提にビューモデルを考えなければなりません。

ビュー、データテンプレート、etc

Visual Studio 2013のプロジェクトが、Visual Studio 2012のプロジェクトと大きく変化した箇所として Commonフォルダーで提供していたStandardStyles.xamlというスタイル定義の廃止があります。StandardStyles.xamlでは、テキストのスタイルに始まり、アプリバーの各種スタイル、GridViewやListViewのデータテンプレートなど、非常に多くのスタイルが定義されていました。これらのスタイル定義が、Visual Studio 2013では無くなっているのです。無くなったことで、どのように変更が必要かを記載します。

  • アプリバーは、AppBarButton、AppBarToggleButtonクラスのIconプロパティとLabelプロパティで簡単に設定できるようになった。
  • データテンプレートは、各アプリごとのページリソースとして定義されるようになった。データテンプレートの編集をページ毎に行う必要がある。
  • テキストのスタイルは、generic.xaml(Windows Kitで提供される)で定義されるHeaderTextBlockStyle、SubheaderTextBlockStyleなどを使用するようになった。個別にスタイルを定義したい場合は、アプリリソースなどで定義する必要がある。
  • 戻るボタンのスタイルが無くなり、AppBarButtonを利用するようになった。

ビューの切り替えについては、分割アプリケーションを除けばVisualStateManagerを使用した方法を利用しなくなりました。これは、新しいウィンドウ化モードのサポートに伴って固定サイズのスナップビュー(横幅が320ピクセル)が廃止されたことに伴うものです。もちろん、コンテンツによってはビューの切り替えをサポートした方が良いので、この場合は分割アプリケーションのVisualStateManagerの使用方法が参考になると思います。

Visual Studioが提供するプロジェクト テンプレートを使用すると同じデザインのアプリばかりになると指摘する方もいらっしゃいますが、私はそのようには考えておりません。ページナビゲーションであったり、基本的なページデザイン(余白であったり、フォントサイズであったり)などを学習することに役立つからです。何よりも、Windows ストア アプリはコンテンツ中心のアプリですから、デザインを自分で定義するのは当たり前のことであり、デザインを変更しないから同じ見た目のアプリになるだけのことが批判されているのだと考えるからです。要は、デザインを自分のアプリらしく設計しましょうというのが、私の主張です。事実、マイクロソフトが提供している開発体験テンプレートも同じプロジェクト テンプレートを使用していても、デザインは様々なものになっていますよね。つまり、アプリが提供するコンテンツを一番良いと思われる方法で提供するデザインにするところまでが、アプリの一部だということなのです。

ここで解説したプロジェクトの構造は、Visual Studio 2013ではグリッド アプリケーション、分割アプリケーション、ハブ アプリケーションに当てはまりますし、アイテム テンプレートのほとんどにも当てはまりますので、理解しておくことでVisual Studioのアイテム テンプレートを有効活用するのに役立つはずです。このエントリーで解説しない機能については、グリッドアプリケーションのドキュメントで公開したドキュメントをご参照ください。Visual Studio 2012ベースのドキュメントですが、きっと役に立つはずです。

Windows 8 アプリの Windows 8.1 へのマイグレーションについて

$
0
0

Windows 8 のストア アプリのプロジェクトを Windows 8.1 用のプロジェクトへ移行する方法は、物江さんのBlogにまとまっていますので、私の方は変更された API に焦点を絞って解説します。Visual Studio 2013を使ってプロジェクトの再ターゲットを行うと、マイグレーションガイドのドキュメントのダイアログが表示されます。このドキュメントは、デベロッパーセンターでも公開されていて、このドキュメントが変更されたAPIのガイドにもなっています。残念ながら英語ドキュメントのままなので、再ターゲットを行われる場合は、一度は必ず目を通すようにしてください。プロジェクトを再ターゲットした後に行うべき作業は、大きくは3種類があります。

  • 変更されたAPIに対する対応
  • 新しいウィンドウ化モードへの対応(320ピクセルのスナップビューの廃止)
  • 追加されたAPIやコントロールに対する対応の検討と実施

変更されたAPIに対応する作業において、XAML/C#、VB、C++ではVisual Studio 2013での支援機能があります。具体的には、ビルドすると変更されたAPIに対してワーニングを出力してくれます。Visual Studio 2013 RCで私が確認している限りは、完璧ではないようですが、かなりの作業で有効に機能しています。一方で、HTML/JavaScriptの場合は、このような支援機能がないのでマイグレーションガイドのドキュメントと照らし合わせて、作業を実施する必要があります。WinJSで、廃止予定のAPIを以下に記載します。

SettingsFlyout.width新しいウィンドウ化モードに合わせて変更します
ListLayout.backdropColor.win-backdrop {background-color:transparent;}へ置き換え
ListLayout.disableBackdrop.win-backdrop {background-color:transparent;}へ置き換え
GridLayout.backdropColor.win-backdrop {background-color: <color>;}へ置き換え
GridLayout.disableBackdrop.win-backdrop {background-color:transparent;}へ置き換え
GridLayout.maxRows.win-surface {height: 200px;}のようにheightを使用します。
GridLayout.groupInfoCellspanningLayout.groupInfo へ置き換え
GridLayout.itemInfoCellspanningLayout.itemInfoへ置き換え
ListView.loadingBehaviorデザインに応じて対応します
ListView.automaticallyLoadPagesデザインに応じて対応します
ListView.loadMorePagesデザインに応じて対応します
ListView.pagesToLoadデザインに応じて対応します
ListView.pagesToLoadThresholdデザインに応じて対応します

 

Windows Runtimeで大きく変更になったのが、ApplicationViewクラスとDisplayPropertiesクラスになります。基本的な考え方は、それぞれのクラスは静的メンバーを提供していますが、Windows 8.1からインスタンス メンバーを提供するようになっています。このために、GetForCurrentView静的メソッドによってインスタンスを取得してから、メンバーへアクセスする形式となります。また、DisplayPropertiesクラスは廃止となりDisplayInformationクラスが同等の機能を提供します。これ以外に、よく使われるものとしてはMediaControlがSystemMediaTransportControlsへ置き換えとなっています。全てではありませんが、良く使うと思われるAPIの変更箇所を次に示します。

API名説明
ApplicationViewGetForCurrentViewメソッドでインスタンスを取得して、メンバーへアクセス
Valueプロパティ、TryUnsnapメソッドの廃止
Orientation、IsFullFullScreenプロパティを利用します。
DisplayPropertiesDispalkyInformationクラスへ置き換え。
GetForCurrentViewメソッドでインスタンスを取得して、メンバーへアクセス
MediaControlSystemMediaTransportControlsへ置き換え。
Geocoordinate.Latitude
Geocoordinate.Longitude
Pointプロパティを使ってGeopointを取得して、Positionプロパティを使って
アクセスします。

もちろん、これ以外にも変更されたAPIはあります。詳細を知りたい場合は、マイグレーションガイドのドキュメントを参照するようにして下さい。

Viewing all 163 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>