本文的样例来自于Windows-universal-samples的XamlListView中的MasterDetailSelection。

Master/details pattern是一个很经典的应用程序结构布局,所以在UWP的设计指南里面也有重点描述的。

此示例由两个Page构成,分别是MasterDetailSelection和DetailPage。两者应用在不同的屏幕宽度,屏幕宽的时候用前者,屏幕窄的时候用后者。

为了依据屏幕宽度改变显示页面,MasterDetailSelection中定义了一个VisualStateGroup,名为PageSizeStatesGroup。并在其中创建了两个VisualState,分别为WideState和NarrowState。在WideState的情况下显示MasterDetailSelection,在NarrowState的情况下显示DetailPage。WideState要求屏幕宽度至少为720。

窄屏的情况下,内容显示受限。精简的MasterDetailSelection作为导航,而DetailPage作为内容页面。DetailPage使用了下面的Transitions,以便能够返回上一级的MasterDetailSelection:

    <Page.Transitions>
        <TransitionCollection>
            <NavigationThemeTransition />
        </TransitionCollection>
    </Page.Transitions>

MasterDetailSelection还定义了一个AppBar,上有若干按键。每个按键要根据目前用户操作的状态来决定隐藏还是显示,于是MasterDetailSelection还定义了另一个VisualStateGroup,名为MasterDetailsStatesGroup,用于控制这些按键的显示。MasterDetailsStatesGroup共有四种状态:MasterState、MasterDetailsState、ExtendedSelectionState和MultipleSelectionState。

上面这四种不同的状态同时也控制着ListView的显示模式。比如在MultipleSelectionState的时候,ListView的状态为:

                        <Setter Target="MasterListView.SelectionMode" Value="Multiple" />
                        <Setter Target="MasterListView.IsItemClickEnabled" Value="False" />

ListView的左边会出现复选框,可以让用户勾选多个选项。

在MasterDetailSelection页面中,页面的内容是通过ContentPresenter来显示的:

            <ContentPresenter
                x:Name="DetailContentPresenter"
                Grid.Column="1"
                Grid.RowSpan="2"
                BorderThickness="1,0,0,0"
                Padding="24,0"
                BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
                Content="{x:Bind MasterListView.SelectedItem, Mode=OneWay}">
                <ContentPresenter.ContentTemplate>
                    <DataTemplate x:DataType="data:Contact">
                        <StackPanel>
                            <TextBlock Style="{ThemeResource TitleTextBlockStyle}"
                                   Margin="0,8"       
                                   Text="{x:Bind Name}"/>
                            <TextBlock Style="{ThemeResource BodyTextBlockStyle}"
                                   Margin="0,9" 
                                   Text="{x:Bind Position}" />
                            <TextBlock Style="{ThemeResource BodyTextBlockStyle}" 
                                   Margin="0,9" 
                                   Text="{x:Bind PhoneNumber}" />
                            <TextBlock Style="{ThemeResource BodyTextBlockStyle}" 
                                   Margin="0,9" 
                                   TextWrapping="WrapWholeWords"
                                   Text="{x:Bind Biography}" />
                        </StackPanel>
                    </DataTemplate>
                </ContentPresenter.ContentTemplate>
                <ContentPresenter.ContentTransitions>
                    <TransitionCollection/>
                </ContentPresenter.ContentTransitions>
            </ContentPresenter>

可以看到此ContentPresenter的ContentTemplate设置为一个DataTemplate 。这个DataTemplate的内容和DetailPage其实是一致的,稍微显得有点重复,或许可以独立出来,变成一个UserControl,以便共享代码。

上面的DataTemplate 有点函数式编程的味道,如果把它看成一个函数,它的输出是模板的内容。那么将其赋值给ContentPresenter的ContentTemplate,就相当于返回一个函数,当需要的时候调用这个函数,然后输出相应的内容。

小结:此示例中使用两个页面来适应宽窄屏,导致两个页面之间需要一些交互代码。比如两个页面都必须有逻辑去侦测窗口大小,当窗口大小改变至条件合适时,切换到另一个页面。并且切换过程中可能需要携带一些参数。比如当DetailPage删除了当前页面时,就必须切换到MasterDetailSelection,并且告知当前页面被删除这个状态。

(完)