我们今天学习一下ContentControl,主要介绍如何使用ContentControl搭配DataTemplate来进行界面的复用,以及通过ContentTemplateSelector进一步减少页面元素数量,提高性能。
假设我们的UWP APP为左右分开两列,左边为ListView显示集合,右边为ListView中选中项的明细页面。左侧ListView会列出每一项的Avatar,共分三种:1.有图像的显示图像。2.没图像有名字显示首字母,3.图像名字都没有,显示两个圈圈。同时在ListView被选中某项时,就在右侧显示大号的Avatar。
是不是挺眼熟的,一看又是把生产上的东西简化出来做Demo了……
可以拿来复用的就是Avatar这块了,我们先尝试着用ContentControl和DataTemplate来绘制这块内容:
<DataTemplate x:Key="AvatarWithVisibility"> <Grid > <Grid Visibility="{Binding Converter={StaticResource RoomTypeConverter},ConverterParameter=Name}"> <Ellipse Fill="Green"></Ellipse> <TextBlock Text="{Binding Index}" TextAlignment="Center" VerticalAlignment="Center"></TextBlock> </Grid> <Grid Visibility="{Binding Converter={StaticResource RoomTypeConverter},ConverterParameter=Image}"> <Ellipse > <Ellipse.Fill> <ImageBrush ImageSource="{Binding ImageUri}"></ImageBrush> </Ellipse.Fill> </Ellipse> </Grid> <Grid CacheMode="BitmapCache" Visibility="{Binding Converter={StaticResource RoomTypeConverter},ConverterParameter=Default}"> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition Height="2*"></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition Width="2*"></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Ellipse Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" Fill="Green"> </Ellipse> <Ellipse Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2" Fill="Orange"> </Ellipse> </Grid> </Grid> </DataTemplate>
很常规的写法,三个重叠的Grid通过RoomTypeConverter来确定Visibility的值,以便确定具体显示哪一个。该DataTemplate的使用页面如下:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <ListView x:Name="listViewAvatar" Grid.Column="0" ItemsSource="{Binding Rooms}"> <ListView.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition ></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <ContentControl Grid.Column="0" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" Width="50" Height="50" ContentTemplate="{StaticResource AvatarWithVisibility}"></ContentControl> <StackPanel Grid.Column="1" > <TextBlock Text="{Binding Name}"></TextBlock> <TextBlock Text="{Binding ImageUri}"></TextBlock> </StackPanel> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> <ContentControl Grid.Column="1" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" Content="{Binding SelectedItem,ElementName=listViewAvatar}" Width="150" Height="150" ContentTemplate="{StaticResource AvatarWithVisibility}"></ContentControl> </Grid>
可以看到页面分成了左右两块,左边ListView的ItemTemplate以及右半边各自使用了ContentControl,其中右半边的ContentControl把Content属性Binding到了ListView的SelectedItem上。通过上述例子可以明显地看到ContentControl复用了DataTemplate的内容,那有没有更进一步的优化呢?
原则上我们希望尽最大可能的减少页面上显示的UIElement的数量,这样能够使布局设置和呈现的速度更快。下面我们尝试用ContentTemplateSelector来优化一下当前的页面。
首先不再通过Visibility区分显示的Grid,而是将三个不同的Grid拆分开来,通过ContentTemplateSelector每次选择正确的样式来显示。因为Visibility即时为Collapsed,在可视化树上仍然会作为UIElement存在。
<DataTemplate x:Key="Name"> <Grid> <Ellipse Fill="Green"></Ellipse> <TextBlock Text="{Binding Index}" TextAlignment="Center" VerticalAlignment="Center"></TextBlock> </Grid> </DataTemplate> <DataTemplate x:Key="Image"> <Grid> <Ellipse > <Ellipse.Fill> <ImageBrush ImageSource="{Binding ImageUri}"></ImageBrush> </Ellipse.Fill> </Ellipse> </Grid> </DataTemplate> <DataTemplate x:Key="Default"> <Grid CacheMode="BitmapCache"> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition Height="2*"></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition Width="2*"></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Ellipse Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" Fill="Green"> </Ellipse> <Ellipse Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2" Fill="Orange"> </Ellipse> </Grid> </DataTemplate>
之前的DataTemplate被拆成三个,并通过CustomTemplateSelector来使用,同一时间可视化树上将只会显示其中一个Grid的内容。
public class CustomTemplateSelector : DataTemplateSelector { protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) { var room = item as Room; if (item == null) { return base.SelectTemplateCore(item, container); } if (room.ImageUri == null) { if (string.IsNullOrEmpty(room.Name)) { return App.Current.Resources["Default"] as DataTemplate; } return App.Current.Resources["Name"] as DataTemplate; } return App.Current.Resources["Image"] as DataTemplate; } }
实际使用的XAML有点类似于ListView本身的ItemTemplateSelector:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <ListView x:Name="listViewAvatar" Grid.Column="0" ItemsSource="{Binding Rooms}"> <ListView.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition ></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <ContentControl Grid.Column="0" ContentTemplateSelector="{StaticResource CustomTemplateSelector}" Content="{Binding}" Width="50" Height="50" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"></ContentControl> <StackPanel Grid.Column="1" > <TextBlock Text="{Binding Name}"></TextBlock> <TextBlock Text="{Binding ImageUri}"></TextBlock> </StackPanel> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> <ContentControl Grid.Column="1" ContentTemplateSelector="{StaticResource CustomTemplateSelector}" Content="{Binding SelectedItem,ElementName=listViewAvatar}" Width="150" Height="150" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"> </ContentControl> </Grid>
XAML看上去和之前似乎没什么不同,但即使是这种简单的模板,在ListView中元素数量达到1000个时(因为虚拟化的缘故,实际远远不到1000个),可视化树上已经有了不小的数量差距。1300 VS 850,实际更复杂的DataTemplate将会有更大的数量差。
另外需要指出的是,以上代码是在VS2015 Update3下完成的。如果是旧的VS2015 Update1,在列表快速滑动时,虚拟化会导致列表中ContentControl错位的显示,需要额外的Binding一个DataContext:
<ContentControl Grid.Column="0" ContentTemplateSelector="{StaticResource CustomTemplateSelector}" DataContext="{Binding}" Content="{Binding}" Width="50" Height="50" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"></ContentControl> <StackPanel Grid.Column="1" >
以上就是今天的内容了,希望能给各位一点帮助。谢谢能看到这里的各位!
顺便吐槽一下UWP不好做啊,除了明面上强大的竞争对手iOS和安卓,其实身后还有传统的Win32程序,毕竟UWP可以在PC上跑就等于想分别的人蛋糕啊,遭到打压是必然的,实在太惨了……
本篇的完整代码依旧放在GitHub上,欢迎取用:
https://github.com/manupstairs/UWPSamples/tree/master/UWPSamples/ContentControlWithTemplateSelector