引言

相信大家对GridView都不陌生,是非常有用的控件,用于平铺有序的显示多个内容项。打开任何WinRT应用或者是微软合作商的网站,都会在APP中发现GridView的使用。“Tiles”提供了一个简单易用,平铺方式来组织内容显示。Windows8的开始菜单是最典型的GridView 示例。“开始菜单”显示了系统中安装的所有应用程序,而且支持重新排列。

本文源于我们项目的开发人员,他们想在项目中提供与GridView相同的用户体验,想要创建类GridView控件。

GridView 可以显示大小不定的内容项,并且以有序的方式显示。如果各个内容项无序,并且内容尺寸大小相同,GirdView还支持拖拽操作。然而,这些功能并不是默认提供的,需要编写一定的代码才能实现。

本文主要介绍了扩展GridView控件——称为GridViewEx, GridViewEx主要实现GridView在不同大小的内容项中的拖拽功能。

背景

首先了解GridView的基本属性和功能,GridView包含一些属性集和 ItemTemplate。为了实现通过拖拽操作执行重排列功能,必须完成以下三件事:

1. 设置AllowDrop属性为true;

2. 设置CanReorderItems 属性值为True;

3. 绑定数据源,该数据源必须支持数据修改或支持重排序。例如,使用ObservableCollection或IList数据源。

<GridView ItemsSource="{Binding}" AllowDrop="True" CanReorderItems="True">
<GridView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Aqua" BorderThickness="1" Background="Peru">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding}"/>
<TextBlock Grid.Row="1">item</TextBlock>
</Grid>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>

扩展后的GridView使用拖拽操作将会非常方便快捷。

GridViewEx 控件

GridViewEx控件弥补了GridView,功能如下:

  • 实现Item Panel 拖拽操作,而不是WrapGrid,StackPanel、VirtualizingStackPanel等
  • 分组时,实现拖拽功能;

我们也为GridViewEx增加了新建分组的功能,如果用户将内容项拖到控件左边或右边时会触发新建分组操作。

实现拖拽代码:

   1:  public class GridViewEx : GridView
   2:  {
   3:      /// <summary>
   4:      /// Initializes a new instance of the <see cref="GridViewEx"/> control.
   5:      /// </summary>
   6:      public GridViewEx()
   7:      {
   8:          // see attached sample
   9:      }
  10:   
  11:      private void GridViewEx_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
  12:      {
  13:          // see attached sample
  14:      }
  15:   
  16:      /// <summary>
  17:      /// Stores dragged items into DragEventArgs.Data.Properties["Items"] value.
  18:      /// Override this method to set custom drag data if you need to.
  19:      /// </summary>
  20:      protected virtual void OnDragStarting(DragItemsStartingEventArgs e)
  21:      {
  22:          // see attached sample
  23:      }

该控件包含几个变量,用来存储拖放内容的索引。OnDragStarting 事件在DragEventArgs.Data.Properties[“Items”] 中存储拖拽的内容。OnDragStarting 需要根据自己的需求重写。

当用户拖拽某一项内容时,需要给用户提示来引导用户将内容放在合适的位置上。标准的GriView对象是通过滑动相邻的内实项来实现的。本文将在GridViewEx中完善此操作。

   1:  /// <summary>
   2:  /// Shows reoder hints while custom dragging.
   3:  /// </summary>
   4:  protected override void OnDragOver(DragEventArgs e)
   5:  {
   6:      // see attached sample }
   7:   
   8:  private int GetDragOverIndex(DragEventArgs e)
   9:  {
  10:      // see attached sample 
  11:  }

实现拖放代码

首先需要重写GridView.OnDrop方法,该方法会当用户释放某一项内容时触发。重写Ondrop方法,代码如下:

   1:  /// <summary>
   2:  /// Handles drag and drop for cases when it is not supported by the Windows.UI.Xaml.Controls.GridView control
   3:  /// </summary>
   4:  protected override async void OnDrop(DragEventArgs e)
   5:  {
   6:      // see attached sample
   7:  }

OnDrop方法主要实现了内容项从源分组移到目标分组的逻辑代码,以及创建新分组的功能。

添加新分组

如果GrideView通过IsSourceGrouped值为True来绑定CollectionViewSource情况下,GridView提供分组功能,这就意味着分组必须对数据源进行分组,但GridView没有访问数据的权限。因此本文在执行拖放操作时,实现添加新分组功能。GridViewEx.BeforeDrop事件处理此需求,并且提供更多的数据信息,如DragEventArgs数据。

当用户执行拖放操作时,触发BeforeDrop 事件。

   1:  /// <summary>
   2:  /// Occurs before performing drop operation,
   3:  /// </summary>
   4:  public event EventHandler<BeforeDropItemsEventArgs> BeforeDrop;
   5:  /// <summary>
   6:  /// Rises the <see cref="BeforeDrop"/> event.
   7:  /// </summary>
   8:  /// <param name="e">Event data for the event.</param>
   9:  protected virtual void OnBeforeDrop(BeforeDropItemsEventArgs e)
  10:  {
  11:      // see attached sample 
  12:  }

BeforeDropItemEventArgs包含关于被拖拽的内容项的重要信息,该信息在OnDrop事件中可使用的。

   1:  /// <summary>
   2:  /// Provides data for the <see cref="GridViewEx.BeforeDrop"/> event.
   3:  /// </summary>
   4:  public sealed class BeforeDropItemsEventArgs : System.ComponentModel.CancelEventArgs
   5:  {
   6:      /// <summary>
   7:      /// Gets the item which is being dragged.
   8:      /// </summary>
   9:      public object Item
  10:      {
  11:          get;
  12:      }
  13:      /// <summary>
  14:      /// Gets the current item index in the underlying data source.
  15:      /// </summary>
  16:      public int OldIndex
  17:      {
  18:          get;
  19:      }
  20:      /// <summary>
  21:      /// Gets the index in the underlying data source where
  22:      /// the item will be inserted by the drop operation.
  23:      /// </summary>
  24:      public int NewIndex
  25:      {
  26:          get;
  27:      }
  28:      /// <summary>
  29:      /// Gets the bool value determining whether end-user actions requested
  30:      /// creation of the new group in the underlying data source.
  31:      /// This property only makes sense if GridViewEx.IsGrouping property is true.
  32:      /// </summary>
  33:      /// <remarks>
  34:      /// If this property is true, create the new data group and insert it into
  35:      /// the groups collection at the positions, specified by the 
  36:      /// <see cref="BeforeDropItemsEventArgs.NewGroupIndex"/> property value.
  37:      /// Then the <see cref="GridViewEx"/> will insert dragged item
  38:      /// into the newly added group.
  39:      /// </remarks>
  40:      public bool RequestCreateNewGroup
  41:      {
  42:          get;
  43:      }
  44:      /// <summary>
  45:      /// Gets the current item data group index in the underlying data source.
  46:      /// This property only makes sense if GridViewEx.IsGrouping property is true.
  47:      /// </summary>
  48:      public int OldGroupIndex
  49:      {
  50:          get;
  51:      }
  52:      /// <summary>
  53:      /// Gets the data group index in the underlying data source
  54:      /// where the item will be inserted by the drop operation.
  55:      /// This property only makes sense if GridViewEx.IsGrouping property is true.
  56:      /// </summary>
  57:      public int NewGroupIndex
  58:      {
  59:          get;
  60:      }
  61:      /// <summary>
  62:      /// Gets the original <see cref="DragEventArgs"/> data. 
  63:      /// </summary>
  64:      public DragEventArgs DragEventArgs
  65:      {
  66:          get;
  67:      }
  68:  }

AllowNewGroup属性确定用户拖拽某一内容项到控件边界时,是否创建新组。GridView并没有提供此功能,在GridViewEX添加此功能。

   1:  /// <summary>
   2:  /// Gets or sets the value determining whether new group should be created at 
   3:  /// dragging the item to the empty space.
   4:  /// </summary>
   5:  public bool AllowNewGroup
   6:  {
   7:      get { return (bool)GetValue(AllowNewGroupProperty); }
   8:      set { SetValue(AllowNewGroupProperty, value); }
   9:  }
  10:   
  11:  /// <summary>
  12:  /// Identifies the <see cref="AllowNewGroup"/> dependency property.
  13:  /// </summary>
  14:  public static readonly DependencyProperty AllowNewGroupProperty =
  15:          DependencyProperty.Register("AllowNewGroup", typeof(bool), 
  16:          typeof(GridViewEx), new PropertyMetadata(false));

为了在拖拽过程中添加分组,需要将AllowNewGroup属性设置为True。处理GridViewEx.BeforeDrop事件,该事件的参数能够帮助决定单项内容的起始位置和目的位置。在BeforeDrop事件的Handler中,使用 NewGroupIndex 创建新的数据组,并插入到已有组集合。最后,需要实现的扩展GridView控件模板。在用户可拖拽的项目的位置创建新分组,并使用占位符来代替。一旦用户拖某一内容放置到控件的边界时,触发创建新分组,ItemsPresenter的两个边界元素是新组的占位符。

GridViewEx控件模板generic.xaml,如下:

   1:  <Style TargetType="local:GridViewEx">
   2:      <Setter Property="Padding" Value="0,0,0,10" />
   3:      <Setter Property="IsTabStop" Value="False" />
   4:      <Setter Property="TabNavigation" Value="Once" />
   5:      <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
   6:      <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled"/>
   7:      <Setter Property="ScrollViewer.HorizontalScrollMode" Value="Enabled" />
   8:      <Setter Property="ScrollViewer.IsHorizontalRailEnabled" Value="False" />
   9:      <Setter Property="ScrollViewer.VerticalScrollMode" Value="Disabled" />
  10:      <Setter Property="ScrollViewer.IsVerticalRailEnabled" Value="False" />
  11:      <Setter Property="ScrollViewer.ZoomMode" Value="Disabled" />
  12:      <Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False" />
  13:      <Setter Property="ScrollViewer.BringIntoViewOnFocusChange" Value="True" />
  14:      <Setter Property="IsSwipeEnabled" Value="True" />
  15:      <Setter Property="Template">
  16:          <Setter.Value>
  17:              <ControlTemplate TargetType="local:GridViewEx">
  18:                  <Border BorderBrush="{TemplateBinding BorderBrush}"
  19:                          Background="{TemplateBinding Background}"
  20:                          BorderThickness="{TemplateBinding BorderThickness}">
  21:                      <ScrollViewer x:Name="ScrollViewer"
  22:                              TabNavigation="{TemplateBinding TabNavigation}"
  23:                              HorizontalScrollMode="
  24:                              {TemplateBinding ScrollViewer.HorizontalScrollMode}"
  25:                              HorizontalScrollBarVisibility=
  26:                                "{TemplateBinding 
  27:                                ScrollViewer.HorizontalScrollBarVisibility}"
  28:                              IsHorizontalScrollChainingEnabled=
  29:                                "{TemplateBinding 
  30:                                ScrollViewer.IsHorizontalScrollChainingEnabled}"
  31:                              VerticalScrollMode="
  32:                              {TemplateBinding ScrollViewer.VerticalScrollMode}"
  33:                              VerticalScrollBarVisibility=
  34:                                "{TemplateBinding 
  35:                                ScrollViewer.VerticalScrollBarVisibility}"
  36:                              IsVerticalScrollChainingEnabled=
  37:                                "{TemplateBinding 
  38:                                ScrollViewer.IsVerticalScrollChainingEnabled}"
  39:                              IsHorizontalRailEnabled="
  40:                              {TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
  41:                              IsVerticalRailEnabled="
  42:                              {TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
  43:                              ZoomMode="{TemplateBinding 
  44:                              ScrollViewer.ZoomMode}"
  45:                              IsDeferredScrollingEnabled="
  46:                              {TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
  47:                              BringIntoViewOnFocusChange="
  48:                              {TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}">
  49:                          <StackPanel Orientation="Horizontal">
  50:                              <Border Width="60" 
  51:                              x:Name="NewGroupPlaceHolderFirst" 
  52:                                      Background="Transparent" 
  53:                                      Padding="{TemplateBinding Padding}" 
  54:                                      Visibility="{Binding AllowNewGroup, 
  55:                                      Converter={StaticResource 
  56:                                        VisibilityConverter}, 
  57:                                        RelativeSource={RelativeSource TemplatedParent}}"/>
  58:                              <ItemsPresenter 
  59:                                  Header="{TemplateBinding Header}" 
  60:                                  HeaderTemplate="{TemplateBinding HeaderTemplate}"
  61:                                  HeaderTransitions="{TemplateBinding HeaderTransitions}"
  62:                                  Padding="{TemplateBinding Padding}"/>
  63:                              <Border Width="60" 
  64:                              x:Name="NewGroupPlaceHolderLast" 
  65:                                      Background="Transparent" 
  66:                                      Padding="{TemplateBinding Padding}" 
  67:                                      Visibility="{Binding AllowNewGroup, 
  68:                                      Converter={StaticResource 
  69:                                        VisibilityConverter}, 
  70:                                        RelativeSource={RelativeSource TemplatedParent}}"/>
  71:                          </StackPanel>
  72:                      </ScrollViewer>
  73:                  </Border>
  74:              </ControlTemplate>
  75:          </Setter.Value>
  76:      </Setter>
  77:  </Style>

丰富GridViewEx功能

如上所示,我们已经实现了基本的拖拽操作,与Windows8 开始菜单类似的功能,接下来讨论如何实现以下功能:

  • 尺寸大小不定的内容项
  • 分组
  • 添加新组
  • 节省跨会话布局空间

大小不定的内容项

Windows8展示了不同大小的Tiles,但是目前GridView或GridViewEx还不支持此功能。因为GridView使用WrapGrid作为默认的ItemsPanel,WrapPanel只能创建一种布局,即所有的条目尺寸相同的。因此微软提供了VariableSizedWrapGrid,支持不同大小块的布局创建。

GridViewEx控件的优势在于能够使用VariableSizedWrapGrid,并且很好的支持拖放操作。为了使用VariableSizedWrapGrid 并显示不同大小的内容项,必须实现以下功能:

将GridViewEx.ItemsPanel 设置为VariableSizedWrapGrid

在GridView中重写GridView 的PrepareContainerForItemOverride 方法。在该方法中,可以设置Item的RowSpan或ColumnSpan属性来识别内容项的大小。

即生成继承GridViewEx的新控件MyGridView。为什么需要扩展GridViewEx控件而不是重写GridViewEx的PrepareContainerForItemOverride方法?因为指定Item尺寸的逻辑必须放在数据模型中,而不是控件内部。

如想将某一项显示较大一点,需要在数据项中创建一个属性返回比1大的整型数值,来设置RowSpanhuoColumnSpan属性。

   1:  public class Item
   2:  {
   3:      public int Id { get; set; }
   4:      public int ItemSize { get; set; }
   5:      /* */
   6:  }

因此,当创建新的内容项,我们要指定ItemSize属性。如果值为1则表明常规尺寸,如果值为2则表明大尺寸,ColumnSpan属性则设置为2。

   1:  /// <summary>
   2:  /// This class sets VariableSizedWrapGrid.ColumnSpanProperty for GridViewItem controls, 
   3:  /// so that every item can have different size in the VariableSizedWrapGrid.
   4:  /// </summary>
   5:  public class MyGridView : GridViewSamples.Controls.GridViewEx
   6:  {
   7:      // set ColumnSpan according to the business logic
   8:      // (maybe some GridViewSamples.Samples.Item or group properties)
   9:      protected override void PrepareContainerForItemOverride(
  10:                Windows.UI.Xaml.DependencyObject element, object item)
  11:      {
  12:          try
  13:          {
  14:              GridViewSamples.Samples.Item it = item as GridViewSamples.Samples.Item;
  15:              if (it != null)
  16:              {
  17:                  element.SetValue(
  18:                    Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, it.ItemSize);
  19:              }
  20:          }
  21:          catch
  22:          {
  23:              element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, 1);
  24:          }
  25:          finally
  26:          {
  27:              base.PrepareContainerForItemOverride(element, item);
  28:          }
  29:      }
  30:  }

创建MyGridView实例,并绑定到数据集合。

   1:  <local:MyGridView AllowDrop="True" CanReorderItems="True" 
   2:            CanDragItems="True" IsSwipeEnabled="True"
   3:            ItemsSource="{Binding}" 
   4:            ItemTemplate="{StaticResource ItemTemplate}" >
   5:      <GridView.ItemsPanel>
   6:          <ItemsPanelTemplate>
   7:              <VariableSizedWrapGrid ItemHeight="160" 
   8:              ItemWidth="160" />
   9:          </ItemsPanelTemplate>
  10:      </GridView.ItemsPanel>
  11:      <GridView.ItemContainerStyle>
  12:          <Style TargetType="GridViewItem">
  13:              <Setter Property="HorizontalContentAlignment" 
  14:              Value="Stretch"/>
  15:              <Setter Property="VerticalContentAlignment" 
  16:              Value="Stretch"/>
  17:          </Style>
  18:      </GridView.ItemContainerStyle>
  19:  </local:MyGridView>

如上所示,我们将指定内容项的ItemSize属性设置为2,效果如图所示:

分组

使用GridViewEx控件,能够实现添加新分组和拖拽等功能,也是在App中最为常见的功能,实现分组必须完成以下设置:

  • 为GridView绑定CollectionViewSource,必须使用支持分组的数据源。CollectionViewSource可视为代理服务器。
  • 使用GroupStyle确定分组结果如何显示,GroupStyle包含Header Tempate及Panel,因此需要指定子项目的排序方式。

在GridViewEx中添加支持不同大小的内容项,逻辑代码:

   1:  <local:MyGridView AllowDrop="True" CanReorderItems="True" 
   2:            CanDragItems="True" IsSwipeEnabled="True"
   3:            ItemsSource="{Binding}" 
   4:            ItemTemplate="{StaticResource ItemTemplate}" >
   5:      <GridView.ItemsPanel>
   6:          <ItemsPanelTemplate>
   7:              <VirtualizingStackPanel Orientation="Horizontal"/>
   8:          </ItemsPanelTemplate>
   9:      </GridView.ItemsPanel>
  10:      <GridView.GroupStyle>
  11:          <GroupStyle>
  12:              <GroupStyle.HeaderTemplate>
  13:                  <DataTemplate>
  14:                      <Grid Background="LightGray" 
  15:                      Margin="0">
  16:                          <TextBlock Foreground="Black" 
  17:                          Margin="10" 
  18:                                    Style="{StaticResource 
  19:                                    GroupHeaderTextStyle}">
  20:                              <Run Text="{Binding Id}"/>
  21:                              <Run Text=" group"/>
  22:                          </TextBlock>
  23:                      </Grid>
  24:                  </DataTemplate>
  25:              </GroupStyle.HeaderTemplate>
  26:   
  27:              <GroupStyle.ContainerStyle>
  28:                  <Style TargetType="GroupItem">
  29:                      <Setter Property="BorderBrush" 
  30:                      Value="DarkGray"/>
  31:                      <Setter Property="BorderThickness" 
  32:                      Value="2"/>
  33:                      <Setter Property="Margin" 
  34:                      Value="3,0"/>
  35:                  </Style>
  36:              </GroupStyle.ContainerStyle>
  37:   
  38:              <GroupStyle.Panel>
  39:                  <ItemsPanelTemplate>
  40:                      <VariableSizedWrapGrid ItemHeight="160" 
  41:                      ItemWidth="160" />
  42:                  </ItemsPanelTemplate>
  43:              </GroupStyle.Panel>
  44:          </GroupStyle>
  45:      </GridView.GroupStyle>
  46:   
  47:      <GridView.ItemContainerStyle>
  48:          <Style TargetType="GridViewItem">
  49:              <Setter Property="HorizontalContentAlignment" 
  50:              Value="Stretch"/>
  51:              <Setter Property="VerticalContentAlignment" 
  52:              Value="Stretch"/>
  53:          </Style>
  54:      </GridView.ItemContainerStyle>
  55:  </local:MyGridView>

运行演示:

添加新分组

自定义的GridViewEx控件支持新分组的创建,因此需要设置AllowNewGroup为True。其次处理添加新分组的数据层,处理GridViewEx.BeforeDrop 事件。

   1:  /// <summary>
   2:  /// Creates new CollectionViewSource and updates page DataContext.
   3:  /// </summary>
   4:  private void UpdateDataContext()
   5:  {
   6:      CollectionViewSource source = new CollectionViewSource();
   7:      source.Source = _groups;
   8:      source.ItemsPath = new PropertyPath("Items");
   9:      source.IsSourceGrouped = true;
  10:      this.DataContext = source;
  11:  }
  12:  // creates new group in the data source,
  13:  // if end-user drags item to the new group placeholder
  14:  private void MyGridView_BeforeDrop(object sender, Controls.BeforeDropItemsEventArgs e)
  15:  {
  16:      if (e.RequestCreateNewGroup)
  17:      {
  18:          // create new group and re-assign datasource 
  19:          Group group = Group.GetNewGroup();
  20:          if (e.NewGroupIndex == 0)
  21:          {
  22:              _groups.Insert(0, group);
  23:          }
  24:          else
  25:          {
  26:              _groups.Add(group);
  27:          }
  28:          UpdateDataContext();
  29:      }
  30:  }

也可以使用Drop事件删除空分组

   1:  // removes empty groups (except the last one)
   2:  private void MyGridView_Drop(object sender, DragEventArgs e)
   3:  {
   4:      bool needReset = false;
   5:      for (int i = _groups.Count - 1; i >= 0; i--)
   6:      {
   7:          if (_groups[i].Items.Count == 0 && _groups.Count > 1)
   8:          {
   9:              _groups.RemoveAt(i);
  10:              needReset = true;
  11:          }
  12:      }
  13:      if (needReset)
  14:      {
  15:          UpdateDataContext();
  16:      }
  17:  }

节省布局空间

Windows8支持挂起或终止功能,为了提供更好的用户体验,我们继续改善此前实现的功能,当用户离开当前页面,将当前的布局暂存。在本示例中,我们使用JSON 字符串简化数据序列化。根据已有的数据、数据的大小及需求,以其他格式来保存数据。我们主要将“业务对象集合”保存。

为了节省布局空间。重写LayoutAwarePage方法:

   1:  /// <summary>
   2:  /// Populates the page with content passed during navigation.  Any saved state is also
   3:  /// provided when recreating a page from a prior session.
   4:  /// </summary>
   5:  /// <param name="navigationParameter">The parameter value passed to
   6:  /// <see cref="Frame.Navigate(Type, 
   7:  /// Object)"/> when this page was initially requested.
   8:  /// </param>
   9:  /// <param name="pageState"
  10:  /// >A dictionary of state preserved by this page during an earlier
  11:  /// session.  This will be null the first time a page is visited.</param>
  12:  protected override void LoadState(Object navigationParameter, 
  13:      Dictionary<String, Object> pageState)
  14:  {
  15:      base.LoadState(navigationParameter, pageState);
  16:      if (pageState != null && pageState.Count > 0 
  17:      && pageState.ContainsKey("Groups"))
  18:      {
  19:          // restore groups and items from the previously serialized state
  20:          System.Runtime.Serialization.Json.DataContractJsonSerializer rootSer = 
  21:          new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(List<Group>));
  22:          var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes
  23:              ((string)pageState["Groups"]));
  24:          _groups = (List<Group>)rootSer.ReadObject(stream);
  25:      }
  26:      else
  27:      {
  28:          // if we get here for the first time and don't have
  29:          // serialized content, fill groups and items from scratch
  30:          for (int j = 1; j <= 12; j++)
  31:          {
  32:              Group group = Group.GetNewGroup();
  33:              for (int i = 1; i <= 7 + j % 3; i++)
  34:              {
  35:                  group.Items.Add(new Item()
  36:                  {
  37:                      Id = i,
  38:                      GroupId = group.Id
  39:                  });
  40:              }
  41:              _groups.Add(group);
  42:          }
  43:      }
  44:      UpdateDataContext();
  45:  }
  46:   
  47:  /// <summary>
  48:  /// Preserves state associated with this page in case the application is suspended or the
  49:  /// page is discarded from the navigation cache.  Values must conform to the serialization
  50:  /// requirements of <see cref="SuspensionManager.SessionState"/>.
  51:  /// </summary>
  52:  /// <param name="pageState">
  53:  /// An empty dictionary to be populated with serializable state.</param>
  54:  protected override void SaveState(Dictionary<String, Object> pageState)
  55:  {
  56:      // save groups and items to JSON string so that 
  57:      // it's possible to restore page state later
  58:      base.SaveState(pageState);
  59:      System.Runtime.Serialization.Json.DataContractJsonSerializer rootSer = 
  60:          new System.Runtime.Serialization.Json.DataContractJsonSerializer
  61:          (typeof(List<Group>));
  62:      var stream = new MemoryStream();
  63:      rootSer.WriteObject(stream, _groups);
  64:      string str = System.Text.Encoding.UTF8.GetString(stream.ToArray(), 
  65:              0, (int)stream.Length);
  66:      pageState.Add("Groups", str);
  67:  }
  68:   
  69:  /// <summary>
  70:  /// Invoked when this page is about to be displayed in a Frame.
  71:  /// </summary>
  72:  /// <param name="e">Event data that describes 
  73:  /// how this page was reached.  The Parameter
  74:  /// property is typically used to configure the page.</param>
  75:  protected override void OnNavigatedTo(NavigationEventArgs e)
  76:  {
  77:      // restore page state
  78:      var frameState = 
  79:      GridViewSamples.Common.SuspensionManager.SessionStateForFrame(this.Frame);
  80:      if (frameState.ContainsKey("TilePageData"))
  81:      {
  82:          this.LoadState(e.Parameter, 
  83:          (Dictionary<String, Object>)frameState["TilePageData"]);
  84:      }
  85:      else
  86:      {
  87:          this.LoadState(e.Parameter, null);
  88:      }
  89:  }
  90:   
  91:  protected override void OnNavigatedFrom(NavigationEventArgs e)
  92:  {
  93:      // save page state with "TilePageData" key
  94:      var frameState = 
  95:      GridViewSamples.Common.SuspensionManager.SessionStateForFrame(this.Frame);
  96:      var pageState = new Dictionary<String, Object>();
  97:      this.SaveState(pageState);
  98:      frameState["TilePageData"] = pageState;
  99:  }

总结

GridViewEx控件丰富了GirdView控件功能,改进了基础功能,提升用户体验。到此已经实现了GridView项与Windows8开始菜单具有的相同用户体验,如果你想了解如何在Windows10平台下开发UWP引用,请持续关注下篇文章:如何在Windows10中开发UWP应用

除了 GirdView 以外,具备触摸和键盘导航操作的自动或手动平铺布局的控件还有ComponentOne TileControl for WinForms,它不但提供自适应Windows8的样式布局,还具有类似Windows8风格的交互体验和灵活便捷的定制能力。

原文链接:http://www.codeproject.com/Articles/536519/Extending-GridView-with-Drag-and-Drop-for-Grouping

扩展GridView控件——为内容项添加拖放及分组功能的更多相关文章

  1. 扩展WebBrowser控件,使其支持拖放文件

    public partial class UserControl1 : WebBrowser { private const int WmDropfiles = 0x233; [DllImport(& ...

  2. 027. asp.net中数据绑定控件之 GridView控件

    GridView控件支持下面的功能: 绑定至数据源控件, 如SqlDataSource 内置排序功能 内置更新和删除功能 内置分页功能 内置行选择功能 可以编程方式访问GridView对象模型以动态设 ...

  3. 在GridView控件FooterTemplate内添加记录 Ver2

    中午有发表一篇博文<在GridView控件FooterTemplate内添加记录> http://www.cnblogs.com/insus/p/3269908.html 添加铵钮是放在F ...

  4. 在aspx页动态加载ascx页面内容,给GridView控件绑定数据

    在aspx页动态加载ascx页面内容 //加载ascx页面内容Control c1 = this.Page.LoadControl("WebUserControl1.ascx"); ...

  5. 在GridView控件FooterTemplate内添加记录 Ver3

    重构此篇<在GridView控件FooterTemplate内添加记录 Ver2> http://www.cnblogs.com/insus/p/3270644.html 这有些缺陷,怎样 ...

  6. 在GridView控件FooterTemplate内添加记录

    在GridView控件FooterTemplate内添加记录,想实现这个功能,有几点要清楚的,这个添加铵钮是在FooterTemplate内,还是在GridView控件外部,位置不同,某些处理逻辑会有 ...

  7. GridView控件

    GridView是ASP.NET 1.x的DataGrid控件的后继者.它提供了同样的基本功能集,同一时候添加�了大量扩展和改进.如前所述,DataGrid(ASP.NET 2.0仍然全然支持)是一个 ...

  8. Repeater, DataList, 和GridView控件的区别

    http://blog.sina.com.cn/s/blog_646dc75c0100h5p6.html http://www.cnblogs.com/phone/archive/2010/09/15 ...

  9. GridView控件的属性、事件

    GridView控件的属性 属性 描述 AllowPaging 指示该控件是否支持分页. AllowSorting 指示该控件是否支持排序. AutoGenerateColumns 指示是否自动地为数 ...

随机推荐

  1. QQ邮箱发送邮件,出现mail from address must be same as authorization user错误

    之前做的一个系统,有个发送邮件的功能,一直能正常使用,今天同事说QQ邮箱发送不了. 立马着手调试,发现服务器一直出现“mail from address must be same as authori ...

  2. 【Mail】Tomcat提供JNDI方式支持JavaMail(三)

    流程介绍 Tomcat提供了JavaMail的支持,是通过JNDI的方式实现的,具体流程是: Tomcat启动的时候,自身产生一个Session对象,放在JNDI容器中给其他项目调用,其他项目只要通过 ...

  3. MyBatis-NET

    http://www.codeproject.com/Articles/894127/MyBatis-NET https://mybatis.github.io/mybatis-3/

  4. 安卓使用adb命令安装软件

    准备工作: 确信 \Android-sdk-windows\tools\下有 adb.exe     AdbWinApi.dll     AdbWinUsbApi.dll 三个文件,如果没有从\and ...

  5. Trie树的创建、插入、查询的实现

    原文:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28977986&id=3807947 1.什么是Trie树 Tr ...

  6. Html/Css(新手入门第三篇)

    一.学习心得---参考优秀的网页来学习. 1我们只做的静态网页主要用的技术?html+css 只要网上看到的,他的源代码公开的.[1].先去分析,他们页面是如何布局(结构化)[2].再试着去做一下,- ...

  7. Win7下安装一个装逼文本编辑器Atom + activate-power-mode插件

    Atom是Github推出的一个文本编辑器,搜索一下大概是给Web前端用的,最近比较火的是他的一个插件activate-power-mode,可以实现打字屏振效果. 用来装装逼还是挺适合的,本来想试试 ...

  8. winfrom调用FastReport模板

    '存放模板的路径 If Dir(Application.StartupPath & "\ReportsFile\HB\01\IN.frx", FileAttribute.N ...

  9. C# 计算字符串在控制台中的显示长度

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  10. mysql 数值函数

    可使用常见的算术操作符.注意就 -. +和 *而言, 若两个参数均为正数,则其计算结果的精确度为 BIGINT (64比特),若其中一个参数为无符号整数, 而其它参数也是整数, 则结果为无符号整数.请 ...