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

公告

重新想象 Windows 8 Store Apps (12) - 控件之 GridView 特性: 拖动项, 项尺寸可变, 分组显示

Posted on 2013-03-18 08:48 webabcd 阅读(...) 评论(...) 编辑 收藏

[源码下载]


重新想象 Windows 8 Store Apps (12) - 控件之 GridView 特性: 拖动项, 项尺寸可变, 分组显示



作者:webabcd


介绍
重新想象 Windows 8 Store Apps 之 GridView

  • 拖动项 - 在 GridView 内拖动 item 以对 item 排序, 拖动 item 到 GridView 外的指定位置以删除 item
  • 项尺寸可变 - 指定 GirdView 中每个 item 所占尺寸
  • 分组显示 - 分组显示集合数据



示例
1、演示如何在 GridView 内拖动 item 以对 item 排序,以及如何拖动 item 到 GridView 外的指定位置以删除 item
GridView/DragItem.xaml

<Page
    x:Class="XamlDemo.Controls.GridView.DragItem"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:XamlDemo.Controls.GridView"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.Resources>
        <DataTemplate x:Key="ItemTemplate">
            <StackPanel Orientation="Vertical">
                <TextBlock TextWrapping="Wrap" FontSize="14.667" Text="{Binding Name}" HorizontalAlignment="Left" />
                <TextBlock TextWrapping="Wrap" FontSize="14.667" Text="{Binding Age}" HorizontalAlignment="Left"/>
            </StackPanel>
        </DataTemplate>
        <Style x:Key="ItemContainerStyle"  TargetType="GridViewItem">
            <Setter Property="Width" Value="292" />
            <Setter Property="Height" Value="80" />
            <!--
                即使将 Margin 设置为“0”,也无法去掉 item 之间的 margin
                如果想要去掉 item 之间的 margin,请将此 Margin 属性设置为“-4”
            -->
            <Setter Property="Margin" Value="0" />
            <Setter Property="Background" Value="Blue" />
        </Style>
        <ItemsPanelTemplate x:Key="ItemsPanel">
            <!--
                注:WrapGrid 继承了 VirtualizingPanel,而 VariableSizedWrapGrid 并未继承 VirtualizingPanel
            -->
            <WrapGrid MaximumRowsOrColumns="3" Orientation="Vertical" VerticalChildrenAlignment="Top" HorizontalChildrenAlignment="Left" />
        </ItemsPanelTemplate>
    </Page.Resources>

    <Grid Background="Transparent">
        <StackPanel Margin="120 0 0 0">

            <TextBlock Name="lblMsg" FontSize="14.667" Text="通过拖动 GirdView 中的 Item 进行排序" />

            <GridView x:Name="gridView" VerticalAlignment="Top" Margin="0 10 10 0" BorderThickness="1" BorderBrush="Red" Background="LightBlue"
                      ItemTemplate="{StaticResource ItemTemplate}"
                      ItemContainerStyle="{StaticResource ItemContainerStyle}"
                      ItemsPanel="{StaticResource ItemsPanel}"
                      IsSwipeEnabled="True" IsItemClickEnabled="True"
                      CanDragItems="True" CanReorderItems="True" AllowDrop="True" 
                      DragItemsStarting="gridView_DragItemsStarting_1" />

            <!--拖动 item 到此处以删除 item-->
            <Grid Name="gridDelete" Margin="0 10 0 0" AllowDrop="True" Drop="gridDelete_Drop_1" DragEnter="gridDelete_DragEnter_1" DragLeave="gridDelete_DragLeave_1" DragOver="gridDelete_DragOver_1">
                <Rectangle Width="300" Height="100" StrokeThickness="1" StrokeDashArray="2" Stroke="Red" Fill="Blue" />
                <TextBlock FontSize="26.667" Text="拖动到此处以删除" TextAlignment="Center" VerticalAlignment="Center" />
            </Grid>
            
        </StackPanel>
    </Grid>
</Page>

GridView/DragItem.xaml.cs

/*
 * 演示如何在 GridView 内拖动 item 以对 item 排序,以及如何拖动 item 到 GridView 外的指定位置以删除 item
 * 
 * GridView - 网格控件
 *     CanDragItems - item 是否可被拖动
 *     CanReorderItems - 是否可通过拖动 item 来排序
 *     AllowDrop - 是否可在 GridView 中 drop
 *     DragItemsStarting - item 开始被拖动时所触发的事件(事件参数 DragItemsStartingEventArgs)
 *      
 * DragItemsStartingEventArgs
 *     Items - 被拖动的 items 集合
 *     Cancel - 是否取消拖动操作
 *     Data - 一个 DataPackage 类型的对象,用于传递数据(与 DataPackage 在剪切板和 Share Contract 中的作用一样)
 *     
 * 
 * 注:
 * drag-drop 间传递数据,clipboard 间传递数据,Share Contract 间传递数据,以及其他场景的数据传递均可通过 DataPackage 类型的对象来完成
 * 本例没有通过 DataPackage 来传递数据(太麻烦),而是通过一个私有字段来传递数据(比较简单)
 */

using System.Collections.ObjectModel;
using Windows.UI.Xaml.Controls;
using System.Linq;
using XamlDemo.Model;
using Windows.UI.Xaml;
using System.Diagnostics;

namespace XamlDemo.Controls.GridView
{
    public sealed partial class DragItem : Page
    {
        // 数据源
        private ObservableCollection<Employee> _dataSource;
        // 拖动中的 Employee 对象
        private Employee _draggingEmployee;

        public DragItem()
        {
            this.InitializeComponent();
            
            // 绑定数据
            _dataSource = new ObservableCollection<Employee>(TestData.GetEmployees());
            gridView.ItemsSource = _dataSource;

            // GridView 中的 items 发生变化时触发的事件
            gridView.ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged;
        }

        void ItemContainerGenerator_ItemsChanged(object sender, Windows.UI.Xaml.Controls.Primitives.ItemsChangedEventArgs e)
        {
            if (e.OldPosition.Index > -1)
            {
                // 在 GridView 中 drop 了 item,且排序发生了变化

                var oldIndex = _dataSource.IndexOf(_draggingEmployee); // 被拖动的 Employee 对象的原位置
                var newIndex = e.Position.Index + e.Position.Offset; // 被拖动的 Employee 对象的新位置

                // 修改数据源
                _dataSource.Move(oldIndex, newIndex);

                _draggingEmployee = null;
            }
            
        }

        // GridView 中的 item 开始被拖动时
        private void gridView_DragItemsStarting_1(object sender, DragItemsStartingEventArgs e)
        {
            _draggingEmployee = e.Items.First() as Employee;
        }


        // GridView 中的 item 被 drop 到了指定的位置后
        private void gridDelete_Drop_1(object sender, DragEventArgs e)
        {
            // 从数据源中删除指定的 Employee 对象
            _dataSource.Remove(_draggingEmployee);
            _draggingEmployee = null;

            // 在 gridDelete 放下了拖动项
            Debug.WriteLine("Drop");
        }

        private void gridDelete_DragEnter_1(object sender, DragEventArgs e)
        {
            // 拖动项被拖进 gridDelete 了
            Debug.WriteLine("DragEnter");
        }

        private void gridDelete_DragLeave_1(object sender, DragEventArgs e)
        {
            // 拖动项被拖出 gridDelete 了
            Debug.WriteLine("DragLeave");
        }

        private void gridDelete_DragOver_1(object sender, DragEventArgs e)
        {
            // 拖动项在 gridDelete 上面拖动着
            Debug.WriteLine("DragOver");
        }
    }
}


2、演示如何指定 GirdView 中每个 item 所占尺寸
GridView/ColorModel.cs

using Windows.UI.Xaml.Media;

namespace XamlDemo.Controls.GridView
{
    /// <summary>
    /// 用于绑定到 VariableSized.xaml 中的 GridView 的数据实体模型
    /// </summary>
    class ColorModel
    {
        public string ColorName { get; set; }
        public SolidColorBrush ColorValue { get; set; }

        // 此对象所占的网格的列合并数
        public int ColSpan { get; set; }
        // 此对象所占的网格的行合并数
        public int RowSpan { get; set; }
    }
}

GridView/MyGridView.cs

/*
 * 此控件可以指定 GridView 的每个 item 所占网格的列合并数和行合并数
 */

using System;
using System.Diagnostics;

namespace XamlDemo.Controls.GridView
{
    public class MyGridView : Windows.UI.Xaml.Controls.GridView
    {
        protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
        {
            try
            {
                // 指定 VariableSizedWrapGrid 的 ColumnSpan 和 RowSpan

                dynamic dynamicItem = item;
                element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, dynamicItem.ColSpan);
                element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.RowSpanProperty, dynamicItem.RowSpan);
            }
            catch(Exception ex)
            {
                // 当有异常情况发生时(比如:item 没有 ColSpan 属性或 RowSpan 属性)

                Debug.WriteLine(ex.ToString());

                element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, 1);
                element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.RowSpanProperty, 1);
            }
            finally
            {
                base.PrepareContainerForItemOverride(element, item);
            }
        }
    }
}

GridView/VariableSized.xaml

<Page
    x:Class="XamlDemo.Controls.GridView.VariableSized"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:XamlDemo.Controls.GridView"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.Resources>
        <DataTemplate x:Key="ItemTemplate">
            <Grid Background="{Binding ColorValue}">
                <Grid Background="Black" VerticalAlignment="Top" HorizontalAlignment="Stretch" Opacity="0.7">
                    <TextBlock Text="{Binding ColorName}" />
                </Grid>
            </Grid>
        </DataTemplate>
        <Style x:Key="ItemContainerStyle" TargetType="GridViewItem">
            <Setter Property="VerticalContentAlignment" Value="Stretch" />
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
            <!--
                即使将 Margin 设置为“0”,也无法去掉 item 之间的 margin
                如果想要去掉 item 之间的 margin,请将此 Margin 属性设置为“-4”
            -->
            <Setter Property="Margin" Value="-4" />
        </Style>
        <ItemsPanelTemplate x:Key="ItemsPanel">
            <!--
                注:WrapGrid 继承了 VirtualizingPanel,而 VariableSizedWrapGrid 并未继承 VirtualizingPanel
            
                ItemWidth, ItemHeight - 每个网格的宽和高
                ColumnSpan, RowSpan - item 所在网格的列合并数和行合并数,本例在后台指定了这两个属性,参见 MyGridView.cs
            -->
            <VariableSizedWrapGrid MaximumRowsOrColumns="4" Orientation="Vertical" ItemWidth="100" ItemHeight="100" Height="400" />
        </ItemsPanelTemplate>

    </Page.Resources>

    <Grid Background="Transparent">
        <StackPanel Margin="120 0 0 0">

            <!--
                使用 MyGridView 控件,其重写了 GridView 的 PrepareContainerForItemOverride() 方法,详见 MyGridView.cs
            -->
            <local:MyGridView x:Name="gridView" Height="400" VerticalAlignment="Top" Margin="0 10 10 0" Background="Yellow"
                              ItemTemplate="{StaticResource ItemTemplate}"
                              ItemContainerStyle="{StaticResource ItemContainerStyle}"
                              ItemsPanel="{StaticResource ItemsPanel}" 
                              IsItemClickEnabled="False" IsSwipeEnabled="False" SelectionMode="None"
                              ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto">
            </local:MyGridView>

        </StackPanel>
    </Grid>
</Page>

GridView/VariableSized.xaml.cs

/*
 * 演示如何指定 GirdView 中每个 item 所占尺寸
 * GridView 是一个网格控件,这里所谓的每个 item 所占尺寸,其本质上就是 item 所在网格的列合并数和行合并数
 * 
 * 要实现此需求的话:
 * 1、必须使用 VariableSizedWrapGrid,具体见 VariableSized.xaml
 * 2、需要重写 GridView 的 PrepareContainerForItemOverride() 方法,具体见 MyGridView.cs
 */

using System.Reflection;
using System.Linq;
using System.Collections.Generic;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using System;
using Windows.UI;

namespace XamlDemo.Controls.GridView
{
    public sealed partial class VariableSized : Page
    {
        public VariableSized()
        {
            this.InitializeComponent();

            BindData();
        }

        private void BindData()
        {
            Random random = new Random();

            // 获取 Windows.UI.Colors 的全部数据
            List<ColorModel> colors = typeof(Colors)  // typeof 在 System.Reflection 命名空间下
                .GetRuntimeProperties()
                .Select(c => new ColorModel
                {
                    ColorName = c.Name,
                    ColorValue = new SolidColorBrush((Color)c.GetValue(null)),
                    ColSpan = random.Next(1, 3), // 此对象所占网格的列合并数
                    RowSpan = random.Next(1, 3) // 此对象所占网格的行合并数
                })
                .ToList();

            // 绑定数据
            gridView.ItemsSource = colors;
        }
    }
}


3、演示如何分组显示集合数据(关于分组的示例会和之后的 SemanticZoom 一起写)
GridView/Group.xaml

<Page
    x:Class="XamlDemo.Controls.GridView.Group"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:XamlDemo.Controls.GridView"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="Transparent">
        <StackPanel Margin="120 0 0 0">

            <TextBlock Name="lblMsg" FontSize="14.667">
                <Run>关于 GridView 的分组显示请参见本 app 的索引页 Index.xaml 和 Index.xaml.cs</Run>
                <LineBreak />
                <Run>分组的功能来源于 ItemsControl(GridView, ListView, FlipView, ListBox 等均继承了 ItemsControl)</Run>
            </TextBlock>

        </StackPanel>
    </Grid>
</Page>



OK
[源码下载]