• 扩展GridView控件——为内容项添加拖放及分组功能


    引言

    相信大家对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

  • 相关阅读:
    ps -aux --sort -rss |head 列出进程拿物理内存占用排序 使用ps aux 查看系统进程时,第六列即 RSS列显示的就是进程使用的物理内存。
    13 memcache服务检查
    shell 颜色
    expr判断整数是相加的值,返回命令的返回值$? 是0,但是少数情况是1,例如1 + -1 ,$? 的结果是1 ,判断要大于1最准确
    ZABBIX监控原理
    ansible分发密钥
    再来一个expect脚本
    11:菜单自动化软件部署经典案例
    19:批量检查多个网站地址是否正常
    数组迭代
  • 原文地址:https://www.cnblogs.com/powertoolsteam/p/GridViewEx.html
Copyright © 2020-2023  润新知