重新想象 Windows 8.1 Store Apps (93) - 控件增强: GridView, ListView
重新想象 Windows 8.1 Store Apps (93) - 控件增强: GridView, ListView
作者:webabcd
介绍
重新想象 Windows 8.1 Store Apps 之控件增强
- GridView 和 ListView 每屏显示的数据量多滚动也流畅
- GridViewItemPresenter 和 ListViewItemPresenter 更方便更快速地显示各种状态
- 自定义 GridViewItemPresenter 和 ListViewItemPresenter
示例
1、演示 GridView 和 ListView 的新增特性: GridView 和 ListView 每屏显示的数据量多滚动也流畅
GridViewAndListView/Employee.cs
namespace Windows81.Controls.GridViewAndListView { public class Employee { public string Name { get; set; } public int Age { get; set; } public bool IsMale { get; set; } } }
GridViewAndListView/TestData.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Windows81.Controls.GridViewAndListView { public class TestData { /// <summary> /// 返回一个 Employee 数据集合,测试用 /// </summary> public static List<Employee> GetEmployees() { var employees = new List<Employee>(); for (int i = 0; i < 10000; i++) { employees.Add( new Employee { Name = "Name " + i.ToString().PadLeft(4, '0'), Age = new Random(i).Next(20, 60), IsMale = Convert.ToBoolean(i % 2) }); } return employees; } } }
GridViewAndListView/IncrementalData.xaml
<Page x:Name="pageRoot" x:Class="Windows81.Controls.GridViewAndListView.IncrementalData" 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:Windows81.Controls.GridViewAndListView" xmlns:common="using:Windows81.Common" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="Transparent"> <GridView x:Name="gridView" Margin="120 0 0 0" ContainerContentChanging="gridView_ContainerContentChanging"> <GridView.ItemTemplate> <DataTemplate> <StackPanel Height="100" Width="100" Background="Blue"> <Rectangle x:Name="placeholderRectangle" Fill="Red" Height="10" Opacity="0" /> <TextBlock x:Name="lblName" Text="{Binding Name}" Foreground="Yellow" /> <TextBlock x:Name="lblAge" Text="{Binding Age}" Foreground="Aqua" /> <TextBlock x:Name="lblIsMale" Text="{Binding IsMale}" Foreground="Gray" /> </StackPanel> </DataTemplate> </GridView.ItemTemplate> </GridView> </Grid> </Page>
GridViewAndListView/IncrementalData.xaml.cs
/* * 演示 GridView 和 ListView 的新增特性 * * 当 GridView 或 ListView 的一屏需要显示的数据量极大时(一屏的 item 多,且每个 item 中的 element 也多),由于每次滚动时需要绘制当前屏的每个 element,这需要占用大量的 ui 资源,所以就会有一些卡顿 * 为了解决这个问题 win8.1 给出了两种解决方案 * 1、设置 GridView 或 ListView 的 ShowsScrollingPlaceholders 属性为 true(默认值),每次显示 item 时先会显示占位符(application 级的背景色块),然后再绘制内容 * 2、通过 GridView 或 ListView 的 ContainerContentChanging 事件,分步绘制 item 中的 element */ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Navigation; using Windows.UI.Xaml.Shapes; namespace Windows81.Controls.GridViewAndListView { public sealed partial class IncrementalData : Page { public IncrementalData() { this.InitializeComponent(); } protected override void OnNavigatedTo(NavigationEventArgs e) { gridView.ItemsSource = TestData.GetEmployees(); // 默认值是 true,即为了保证流畅,每次显示 item 时先会显示占位符(application 级的背景色块),然后再绘制内容 // 本例演示 ContainerContentChanging 事件的使用,所以不会用到这个 gridView.ShowsScrollingPlaceholders = false; } private void gridView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) { // 交由我处理吧 args.Handled = true; // 第 1 阶段绘制 // args.Phase.ToString(); // 0 StackPanel templateRoot = (StackPanel)args.ItemContainer.ContentTemplateRoot; Rectangle placeholderRectangle = (Rectangle)templateRoot.FindName("placeholderRectangle"); TextBlock lblName = (TextBlock)templateRoot.FindName("lblName"); TextBlock lblAge = (TextBlock)templateRoot.FindName("lblAge"); TextBlock lblIsMale = (TextBlock)templateRoot.FindName("lblIsMale"); // 显示自定义占位符(也可以不用这个,而是直接显示 item 的背景) placeholderRectangle.Opacity = 1; // 除了占位符外,所有 item 全部暂时不绘制 lblName.Opacity = 0; lblAge.Opacity = 0; lblIsMale.Opacity = 0; // 开始下一阶段的绘制 args.RegisterUpdateCallback(ShowName); } private void ShowName(ListViewBase sender, ContainerContentChangingEventArgs args) { // 第 2 阶段绘制 // args.Phase.ToString(); // 1 Employee employee = (Employee)args.Item; SelectorItem itemContainer = (SelectorItem)args.ItemContainer; StackPanel templateRoot = (StackPanel)itemContainer.ContentTemplateRoot; TextBlock lblName = (TextBlock)templateRoot.FindName("lblName"); // 绘制第 2 阶段的内容 lblName.Text = employee.Name; lblName.Opacity = 1; // 开始下一阶段的绘制 args.RegisterUpdateCallback(ShowAge); } private void ShowAge(ListViewBase sender, ContainerContentChangingEventArgs args) { // 第 3 阶段绘制 // args.Phase.ToString(); // 2 Employee employee = (Employee)args.Item; SelectorItem itemContainer = (SelectorItem)args.ItemContainer; StackPanel templateRoot = (StackPanel)itemContainer.ContentTemplateRoot; TextBlock lblAge = (TextBlock)templateRoot.FindName("lblAge"); // 绘制第 3 阶段的内容 lblAge.Text = employee.Age.ToString(); lblAge.Opacity = 1; // 开始下一阶段的绘制 args.RegisterUpdateCallback(ShowIsMale); } private void ShowIsMale(ListViewBase sender, ContainerContentChangingEventArgs args) { // 第 4 阶段绘制 // args.Phase.ToString(); // 3 Employee employee = (Employee)args.Item; SelectorItem itemContainer = (SelectorItem)args.ItemContainer; StackPanel templateRoot = (StackPanel)itemContainer.ContentTemplateRoot; Rectangle placeholderRectangle = (Rectangle)templateRoot.FindName("placeholderRectangle"); TextBlock lblIsMale = (TextBlock)templateRoot.FindName("lblIsMale"); // 绘制第 4 阶段的内容 lblIsMale.Text = employee.IsMale.ToString(); lblIsMale.Opacity = 1; // 隐藏自定义占位符 placeholderRectangle.Opacity = 0; } } }
2、GridViewItemPresenter 和 ListViewItemPresenter - 用于设置控件的各种状态(win8.1 新引入),比 win8 的方式更方便,且更快(win8 的方式是全部加载完后再显示,win8.1 的此方式是需要的时候才加载)
GridViewAndListView/ItemPresenter.xaml
<Page x:Class="Windows81.Controls.GridViewAndListView.ItemPresenter" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Windows81.Controls.GridViewAndListView" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Page.Resources> <Style x:Key="MyGridViewItemPresenterTemplate" TargetType="GridViewItem"> <Setter Property="Background" Value="Transparent"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="GridViewItem"> <!-- GridViewItemPresenter 和 ListViewItemPresenter - 用于设置控件的各种状态(win8.1 新引入),比 win8 的方式更方便,且更快(win8 的方式是全部加载完后再显示,win8.1 的此方式是需要的时候才加载) Margin - item 的 margin SelectionCheckMarkVisualEnabled - 是否显示选中状态的标记 SelectedBorderThickness - 选中状态的边框粗细 SelectedBackground - 选中状态的边框颜色 CheckBrush - 选中状态的图标(本例就是那个小对勾) ...... - 还有好多好多,看文档吧 --> <GridViewItemPresenter Margin="10" SelectionCheckMarkVisualEnabled="True" SelectedBorderThickness="3" SelectedBackground="Red" CheckBrush="{ThemeResource ListViewItemCheckThemeBrush}"/> </ControlTemplate> </Setter.Value> </Setter> </Style> </Page.Resources> <Grid Background="Transparent"> <GridView x:Name="gridView" Margin="120 0 0 0" ItemContainerStyle="{StaticResource MyGridViewItemPresenterTemplate}" SelectionMode="Multiple"> <GridView.ItemTemplate> <DataTemplate> <StackPanel Height="100" Width="100" Background="Blue"> <TextBlock x:Name="lblName" Text="{Binding Name}" Foreground="Yellow" /> <TextBlock x:Name="lblAge" Text="{Binding Age}" Foreground="Aqua" /> <TextBlock x:Name="lblIsMale" Text="{Binding IsMale}" Foreground="Gray" /> </StackPanel> </DataTemplate> </GridView.ItemTemplate> </GridView> </Grid> </Page>
GridViewAndListView/ItemPresenter.xaml.cs
/* * GridViewItemPresenter 和 ListViewItemPresenter - 用于设置控件的各种状态(win8.1 新引入),比 win8 的方式更方便,且更快(win8 的方式是全部加载完后再显示,win8.1 的此方式是需要的时候才加载) */ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; namespace Windows81.Controls.GridViewAndListView { public sealed partial class ItemPresenter : Page { public ItemPresenter() { this.InitializeComponent(); } protected override void OnNavigatedTo(NavigationEventArgs e) { gridView.ItemsSource = TestData.GetEmployees(); } } }
3、演示自定义 GridViewItemPresenter 和 ListViewItemPresenter 的使用
GridViewAndListView/MyItemPresenter.cs
/* * 自定义 GridViewItemPresenter 和 ListViewItemPresenter * * 本例演示如何自定义一个 ContentPresenter 类,以用于 GridView(参见:ItemPresenterCustom.xaml) */ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Animation; using Windows.UI.Xaml.Shapes; namespace Windows81.Controls.GridViewAndListView { class MyItemPresenter : ContentPresenter { Grid _contentGrid = null; // item 的 grid(数据模板中的 Grid,也就是说要使用本例的这个 ContentPresenter 则数据模板中必须要用 Grid 做容器) Rectangle _pointerOverBorder = null; // 鼠标经过 Rectangle _focusVisual = null; // 选中 PointerDownThemeAnimation _pointerDownAnimation = null; Storyboard _pointerDownStoryboard = null; GridView _parentGridView; public MyItemPresenter() : base() { base.Margin = new Thickness(10); } protected override void OnApplyTemplate() { base.OnApplyTemplate(); var obj = VisualTreeHelper.GetParent(this); while (!(obj is GridView)) { obj = VisualTreeHelper.GetParent(obj); } _parentGridView = (GridView)obj; _contentGrid = (Grid)VisualTreeHelper.GetChild(this, 0); } protected override bool GoToElementStateCore(string stateName, bool useTransitions) { base.GoToElementStateCore(stateName, useTransitions); switch (stateName) { // 正常状态 case "Normal": HidePointerOverVisuals(); HideFocusVisuals(); if (useTransitions) { StopPointerDownAnimation(); } break; // 选中状态 case "Focused": case "PointerFocused": ShowFocusVisuals(); break; // 取消选中状态 case "Unfocused": HideFocusVisuals(); break; // 鼠标经过状态 case "PointerOver": ShowPointerOverVisuals(); if (useTransitions) { StopPointerDownAnimation(); } break; // 鼠标点击状态 case "Pressed": case "PointerOverPressed": if (useTransitions) { StartPointerDownAnimation(); } break; default: break; } return true; } private void StartPointerDownAnimation() { if (_pointerDownStoryboard == null) CreatePointerDownStoryboard(); _pointerDownStoryboard.Begin(); } private void StopPointerDownAnimation() { if (_pointerDownStoryboard != null) _pointerDownStoryboard.Stop(); } private void ShowFocusVisuals() { if (!FocusElementsAreCreated()) CreateFocusElements(); _focusVisual.Opacity = 1; } private void HideFocusVisuals() { if (FocusElementsAreCreated()) _focusVisual.Opacity = 0; } private void ShowPointerOverVisuals() { if (!PointerOverElementsAreCreated()) CreatePointerOverElements(); _pointerOverBorder.Opacity = 1; } private void HidePointerOverVisuals() { if (PointerOverElementsAreCreated()) _pointerOverBorder.Opacity = 0; } private void CreatePointerDownStoryboard() { _pointerDownAnimation = new PointerDownThemeAnimation(); Storyboard.SetTarget(_pointerDownAnimation, _contentGrid); _pointerDownStoryboard = new Storyboard(); _pointerDownStoryboard.Children.Add(_pointerDownAnimation); } private void CreatePointerOverElements() { _pointerOverBorder = new Rectangle(); _pointerOverBorder.IsHitTestVisible = false; _pointerOverBorder.Opacity = 0; _pointerOverBorder.Fill = (SolidColorBrush)_parentGridView.Resources["PointerOverBrush"]; // 此资源要预先在 GridView 中定义 _contentGrid.Children.Insert(_contentGrid.Children.Count, _pointerOverBorder); } private void CreateFocusElements() { _focusVisual = new Rectangle(); _focusVisual.IsHitTestVisible = false; _focusVisual.Height = 10; _focusVisual.StrokeThickness = 2; _focusVisual.VerticalAlignment = VerticalAlignment.Bottom; _focusVisual.Fill = (SolidColorBrush)_parentGridView.Resources["FocusBrush"]; // 此资源要预先在 GridView 中定义 _focusVisual.Stroke = (SolidColorBrush)_parentGridView.Resources["FocusBrush"]; // 此资源要预先在 GridView 中定义 _contentGrid.Children.Insert(0, _focusVisual); } private bool FocusElementsAreCreated() { return _focusVisual != null; } private bool PointerOverElementsAreCreated() { return _pointerOverBorder != null; } } }
GridViewAndListView/ItemPresenterCustom.xaml
<Page x:Class="Windows81.Controls.GridViewAndListView.ItemPresenterCustom" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Windows81.Controls.GridViewAndListView" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Page.Resources> <Style x:Key="MyGridViewItemPresenterTemplate" TargetType="GridViewItem"> <Setter Property="Background" Value="Transparent"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="GridViewItem"> <!-- 自定义 GridViewItemPresenter 和 ListViewItemPresenter 关于 GridViewItemPresenter 和 ListViewItemPresenter 的说明参见:ItemPresenter.xaml MyItemPresenter 参见 MyItemPresenter.cs --> <local:MyItemPresenter/> </ControlTemplate> </Setter.Value> </Setter> </Style> </Page.Resources> <Grid Background="Transparent"> <GridView x:Name="gridView" Margin="120 0 0 0" ItemContainerStyle="{StaticResource MyGridViewItemPresenterTemplate}" SelectionMode="Multiple"> <GridView.Resources> <!--MyItemPresenter.cs 中需要用到的资源--> <SolidColorBrush x:Name="PointerOverBrush" Color="#50505050"/> <SolidColorBrush x:Name="FocusBrush" Color="#ffff0000"/> </GridView.Resources> <GridView.ItemTemplate> <DataTemplate> <Grid Height="100" Width="100" Background="Blue"> <TextBlock x:Name="lblName" Text="{Binding Name}" Foreground="Yellow" /> </Grid> </DataTemplate> </GridView.ItemTemplate> </GridView> </Grid> </Page>
GridViewAndListView/ItemPresenterCustom.xaml.cs
/* * 演示自定义 GridViewItemPresenter 和 ListViewItemPresenter 的使用 * * 关于 GridViewItemPresenter 和 ListViewItemPresenter 的说明参见:ItemPresenter.xaml */ using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; namespace Windows81.Controls.GridViewAndListView { public sealed partial class ItemPresenterCustom : Page { public ItemPresenterCustom() { this.InitializeComponent(); } protected override void OnNavigatedTo(NavigationEventArgs e) { gridView.ItemsSource = TestData.GetEmployees(); } } }
OK
[源码下载]