posts - 615, comments - 10492, trackbacks - 594, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

公告

重新想象 Windows 8.1 Store Apps (93) - 控件增强: GridView, ListView

Posted on 2015-01-05 15:06 webabcd 阅读(...) 评论(...) 编辑 收藏

[源码下载]


重新想象 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
[源码下载]