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 。 你可以缩小到跳转列表,然后从那里导航回任何组。 此应用有两个主要部分:提供分组数据源的视图模型,以及绑定到该视图模型的用户界面。 正如我们所看到的,这两个部分都可以轻松地从 Windows Phone Silverlight 技术移植到 通用 Windows 平台 (UWP)。

bookstore2wpsl8 的外观

移植到 Windows 10 项目

在 Visual Studio 中创建新项目是一项快速任务,从 Bookstore2WPSL8 将文件复制到其中,并在新项目中包括复制的文件。 首先创建新的空白应用程序(Windows 通用)项目。 将其命名为 Bookstore2Universal_10。 这些是要从 Bookstore2WPSL8 复制到 Bookstore2Universal_10 的文件。

  • 复制包含书籍封面图像 PNG 文件的文件夹(该文件夹是 \Assets\CoverImages)。 复制文件夹后,在解决方案资源管理器中,确保打开“显示所有文件”。 右键单击你复制的文件夹,然后单击“包括在项目中”。 此命令是指在项目中“包括”文件或文件夹。 每次你复制文件或文件夹时,请在“解决方案资源管理器”中单击“刷新”,然后将文件或文件夹包括在项目中。 对于目标中要替换的文件,无需执行此操作。
  • 复制包含视图模型源文件的文件夹(该文件夹是 \ViewModel)。
  • 复制 MainPage.xaml 并替换目标中的文件。

我们可以保留 App.xaml,并在 Windows 10 项目中App.xaml.cs Visual Studio 为我们生成。

编辑你刚刚复制的源代码和标记文件,并将对 Bookstore2WPSL8 命名空间的任何引用更改为 Bookstore2Universal_10。 执行此操作的快速方法是使用“在文件中替换”功能。 在视图模型源文件中的命令性代码中,需要这些移植更改。

  • DesignMode更改为System.ComponentModel.DesignerProperties“解析”命令,然后在其上使用“解析”命令。 删除属性 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 中,需要这些初始移植更改。

替换 LongListSelector

LongListSelector 替换为 SemanticZoom 控件需要执行几个步骤,因此让我们开始吧。 LongListSelector 直接绑定到分组数据源,但 SemanticZoom 包含 ListView 或 GridView 控件,该控件通过 CollectionViewSource 适配器间接绑定到数据。 CollectionViewSource 需要作为资源存在于标记中,因此让我们首先将该元素添加到 MainPage.xaml 中的标记中<Page.Resources>

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

请注意,LongListSelector.ItemsSource 上的绑定将成为 CollectionViewSource.Source 的值而 LongListSelector.IsGroupingEnabled 将成为 CollectionViewSource.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)。 此外,请注意,缩小视图绑定到名为 CollectionGroupsCollectionViewSource 的特殊属性,该属性是包含组而不是项的集合。

我们不再需要 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 应用

视图模型和放大和缩小视图正在正确协同工作,尽管有一个问题是我们需要做更多的样式和模板化工作。 例如,例如,尚未使用正确的样式和画笔,使得文本在可通过单击操作缩小的组标题上不可见。当你在桌面设备上运行应用时,你将会遇到第二个问题,即应用尚未适应其用户界面,使得无法在较大的设备上提供最佳的体验和空间的使用,并且窗口的大小可能会比移动设备的屏幕大小大很多。 因此,在接下来的几个部分中(初始样式和模板化、 自适应 UI最终样式),我们将解决这些问题。

初始样式设置和模板化

若要很好地缩小组标题的空间,请编辑AuthorGroupHeaderTemplate并设置边框边距"0,0,0,9.6"

若要很好地缩小书籍项的空间,请编辑 BookTemplate 并将 Margin 设置为 "9.6,0" 这两个 TextBlocks。

若要更好地布局应用名称和页面标题,在内部TitlePanel,通过将值设置为 "7.2,0,0,0" 来删除第二个 TextBlock 上的上边距。 在自身上 TitlePanel ,将边距设置为 0 (或任何值都适合你)

将背景更改为 LayoutRoot"{ThemeResource ApplicationPageBackgroundThemeBrush}"

自适应 UI

由于我们从手机应用开始,因此,移植应用的 UI 布局对于此阶段的小型设备和窄窗口实际上才有意义,这并不奇怪。 但是,我们确实希望 UI 布局能够适应自身,并在应用在宽窗口中运行时更好地利用空间(这仅在具有大屏幕的设备上可用),并且仅当应用窗口较窄(在小型设备上发生)时,它才使用我们目前使用的 UI( 在小型设备上发生) 还可以在大型设备上发生)。

可以使用自适应 Visual State Manager 功能来实现此目的。 我们将设置视觉元素的属性,以便默认情况下,UI 使用我们现在使用的模板以窄状态布局。 然后,我们将检测应用窗口的宽度是否大于或等于特定大小(以有效像素单位为单位),并且为了响应,我们将更改视觉元素的属性,以便获得更大、更宽的布局。 我们将这些属性更改置于视觉状态,我们将使用自适应触发器持续监视并确定是否应用该视觉状态,具体取决于窗口的宽度(以有效像素为单位)。 在本例中,我们在窗口宽度上触发,但也可以触发窗口高度。

最小窗口宽度为 548 epx 适用于此用例,因为这是我们想要显示宽布局的最小设备的大小。 手机通常小于 548 epx,因此,在这样的小型设备上,我们会保持默认窄布局。 在电脑上,窗口默认将启动足够宽,以触发切换到宽状态,这将显示 250x250 大小的项。 在此处,你将能够拖动足够窄的窗口,以显示至少两列的 250x250 项。 比该范围更窄且触发器将停用的任何范围,将删除宽视觉状态,并且默认窄布局将生效。

在处理自适应视觉状态管理器部分之前,我们首先需要设计宽状态,这意味着将一些新的视觉元素和模板添加到标记。 这些步骤介绍如何执行此操作。 通过对视觉元素和模板的命名约定,我们将在为宽状态的任何元素或模板的名称中包含“wide”一词。 如果元素或模板不包含“wide”一词,则可以假定它是窄状态,即默认状态,其属性值在页面上的视觉元素上设置为本地值。 只有宽状态的属性值通过标记中的实际视觉状态设置。

  • 在标记中创建 SemanticZoom 控件的副本,并在副本上设置x:Name="narrowSeZo" 在原始的、设置 x:Name="wideSeZo" 和设置 Visibility="Collapsed" 上,以便默认情况下不可见宽的。
  • wideSeZo中,在放大视图和缩小视图中将 ListView更改为 GridView
  • 创建这三个资源 AuthorGroupHeaderTemplate的副本, ZoomedOutAuthorTemplate并将 BookTemplate 单词 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>

    ...

最终样式

剩下的都是一些最终的样式调整。

  • AuthorGroupHeaderTemplateTextBlock设置Foreground="White",以便在移动设备系列上运行时看起来正确。
  • FontWeight="SemiBold"同时添加到 TextBlockAuthorGroupHeaderTemplate ZoomedOutAuthorTemplate.
  • narrowSeZo中,缩小视图中的组标题和作者是左对齐的,而不是拉伸的,因此让我们来解决此问题。 我们将为已缩放视图创建 HeaderContainerStyle,并将 HorizontalContentAlignment 设置为 。Stretch 我们将为包含同 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 应用,放大视图,两个大小的窗口

在桌面设备上运行的已移植的 Windows 10 应用,放大视图,两个大小的窗口 在桌面设备上运行的已移植的 Windows 10 应用,缩小视图,两个大小的窗口

在桌面设备上运行的已移植的 Windows 10 应用,缩小视图,两个大小的窗口

在移动设备上运行的已移植的 Windows 10 应用,放大视图

在移动设备上运行的已移植的 Windows 10 应用,放大视图

在移动设备上运行的已移植的 Windows 10 应用,缩小视图

在移动设备上运行的已移植的 Windows 10 应用,缩小视图

使视图模型更加灵活

本部分包含一个设施示例,通过移动应用以使用 UWP 来向我们开放。 此处,我们将介绍一些可选步骤,以便通过 CollectionViewSource 访问视图模型时更灵活。 我们从 Windows Phone Silverlight 应用 Bookstore2WPSL8 移植的视图模型(源文件位于 ViewModel\BookstoreViewModel.cs 中)包含一个派生自 List<T> 的名为 Author 的类,其中 T 是 BookSku。 这意味着 Author 类 是一 组 BookSku。

将 CollectionViewSource.Source 绑定到 Author 时,我们唯一传达的内容是 Author 中的每个作者都是一组内容。 我们将它留给 CollectionViewSource ,以确定作者是一组 BookSku。 这很有效:但它并不灵活。 如果我们希望 Author 既是 BookSku 的一组,又是作者居住的一组地址,该怎么办? 作者不能 这两个组。 但是,Author 可以 具有 任意数量的组。 这就是解决方案:使用 has-a-group 模式,而不是除组模式外, 我们当前使用的是组 模式。 下面介绍如何操作:

  • 更改 Author,使其不再派生自 List<T>
  • 将此字段添加到
  • 将此属性添加到
  • 当然,我们可以重复上述两个步骤,以根据需要向 Author 添加任意数量的组。
  • 将 AddBookSku 方法的实现更改为 this.BookSkus.Add(bookSku);.
  • 现在,作者至少有一个组,我们需要与 CollectionViewSource 通信,它应使用哪些组。 为此,请将此属性添加到 CollectionViewSourceItemsPath="BookSkus"

这些更改使此应用在功能上保持不变,但你现在知道,如果需要,如何扩展 Author 和 CollectionViewSource。 让我们对 Author 进行最后一次更改,以便在未指定 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 的所有设施和概念以及其他更多内容都可以采用 SemanticZoom、ListView、GridView 和 CollectionViewSource 的形式供 UWP 应用使用。 我们介绍了如何在 UWP 应用中重复使用或复制和编辑命令性代码和标记,以实现针对最窄、最宽的 Windows 设备外形规格和所有大小之间的功能、UI 和交互。