次の方法で共有


Windows Phone Silverlight から UWP へのケース スタディ: Bookstore2

このケース スタディは、「Bookstore1」で説明されている情報に基づいて作成されています。ここでは、最初に、グループ化されたデータを LongListSelector に表示する Windows Phone Silverlight アプリについて取り上げます。 ビュー モデルでは、Author クラスの各インスタンスは、該当する著者によって書かれた書籍のグループを表します。LongListSelector では、著者ごとにグループ化された書籍の一覧を表示したり、縮小して著者のジャンプ リストを表示したりすることができます。 ジャンプ リストを使うと、書籍の一覧をスクロールするよりもすばやく移動することができます。 アプリを Windows 10 ユニバーサル Windows プラットフォーム (UWP) アプリに移植する手順について説明します。

Visual Studio で Bookstore2Universal_10 を開こうとすると、"Visual Studio 更新プログラムが必要" というメッセージが表示される場合は、「TargetPlatformVersion」の手順に従って、ターゲット プラットフォームのバージョン番号を設定してください。

ダウンロード

Bookstore2WPSL8 Windows Phone Silverlight アプリをダウンロード

Bookstore2Universal_10 Windows 10 アプリをダウンロードします

Windows Phone Silverlight アプリ

次の図は、Bookstore2WPSL8 (移植するアプリ) の外観を示しています。 これは、著者別にグループ化された書籍の垂直スクロール LongListSelector です。 ジャンプ リストに縮小して、そこから任意のグループに戻ることができます。 このアプリには、グループ化されたデータ ソースを提供するビュー モデルと、そのビュー モデルにバインドするユーザー インターフェイスという 2 つの主要な要素があります。 ご覧のとおり、これらの 2 つの要素は、Windows Phone Silverlight テクノロジから ユニバーサル Windows プラットフォーム (UWP) に簡単に移植できます。

bookstore2wpsl8 の外観

Windows 10 プロジェクトへの移植

Visual Studio で新しいプロジェクトを作成し、Bookstore2WPSL8 からファイルをコピーして、コピーしたファイルを新しいプロジェクトに含めるのは簡単な作業です。 まず、新しい空のアプリケーション (Windows ユニバーサル) プロジェクトを作成します。 そして、"Bookstore2Universal_10" という名前を付けます。 Bookstore2WPSL8 から Bookstore2Universal_10 にコピーするファイルを以下に示します。

  • ブック カバーの画像の PNG ファイルを含むフォルダー (フォルダーは \Assets\CoverImages) をコピーします。 フォルダーをコピーした後、ソリューション エクスプローラーで、[すべてのファイル表示] がオンになっていることを確認。 コピーしたフォルダーを右クリックし、[ プロジェクト内を含む] をクリックします。 このコマンドは、プロジェクト内のファイルまたはフォルダーを "含む" ことを意味します。 ファイルまたはフォルダーをコピーするたびに、ソリューション エクスプローラーRefresh をクリックし、プロジェクトにファイルまたはフォルダーを含めます。 変換先で置き換えるファイルに対してこれを行う必要はありません。
  • ビュー モデル ソース ファイルを含むフォルダー (フォルダーは \ViewModel) をコピーします。
  • MainPage.xaml をコピーし、コピー先のファイルを置き換えます。

Windows 10 プロジェクトで Visual Studio によって生成された App.xaml とApp.xaml.csを保持できます。

コピーしたソース コードとマークアップ ファイルを編集し、Bookstore2WPSL8 名前空間への参照をすべて、Bookstore2Universal_10 に変更します。 これを行う簡単な方法は、 ファイル内の配置 機能を使用することです。 ビュー モデル ソース ファイルの命令型コードでは、これらの移植の変更が必要です。

  • System.ComponentModel.DesignerPropertiesDesignMode に変更し、Resolve コマンドを使用します。 IsInDesignTool プロパティを削除し、IntelliSense を使用して正しいプロパティ名 (DesignModeEnabled) を追加します。
  • ImageSourceResolve コマンドを使用します。
  • BitmapImageResolve コマンドを使用します。
  • using System.Windows.Media;using System.Windows.Media.Imaging;を削除します。
  • Bookstore2Universal_10.BookstoreViewModel.AppName プロパティによって返された値を "BOOKSTORE2WPSL8" から "BOOKSTORE2UNIVERSAL" に変更します。
  • Bookstore1 の場合と同様に、BookSku.CoverImage プロパティの実装を更新します (「ビュー モデルへのイメージのバインドを参照してください)。

MainPage.xaml では、これらの初期移植の変更が必要です。

  • phone:PhoneApplicationPagePageに変更します (プロパティ要素構文の出現箇所を含む)。
  • phoneshell名前空間プレフィックス宣言を削除します。
  • 残りの名前空間プレフィックス宣言で "clr-namespace" を "using" に変更します。
  • SupportedOrientations="Portrait"を削除してOrientation="Portrait"し、新しいプロジェクトのアプリ パッケージ マニフェストでPortraitを構成します。
  • shell:SystemTray.IsVisible="True"を削除します。
  • ジャンプ リスト項目コンバーターの型 (リソースとしてマークアップに存在します) は、 Windows.UI.Xaml.Controls.Primitives 名前空間に移動しました。 そのため、名前空間のプレフィックス宣言 Windows_UI_Xaml_Controls_Primitives を追加し、これを Windows.UI.Xaml.Controls.Primitives にマップします。 ジャンプ リスト 項目コンバーター リソースで、プレフィックスを phone: から Windows_UI_Xaml_Controls_Primitives: に変更します。
  • Bookstore1 の場合と同様にPhoneTextExtraLargeStyleTextBlock スタイルへのすべての参照をSubtitleTextBlockStyleへの参照に置き換え、PhoneTextSubtleStyleSubtitleTextBlockStyleに置き換え、PhoneTextNormalStyleCaptionTextBlockStyleに置き換え、PhoneTextTitle1StyleHeaderTextBlockStyleに置き換えます。
  • BookTemplateには 1 つの例外があります。 2 番目の TextBlock のスタイルは CaptionTextBlockStyleを参照する必要があります。
  • AuthorGroupHeaderTemplate内のTextBlockから FontFamily 属性を削除し、PhoneAccentBrushではなくSystemControlBackgroundAccentBrushを参照するようにBorderの背景を設定します。
  • 表示ピクセルに関連する変更があるためマークアップを通過し、固定サイズディメンション (余白、幅、高さなど) に 0.8 を乗算します。

LongListSelector の置き換え

LongListSelectorSemanticZoom コントロールに置き換えると、いくつかの手順が実行されるので、その上で始めましょう。 LongListSelector はグループ化されたデータ ソースに直接バインドされますが、SemanticZoom には ListView または GridView コントロールが含まれており、CollectionViewSource アダプターを介してデータに間接的にバインドされます。 CollectionViewSourceは、マークアップ内にリソースとして存在する必要があるため、まず、<Page.Resources>内の MainPage.xaml のマークアップに追加します。

    <CollectionViewSource
        x:Name="AuthorHasACollectionOfBookSku"
        Source="{Binding Authors}"
        IsSourceGrouped="true"/>

LongListSelector.ItemsSource のバインドは CollectionViewSource.Source の値になり、LongListSelector.IsGroupingEnabledCollectionViewSource.IsSourceGrouped になります。 CollectionViewSourceには、バインドできるように名前が付けられます (注意: キーではありません。

次に、 phone:LongListSelector をこのマークアップに置き換えます。これにより、使用する暫定的な SemanticZoom が提供されます。

    <SemanticZoom>
        <SemanticZoom.ZoomedInView>
            <ListView
                ItemsSource="{Binding Source={StaticResource AuthorHasACollectionOfBookSku}}"
                ItemTemplate="{StaticResource BookTemplate}">
                <ListView.GroupStyle>
                    <GroupStyle
                        HeaderTemplate="{StaticResource AuthorGroupHeaderTemplate}"
                        HidesIfEmpty="True"/>
                </ListView.GroupStyle>
            </ListView>
        </SemanticZoom.ZoomedInView>
        <SemanticZoom.ZoomedOutView>
            <ListView
                ItemsSource="{Binding CollectionGroups, Source={StaticResource AuthorHasACollectionOfBookSku}}"
                ItemTemplate="{StaticResource ZoomedOutAuthorTemplate}"/>
        </SemanticZoom.ZoomedOutView>
    </SemanticZoom>

フラット リストモードとジャンプ リスト モードの LongListSelector 概念は、ズームインビューとズームアウトビューの SemanticZoom の概念でそれぞれ回答されます。 拡大表示ビューはプロパティであり、そのプロパティを ListView のインスタンスに設定します。 この場合、縮小表示ビューも ListView に設定され、 ListView コントロールは両方とも CollectionViewSource にバインドされます。 拡大表示ビューでは、LongListSelector と同じ項目テンプレート、グループ ヘッダー テンプレート、HideEmptyGroups 設定 (現在は HidesIfEmpty) が使用のフラット リストと同じです。 また、縮小表示ビューでは、 LongListSelector のジャンプ リスト スタイル (AuthorNameJumpListStyle) 内のテンプレートとよく似た項目テンプレートが使用されます。 また、縮小表示ビューは、 CollectionViewSource CollectionGroups という名前の特別なプロパティにバインドされることに注意してください。これは、項目ではなくグループを含むコレクションです。

少なくともすべてではなく、 AuthorNameJumpListStyleは必要なくなりました。 縮小表示で必要なのは、グループ (このアプリの作成者) のデータ テンプレートのみです。 そのため、 AuthorNameJumpListStyle スタイルを削除し、このデータ テンプレートに置き換えます。

   <DataTemplate x:Key="ZoomedOutAuthorTemplate">
        <Border Margin="9.6,0.8" Background="{Binding Converter={StaticResource JumpListItemBackgroundConverter}}">
            <TextBlock Margin="9.6,0,9.6,4.8" Text="{Binding Group.Name}" Style="{StaticResource SubtitleTextBlockStyle}"
            Foreground="{Binding Converter={StaticResource JumpListItemForegroundConverter}}" VerticalAlignment="Bottom"/>
        </Border>
    </DataTemplate>

このデータ テンプレートのデータ コンテキストは項目ではなくグループであるため、 Group という名前の特別なプロパティにバインドされることに注意してください。

これで、アプリをビルドして実行できます。 モバイル エミュレーターでの外観を次に示します。

最初のソース コードの変更を伴うモバイル上の UWP アプリ

ビュー モデルとズームインビューとズームアウトビューは正常に連携していますが、問題の 1 つは、もう少しスタイル設定とテンプレート処理を行う必要があるということです。 たとえば、適切なスタイルとブラシがまだ使われていないため、縮小表示のためにクリックできるグループ ヘッダーにはテキストが表示されていません。デスクトップ デバイスでアプリを実行する場合、2 つ目の問題があります。ウィンドウがモバイル デバイスの画面よりもずっとサイズが大きい可能性がある大型のデバイスで、アプリのインターフェイスが最適なエクスペリエンスを提供し、領域を有効に活用できるように調整されていません。 そのため、次のいくつかのセクション (Initial のスタイル設定とテンプレートAdaptive UIFinal style) では、これらの問題を解決します。

初期のスタイル設定とテンプレート

グループ ヘッダーをうまく配置するには、AuthorGroupHeaderTemplateを編集し、Border"0,0,0,9.6"Marginを設定します。

書籍の項目をうまく配置するには、BookTemplateを編集し、Marginを両方のTextBlock"9.6,0"に設定します。

アプリ名とページ タイトルをもう少し適切にレイアウトするには、TitlePanel内で、値を "7.2,0,0,0" に設定して、2 番目の TextBlock 上の Margin を削除します。 また、 TitlePanel 自体で、余白を 0 に設定します (または、任意の値が適切に表示されます)

LayoutRootの背景を"{ThemeResource ApplicationPageBackgroundThemeBrush}"に変更します。

アダプティブ UI

電話アプリを使い始めたので、移植されたアプリの UI レイアウトが、プロセスのこの段階で小さなデバイスや狭いウィンドウに対してのみ意味を持つのは驚くような作業です。 しかし、アプリが広いウィンドウ (大画面のデバイスでのみ可能) で実行されている場合に、UI レイアウト自体を適応させ、スペースをより適切に使用し、アプリのウィンドウが狭い (小さなデバイスで発生する) 現在の UI のみを使用したいと考えています。 また、大規模なデバイスでも発生する可能性があります)。

これを実現するには、アダプティブ Visual State Manager 機能を使用できます。 現在使用しているテンプレートを使用して、既定で UI が狭い状態でレイアウトされるように、ビジュアル要素のプロパティを設定します。 次に、アプリのウィンドウが特定のサイズ ( 効果のないピクセル単位で測定) より広いか等しい場合を検出し、これに応じてビジュアル要素のプロパティを変更して、より大きく広いレイアウトを取得します。 これらのプロパティの変更を視覚的な状態に配置し、アダプティブ トリガーを使用して、有効ピクセル単位のウィンドウの幅に応じて、その表示状態を継続的に監視して適用するかどうかを判断します。 この場合、ウィンドウの幅でトリガーされますが、ウィンドウの高さでもトリガーできます。

最小ウィンドウ幅 548 epx は、幅の広いレイアウトを表示する最小のデバイスのサイズであるため、このユース ケースに適しています。 通常、電話は 548 epx より小さいので、そのような小さなデバイスでは、既定の狭いレイアウトのままです。 PC では、ウィンドウは既定でワイド状態に切り替えるのに十分な幅で起動され、250 x 250 サイズの項目が表示されます。 そこから、250 x 250 項目の少なくとも 2 つの列を表示するのに十分な幅のウィンドウをドラッグできます。 それより狭いとトリガーが非アクティブ化され、ワイドな表示状態が削除され、既定の狭いレイアウトが有効になります。

アダプティブ Visual State Manager の部分に取り組む前に、まずワイド状態を設計する必要があります。つまり、マークアップにいくつかの新しいビジュアル要素とテンプレートを追加します。 これらの手順では、その方法について説明します。 ビジュアル要素とテンプレートの名前付け規則を使用して、ワイド状態の要素またはテンプレートの名前に "wide" という単語を含めます。 要素またはテンプレートに "wide" という単語が含まれていない場合は、狭い状態 (既定の状態)、プロパティ値がページ内のビジュアル要素のローカル値として設定されていると仮定できます。 マークアップの実際の表示状態を使用して設定されるのは、ワイド状態のプロパティ値のみです。

  • マークアップの SemanticZoom コントロールのコピーを作成し、コピーに x:Name="narrowSeZo" を設定します。 元の場合は、 x:Name="wideSeZo" を設定し、幅の広いものが既定で表示されないように Visibility="Collapsed" も設定します。
  • wideSeZoで、ListView を拡大表示ビューと縮小表示ビューの両方でGridViewに変更します。
  • これら 3 つのリソース AuthorGroupHeaderTemplateZoomedOutAuthorTemplateBookTemplate のコピーを作成し、 Wide という単語をコピーのキーに追加します。 また、これらの新しいリソースのキーを参照するように、 wideSeZo を更新します。
  • AuthorGroupHeaderTemplateWideの内容を<TextBlock Style="{StaticResource SubheaderTextBlockStyle}" Text="{Binding Name}"/>に置き換えます。
  • ZoomedOutAuthorTemplateWide の内容を次のコードで置き換えます。
    <Grid HorizontalAlignment="Left" Width="250" Height="250" >
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}"/>
        <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
          <TextBlock Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}"
              Style="{StaticResource SubtitleTextBlockStyle}"
            Height="80" Margin="15,0" Text="{Binding Group.Name}"/>
        </StackPanel>
    </Grid>
  • BookTemplateWide の内容を次のコードで置き換えます。
    <Grid HorizontalAlignment="Left" Width="250" Height="250">
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}"/>
        <Image Source="{Binding CoverImage}" Stretch="UniformToFill"/>
        <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
            <TextBlock Style="{StaticResource SubtitleTextBlockStyle}"
                Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}"
                TextWrapping="NoWrap" TextTrimming="CharacterEllipsis"
                Margin="12,0,24,0" Text="{Binding Title}"/>
            <TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="{Binding Author.Name}"
                Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" TextWrapping="NoWrap"
                TextTrimming="CharacterEllipsis" Margin="12,0,12,12"/>
        </StackPanel>
    </Grid>
  • 広い状態の場合、拡大表示ビューのグループの周囲には、垂直方向の呼吸空間が必要になります。 項目パネル テンプレートを作成して参照すると、必要な結果が得られます。 マークアップの外観を次に示します。
   <ItemsPanelTemplate x:Key="ZoomedInItemsPanelTemplate">
        <ItemsWrapGrid Orientation="Horizontal" GroupPadding="0,0,0,20"/>
    </ItemsPanelTemplate>
    ...

    <SemanticZoom x:Name="wideSeZo" ... >
        <SemanticZoom.ZoomedInView>
            <GridView
            ...
            ItemsPanel="{StaticResource ZoomedInItemsPanelTemplate}">
            ...
  • 最後に、適切な Visual State Manager マークアップを LayoutRootの最初の子として追加します。
    <Grid x:Name="LayoutRoot" ... >
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState x:Name="WideState">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="548"/>
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="wideSeZo.Visibility" Value="Visible"/>
                        <Setter Target="narrowSeZo.Visibility" Value="Collapsed"/>
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

    ...

最終的なスタイル設定

残っているのは、最終的なスタイル調整だけです。

  • AuthorGroupHeaderTemplateで、TextBlockForeground="White"を設定して、モバイル デバイス ファミリで実行するときに正しく表示されるようにします。
  • AuthorGroupHeaderTemplateZoomedOutAuthorTemplateの両方で、TextBlockFontWeight="SemiBold"を追加します。
  • narrowSeZoでは、拡大表示ビューのグループ ヘッダーと作成者は、ストレッチではなく左揃えになっているので、これに取り組みましょう。 HorizontalContentAlignmentStretchに設定して、拡大表示ビューのHeaderContainerStyleを作成します。 また、同じ Setter を含む縮小表示ビューのItemContainerStyle を作成します。 次のような外観です。
   <Style x:Key="AuthorGroupHeaderContainerStyle" TargetType="ListViewHeaderItem">
        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    </Style>

    <Style x:Key="ZoomedOutAuthorItemContainerStyle" TargetType="ListViewItem">
        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    </Style>

    ...

    <SemanticZoom x:Name="narrowSeZo" ... >
        <SemanticZoom.ZoomedInView>
            <ListView
            ...
                <ListView.GroupStyle>
                    <GroupStyle
                    ...
                    HeaderContainerStyle="{StaticResource AuthorGroupHeaderContainerStyle}"
                    ...
        <SemanticZoom.ZoomedOutView>
            <ListView
                ...
                ItemContainerStyle="{StaticResource ZoomedOutAuthorItemContainerStyle}"
                ...

このスタイル設定操作の最後のシーケンスでは、アプリは次のようになります。

デスクトップ デバイスで実行されている移植された Windows 10 アプリ、拡大表示、2 つのサイズのウィンドウ

デスクトップ デバイスで実行されている移植された Windows 10 アプリ、拡大表示、2 つのサイズのウィンドウ デスクトップ デバイスで実行されている移植された Windows 10 アプリ、縮小表示、2 つのサイズのウィンドウ

デスクトップ デバイスで実行されている移植された Windows 10 アプリ、縮小表示、2 つのサイズのウィンドウ

モバイル デバイスで実行されている移植された Windows 10 アプリ(拡大表示)

モバイル デバイスで実行されている移植された Windows 10 アプリ(拡大表示)

モバイル デバイスで実行されている移植された Windows 10 アプリの縮小表示

モバイル デバイスで実行されている移植された Windows 10 アプリ(縮小表示)

ビュー モデルの柔軟性を高める

このセクションでは、UWP を使用するようにアプリを移動したことによって、私たちに開く機能の例を示します。 ここでは、 CollectionViewSource を使用してアクセスするときにビュー モデルの柔軟性を高めるために従うことができる省略可能な手順について説明。 Windows Phone Silverlight アプリ Bookstore2WPSL8 から移植したビュー モデル (ViewModel\BookstoreViewModel.cs 内のソース ファイル) には、Author という名前のクラスが含まれています。このクラスは List<T> から派生したクラスです (この T は BookSku になります)。 つまり、Author クラス BookSku の a グループです。

CollectionViewSource.Sourceを作成者にバインドする場合、私たちが伝える唯一のことは、作成者の各著者がなグループであるということです。 作成者が BookSku のグループであることを判断するには、 CollectionViewSource に任せます。 これは機能しますが、柔軟性はありません。 Author を both BookSku and のグループ 作成者が住んでいるアドレスのグループにしたい場合はどうしますか? 作成者は、両方のグループできません。 ただし、Author は任意の数のグループ動作できます。 それが解決策です。現在使用しているグループ パターンの代わりに、またはそれに加えて、has-a-group パターンを使用します。 方法は以下のとおりです。

  • 作成者を変更して、 List<T> から派生しないようにします。
  • このフィールドを次に追加します:
  • このプロパティを次に追加します:
  • もちろん、上記の 2 つの手順を繰り返して、必要な数のグループを Author に追加できます。
  • AddBookSku メソッドの実装を this.BookSkus.Add(bookSku);に変更します。
  • Author has 少なくとも 1 つのグループを作成したので、 CollectionViewSource 使用するグループと通信する必要があります。 これを行うには、このプロパティを CollectionViewSource に追加します。 ItemsPath="BookSkus"

これらの変更により、このアプリは機能的に変更されませんが、必要に応じて Author と CollectionViewSource を拡張する方法がわかりました。 Author に最後の変更を 1 つ加えて、 使用する場合は CollectionViewSource.ItemsPath 指定選択した既定のグループが使用されるようにします。

    public class Author : IEnumerable<BookSku>
    {
        ...

        public IEnumerator<BookSku> GetEnumerator()
        {
            return this.BookSkus.GetEnumerator();
        }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.BookSkus.GetEnumerator();
        }
    }

これで、必要に応じて ItemsPath="BookSkus" を削除できます。アプリの動作は引き続き同じです。

まとめ

このケース スタディでは、前のユーザー インターフェイスよりも野心的なユーザー インターフェイスが含まれています。 Windows Phone Silverlight の LongListSelector に関するすべての機能や概念などが、SemanticZoomListViewGridViewCollectionViewSource の形式を使って UWP アプリで利用できることを学習しました。 UWP アプリで命令型コードとマークアップの両方を再利用またはコピーおよび編集して、最も狭くて幅の広い Windows デバイス フォーム ファクターと、その間のすべてのサイズに合わせて調整された機能、UI、対話を実現する方法について説明しました。