Extending GridView with Drag and Drop for Grouping and Variable Sized Items






4.98/5 (23 votes)
This article describes the implementation of an extended GridView control that enables drag and drop with grouping and variable sized items.
Introduction
The GridView
is a great control that can be used in many different ways to display tiled content in your Windows Store apps. If you’ve looked at any WinRT applications lately, or even Microsoft Partner websites, you’ll recognize the popularity of tiles when it comes to UI design in the Windows World. Tiles provide a simple, sleek way to organize a list of items or navigation areas of your application. Perhaps the greatest example of tiled content is the Windows 8 start screen itself. It displays each of your apps in a sizable tile that can be rearranged and grouped to the users’ desire.
As is typical with native applications, we developers want to emulate the same experience within our own applications. This imitation goes back to the early days of Windows and has been a consistent approach to user interfaces. If you’re trying to emulate the Windows 8 start screen in your own Windows Store application, the GridView
control is a great place to start.
The GridView
can display variable sized tiles and group them for you, or it can display non-grouped items of the same size with support for drag and drop. Unfortunately, you can’t have everything enabled by default. For instance, you don’t get drag and drop for all items panels. Certain items panels are necessary if you want a mixture of different sized items (i.e., VariableSizedWrapGrid
). Also drag and drop is not supported when grouping is enabled.
This article describes the implementation of an extended GridView
control, GridViewEx
, which removes these limitations. The sample provided enables you to deliver drag and drop in a GridView
that has support for grouping and variable sized items.
If you are developing for Universal Windows Platform (UWP) under Windows 10, please use updated version from the new article How to Upgrade Extended GridView from WinRT to Universal Windows Platform (UWP)
Background
First, let’s see how we can enable drag and drop in the simplest scenario. Here, we have a GridView
with minimal properties set and a very basic ItemTemplate
. To enable drag-and-drop reordering, you need to do three things:
- Set the
AllowDrop
property totrue
. - Set the
CanReorderItems
property totrue
. - Bind to a data source that supports data modification, or specifically reordering. For example, you could use something like
ObservableCollection
orIList
(Note: UnboundGridView
s also support reordering).
<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>
You will notice that very easily we have some level of drag-and-drop support in our GridView
.
As mentioned earlier, there are a couple of major limitations to enabling drag and drop for both bound and unbound scenarios. Specifically, you can’t have grouping enabled or a mix of variable sized items. If you look at the Windows 8 Start Screen, you will notice that there is grouping, differently sized items, and drag and drop. If you’re really trying to emulate this experience, you will want two or three of these features combined. How can we implement all of these features in a GridView
? We will need to extend the control to support these other scenarios. Now let’s take a look at the GridViewEx
control.
The GridViewEx Control
The GridViewEx
control implements drag and drop for cases which are not supported by the regular GridView
control:
- For items panels other than
WrapGrid
,StackPanel
, andVirtualizingStackPanel
- When grouping is enabled
It also allows adding new groups to the underlying data source if the user drags some item to the left-most or right-most edges of the control.
Dragging Code
Let’s look at the control implementation and how we handle dragging items.
public class GridViewEx : GridView
{
/// <summary>
/// Initializes a new instance of the <see cref="GridViewEx"/> control.
/// </summary>
public GridViewEx()
{
// see attached sample
}
private void GridViewEx_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
{
// see attached sample
}
/// <summary>
/// Stores dragged items into DragEventArgs.Data.Properties["Items"] value.
/// Override this method to set custom drag data if you need to.
/// </summary>
protected virtual void OnDragStarting(DragItemsStartingEventArgs e)
{
// see attached sample
}
The control has several fields which store the indices of several active items during the drag/drop process. The OnDragStarting
event stores dragged items into the DragEventArgs.Data.Properties[“Items”]
value. You would override this method to set custom drag data if you need to.
When the user drags an item, we need to show hints as to where the item will be placed if dropped. The standard GridView
handles this by sliding adjacent items out of the way. We will implement this exact behavior ourselves in GridViewEx
because we need to account for cases where GridView
does not support dropping.
/// <summary>
/// Shows reoder hints while custom dragging.
/// </summary>
protected override void OnDragOver(DragEventArgs e)
{
// see attached sample }
private int GetDragOverIndex(DragEventArgs e)
{
// see attached sample
}
OnDragOver
applies reorder hints when an item is dragged over neighboring items. The neighboring items are calculated from the GetIntersectingItems
method. There are five possible ReorderHintStates
to set depending on the location of each item:
NoReorderHint
BottomReorderHint
TopReorderHint
RightReorderHint
LeftReorderHint
Dropping Code
Next, let’s look at the code that handles dropping.
We have to override GridView.OnDrop
method which is called every time when an end-user drops an item to the new location. Our override handles dropping for any ItemsPanel
that the standard GridView
does not support dropping.
/// <summary>
/// Handles drag and drop for cases when it is not supported by the Windows.UI.Xaml.Controls.GridView control
/// </summary>
protected override async void OnDrop(DragEventArgs e)
{
// see attached sample
}
The OnDrop
method includes logic for moving items from one group to another when grouping is enabled, and for new group creation if it is requested by end-user actions.
Adding New Groups
The GridView
supports grouping if it is bound to the CollectionViewSource
with the IsSourceGrouped
property set to true
. That means that the grouping logic should be implemented on the data source level and GridView
has no access to it. Here, we see that to add new groups during the drag-and-drop operation, we need something more than the standard Drop
event. The GridViewEx.BeforeDrop
event allows us to handle this situation and supplies more information including the original DragEventArgs
data.
The BeforeDrop
event occurs before the user performs a drop operation.
/// <summary>
/// Occurs before performing drop operation,
/// </summary>
public event EventHandler<BeforeDropItemsEventArgs> BeforeDrop;
/// <summary>
/// Rises the <see cref="BeforeDrop"/> event.
/// </summary>
/// <param name="e">Event data for the event.</param>
protected virtual void OnBeforeDrop(BeforeDropItemsEventArgs e)
{
// see attached sample
}
The BeforeDropItemEventArgs
carries important information about the item being dragged so that it can be accessed later in the OnDrop
event.
/// <summary>
/// Provides data for the <see cref="GridViewEx.BeforeDrop"/> event.
/// </summary>
public sealed class BeforeDropItemsEventArgs : System.ComponentModel.CancelEventArgs
{
/// <summary>
/// Gets the item which is being dragged.
/// </summary>
public object Item
{
get;
}
/// <summary>
/// Gets the current item index in the underlying data source.
/// </summary>
public int OldIndex
{
get;
}
/// <summary>
/// Gets the index in the underlying data source where
/// the item will be inserted by the drop operation.
/// </summary>
public int NewIndex
{
get;
}
/// <summary>
/// Gets the bool value determining whether end-user actions requested
/// creation of the new group in the underlying data source.
/// This property only makes sense if GridViewEx.IsGrouping property is true.
/// </summary>
/// <remarks>
/// If this property is true, create the new data group and insert it into
/// the groups collection at the positions, specified by the
/// <see cref="BeforeDropItemsEventArgs.NewGroupIndex"/> property value.
/// Then the <see cref="GridViewEx"/> will insert dragged item
/// into the newly added group.
/// </remarks>
public bool RequestCreateNewGroup
{
get;
}
/// <summary>
/// Gets the current item data group index in the underlying data source.
/// This property only makes sense if GridViewEx.IsGrouping property is true.
/// </summary>
public int OldGroupIndex
{
get;
}
/// <summary>
/// Gets the data group index in the underlying data source
/// where the item will be inserted by the drop operation.
/// This property only makes sense if GridViewEx.IsGrouping property is true.
/// </summary>
public int NewGroupIndex
{
get;
}
/// <summary>
/// Gets the original <see cref="DragEventArgs"/> data.
/// </summary>
public DragEventArgs DragEventArgs
{
get;
}
}
The AllowNewGroup
property determines whether new groups should be created if the user drags an item to the far edges of the control. This feature is not supported in the standard GridView
under any scenario so it’s a nice added benefit of the GridViewEx
class.
/// <summary>
/// Gets or sets the value determining whether new group should be created at
/// dragging the item to the empty space.
/// </summary>
public bool AllowNewGroup
{
get { return (bool)GetValue(AllowNewGroupProperty); }
set { SetValue(AllowNewGroupProperty, value); }
}
/// <summary>
/// Identifies the <see cref="AllowNewGroup"/> dependency property.
/// </summary>
public static readonly DependencyProperty AllowNewGroupProperty =
DependencyProperty.Register("AllowNewGroup", typeof(bool),
typeof(GridViewEx), new PropertyMetadata(false));
To allow new group creation with drag-and-drop operations, you should set the AllowNewGroup
property to true
. To handle adding new groups to the data layer, you should handle the GridViewEx.BeforeDrop
event. The event arguments help determine the item’s origin and destination. Within the BeforeDrop
event handler, you can create the new data group and insert it into the group’s collection at the position specified by the argument’s NewGroupIndex
property.
The last thing necessary for adding the new group feature is extending the default GridView
control template. We need a filler or placeholder, where the user can drag an item to create a new group. The GridViewEx
control template supports adding new groups if the user drags some item to the left-most or right-most edge of control. So, two border elements on either end of the ItemsPresenter
are placeholders for the new groups.
The GridViewEx
control template from generic.xaml.
<Style TargetType="local:GridViewEx">
<Setter Property="Padding" Value="0,0,0,10" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="TabNavigation" Value="Once" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ScrollViewer.HorizontalScrollMode" Value="Enabled" />
<Setter Property="ScrollViewer.IsHorizontalRailEnabled" Value="False" />
<Setter Property="ScrollViewer.VerticalScrollMode" Value="Disabled" />
<Setter Property="ScrollViewer.IsVerticalRailEnabled" Value="False" />
<Setter Property="ScrollViewer.ZoomMode" Value="Disabled" />
<Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False" />
<Setter Property="ScrollViewer.BringIntoViewOnFocusChange" Value="True" />
<Setter Property="IsSwipeEnabled" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:GridViewEx">
<Border BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer x:Name="ScrollViewer"
TabNavigation="{TemplateBinding TabNavigation}"
HorizontalScrollMode="
{TemplateBinding ScrollViewer.HorizontalScrollMode}"
HorizontalScrollBarVisibility=
"{TemplateBinding
ScrollViewer.HorizontalScrollBarVisibility}"
IsHorizontalScrollChainingEnabled=
"{TemplateBinding
ScrollViewer.IsHorizontalScrollChainingEnabled}"
VerticalScrollMode="
{TemplateBinding ScrollViewer.VerticalScrollMode}"
VerticalScrollBarVisibility=
"{TemplateBinding
ScrollViewer.VerticalScrollBarVisibility}"
IsVerticalScrollChainingEnabled=
"{TemplateBinding
ScrollViewer.IsVerticalScrollChainingEnabled}"
IsHorizontalRailEnabled="
{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
IsVerticalRailEnabled="
{TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
ZoomMode="{TemplateBinding
ScrollViewer.ZoomMode}"
IsDeferredScrollingEnabled="
{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
BringIntoViewOnFocusChange="
{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}">
<StackPanel Orientation="Horizontal">
<Border Width="60"
x:Name="NewGroupPlaceHolderFirst"
Background="Transparent"
Padding="{TemplateBinding Padding}"
Visibility="{Binding AllowNewGroup,
Converter={StaticResource
VisibilityConverter},
RelativeSource={RelativeSource TemplatedParent}}"/>
<ItemsPresenter
Header="{TemplateBinding Header}"
HeaderTemplate="{TemplateBinding HeaderTemplate}"
HeaderTransitions="{TemplateBinding HeaderTransitions}"
Padding="{TemplateBinding Padding}"/>
<Border Width="60"
x:Name="NewGroupPlaceHolderLast"
Background="Transparent"
Padding="{TemplateBinding Padding}"
Visibility="{Binding AllowNewGroup,
Converter={StaticResource
VisibilityConverter},
RelativeSource={RelativeSource TemplatedParent}}"/>
</StackPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
These new template parts should be defined in our code too.
[TemplatePart(Name = GridViewEx.NewGroupPlaceHolderFirstName, Type = typeof(FrameworkElement))]
[TemplatePart(Name = GridViewEx.NewGroupPlaceHolderLastName, Type = typeof(FrameworkElement))]
public class GridViewEx : GridView
{
private const string NewGroupPlaceHolderFirstName = "NewGroupPlaceHolderFirst";
private FrameworkElement _newGroupPlaceHolderFirst;
private const string NewGroupPlaceHolderLastName = "NewGroupPlaceHolderLast";
private FrameworkElement _newGroupPlaceHolderLast;
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
_newGroupPlaceHolderFirst =
GetTemplateChild(NewGroupPlaceHolderFirstName) as FrameworkElement;
_newGroupPlaceHolderLast =
GetTemplateChild(NewGroupPlaceHolderLastName) as FrameworkElement;
}
Using and Extending GridViewEx
Right now, we have a drag-and-drop GridView
solution that at first appears identical to the standard GridView
. Our goal is for it to behave more like the Windows 8 start screen. Let’s discuss how to implement the following features that would otherwise be unsupported:
- Variable Sized Items
- Grouping
- Adding new groups
- Saving layout across sessions
Variable Sized Items
The Windows 8 start screen shows tiles of various sizes (well, two sizes exactly). If you try to make items of different sizes in a default GridView
or GridViewEx
, it won’t work. That’s because the GridView
uses a WrapGrid
as its default ItemsPanel
. WrapGrid
creates a uniform layout where each item is of the same size. For that reason, Microsoft also includes a VariableSizedWrapGrid
, which as the name implies, supports items of different sizes.
The benefit of the GridViewEx
control is that you can use VariableSizedWrapGrid
and still retain support for drag and drop. To use VariableSizedWrap
grid and display items of various sizes, you must do two things:
- Set the
GridViewEx.ItemsPanel
to an instance ofVariableSizedWrapGrid
. - Override the
PrepareContainerForItemOverride
method on theGridView
. In this method, you set theRowSpan
orColumnSpan
properties on the items to indicate its size.
This means we need to extend the GridViewEx
control with another control named MyGridView
. Why extend GridViewEx
rather than simply override PrepareContainerForItemOverride
within the GridViewEx
class? Because the logic for specifying item size should be in your data model and not within the control itself. That is, if you want to leave GridViewEx
as a versatile control for usage in more than one place.
For instance, say you want specific items to appear larger, you would create a property on your data item that returns an integer value higher than 1
and use that to set the RowSpan
or ColumnSpan
property.
public class Item
{
public int Id { get; set; }
public int ItemSize { get; set; }
/* */
}
Here, when we create each item, we will specify the ItemSize
to be either 1
or 2
indicating regular (1) or larger (2). Larger items will have its ColumnSpan
property set to two so it occupies the space of two items horizontally. You can also set the RowSpan
to make items larger vertically as well.
/// <summary>
/// This class sets VariableSizedWrapGrid.ColumnSpanProperty for GridViewItem controls,
/// so that every item can have different size in the VariableSizedWrapGrid.
/// </summary>
public class MyGridView : GridViewSamples.Controls.GridViewEx
{
// set ColumnSpan according to the business logic
// (maybe some GridViewSamples.Samples.Item or group properties)
protected override void PrepareContainerForItemOverride(
Windows.UI.Xaml.DependencyObject element, object item)
{
try
{
GridViewSamples.Samples.Item it = item as GridViewSamples.Samples.Item;
if (it != null)
{
element.SetValue(
Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, it.ItemSize);
}
}
catch
{
element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, 1);
}
finally
{
base.PrepareContainerForItemOverride(element, item);
}
}
}
Now let’s create a MyGridView
instance and bind it to a collection of items.
<local:MyGridView AllowDrop="True" CanReorderItems="True"
CanDragItems="True" IsSwipeEnabled="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ItemTemplate}" >
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid ItemHeight="160"
ItemWidth="160" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch"/>
<Setter Property="VerticalContentAlignment"
Value="Stretch"/>
</Style>
</GridView.ItemContainerStyle>
</local:MyGridView>
In the code that initializes the collection, we will set the ItemSize
property on certain items (business logic can determine this) to be 2
, thus indicating a larger tile that spans two columns.
Grouping
Using the GridViewEx
control, we can enable grouping and drag and drop together. You can group a GridViewEx
control just as you would enable grouping for the standard GridView
. In fact, the Grid App template that you’ve probably started your application with uses grouping. You implement grouping by doing two things:
- Bind the
GridView
to aCollectionViewSource
with a grouping-enabled data source. Meaning, the data source should contain groups of data such as each item containing a collection of child items. TheCollectionViewSource
acts as a proxy over the collection class to enable grouping. - Specify a
GroupStyle
to determine how groups are displayed.GroupStyle
includes aHeaderTempate
and a Panel that specifies how child items in the group are arranged.
Optionally, you can specify the GroupStyle.ContainerStyle
. This modifies the group container appearance. For example, you could add a border around each group. Let’s add grouping to our MyGridView
implementation of GridViewEx
. Remember, we extended GridViewEx
to add business logic for variable sized items.
<local:MyGridView AllowDrop="True" CanReorderItems="True"
CanDragItems="True" IsSwipeEnabled="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ItemTemplate}" >
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Background="LightGray"
Margin="0">
<TextBlock Foreground="Black"
Margin="10"
Style="{StaticResource
GroupHeaderTextStyle}">
<Run Text="{Binding Id}"/>
<Run Text=" group"/>
</TextBlock>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="BorderBrush"
Value="DarkGray"/>
<Setter Property="BorderThickness"
Value="2"/>
<Setter Property="Margin"
Value="3,0"/>
</Style>
</GroupStyle.ContainerStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid ItemHeight="160"
ItemWidth="160" />
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch"/>
<Setter Property="VerticalContentAlignment"
Value="Stretch"/>
</Style>
</GridView.ItemContainerStyle>
</local:MyGridView>
It’s important to note when grouping is enabled and a GroupStyle
is specified, the ItemsPanel
has a new meaning. We changed ItemsPanel
from a VariableSizedWrapGrid
to a VirtualizingStackPanel
. With grouping, ItemsPanel
refers to how the groups are arranged in the GridView
. Since we want to support different sized items within each group, we moved our VariableSizedWrapGrid
to the GroupStyle.Panel
template.
Run the sample and notice that we have grouping, variable sized items, and now drag and drop between groups thanks to the custom GridViewEx
control.
Adding New Groups
The custom GridViewEx
control also added support for adding new groups when the user drags an item to the far left and right edges of the control. To allow new group creation, set the AllowNewGroup
property to true
. Then to handle adding new groups to the data layer, handle the GridViewEx.BeforeDrop
event. The event arguments help determine the item’s origin and destination. Within the BeforeDrop
event handler, you can create the new data group and insert it into the groups collection at the position specified by the argument’s NewGroupIndex
property. The reason this is left to the developer is because the GridViewEx
control knows nothing about your data structure.
/// <summary>
/// Creates new CollectionViewSource and updates page DataContext.
/// </summary>
private void UpdateDataContext()
{
CollectionViewSource source = new CollectionViewSource();
source.Source = _groups;
source.ItemsPath = new PropertyPath("Items");
source.IsSourceGrouped = true;
this.DataContext = source;
}
// creates new group in the data source,
// if end-user drags item to the new group placeholder
private void MyGridView_BeforeDrop(object sender, Controls.BeforeDropItemsEventArgs e)
{
if (e.RequestCreateNewGroup)
{
// create new group and re-assign datasource
Group group = Group.GetNewGroup();
if (e.NewGroupIndex == 0)
{
_groups.Insert(0, group);
}
else
{
_groups.Add(group);
}
UpdateDataContext();
}
}
We also can use the Drop
event to clear any empty groups.
// removes empty groups (except the last one)
private void MyGridView_Drop(object sender, DragEventArgs e)
{
bool needReset = false;
for (int i = _groups.Count - 1; i >= 0; i--)
{
if (_groups[i].Items.Count == 0 && _groups.Count > 1)
{
_groups.RemoveAt(i);
needReset = true;
}
}
if (needReset)
{
UpdateDataContext();
}
}
Saving Layout Across Sessions
In Windows 8 application can be suspended or terminated when the user switches away from it. For better user experience, our sample stores current layout when end-user navigates to the other page or when application is deactivated. In this sample, we use the simplest data serialization to JSON string. Depending on your data structure, data size and your needs, you can save data in other format and to the other place. In our case, it would be enough to save underlying business objects collection.
To save page layout, we use overrides for the LayoutAwarePage
methods (see comments in code):
/// <summary>
/// Populates the page with content passed during navigation. Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="navigationParameter">The parameter value passed to
/// <see cref="Frame.Navigate(Type,
/// Object)"/> when this page was initially requested.
/// </param>
/// <param name="pageState"
/// >A dictionary of state preserved by this page during an earlier
/// session. This will be null the first time a page is visited.</param>
protected override void LoadState(Object navigationParameter,
Dictionary<String, Object> pageState)
{
base.LoadState(navigationParameter, pageState);
if (pageState != null && pageState.Count > 0
&& pageState.ContainsKey("Groups"))
{
// restore groups and items from the previously serialized state
System.Runtime.Serialization.Json.DataContractJsonSerializer rootSer =
new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(List<Group>));
var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes
((string)pageState["Groups"]));
_groups = (List<Group>)rootSer.ReadObject(stream);
}
else
{
// if we get here for the first time and don't have
// serialized content, fill groups and items from scratch
for (int j = 1; j <= 12; j++)
{
Group group = Group.GetNewGroup();
for (int i = 1; i <= 7 + j % 3; i++)
{
group.Items.Add(new Item()
{
Id = i,
GroupId = group.Id
});
}
_groups.Add(group);
}
}
UpdateDataContext();
}
/// <summary>
/// Preserves state associated with this page in case the application is suspended or the
/// page is discarded from the navigation cache. Values must conform to the serialization
/// requirements of <see cref="SuspensionManager.SessionState"/>.
/// </summary>
/// <param name="pageState">
/// An empty dictionary to be populated with serializable state.</param>
protected override void SaveState(Dictionary<String, Object> pageState)
{
// save groups and items to JSON string so that
// it's possible to restore page state later
base.SaveState(pageState);
System.Runtime.Serialization.Json.DataContractJsonSerializer rootSer =
new System.Runtime.Serialization.Json.DataContractJsonSerializer
(typeof(List<Group>));
var stream = new MemoryStream();
rootSer.WriteObject(stream, _groups);
string str = System.Text.Encoding.UTF8.GetString(stream.ToArray(),
0, (int)stream.Length);
pageState.Add("Groups", str);
}
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes
/// how this page was reached. The Parameter
/// property is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// restore page state
var frameState =
GridViewSamples.Common.SuspensionManager.SessionStateForFrame(this.Frame);
if (frameState.ContainsKey("TilePageData"))
{
this.LoadState(e.Parameter,
(Dictionary<String, Object>)frameState["TilePageData"]);
}
else
{
this.LoadState(e.Parameter, null);
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
// save page state with "TilePageData" key
var frameState =
GridViewSamples.Common.SuspensionManager.SessionStateForFrame(this.Frame);
var pageState = new Dictionary<String, Object>();
this.SaveState(pageState);
frameState["TilePageData"] = pageState;
}
The above code serializes layout to the page state. Then, our sample application uses SuspensionManager
to save this information along with the app state. You can find the full code in the attached samples. If you need more information about saving the app's state, start from MSDN article Manage app lifecycle and state.
Summary
The custom GridViewEx
control enables us to combine several useful features of the GridView
control. Taking advantage of more features allows us to deliver user experiences that are becoming the new norm when it comes to Windows Store app development.
There’s more we can do to our GridView
items to make them behave like the Windows 8 start screen. For instance, the start tiles are “alive” as they rotate through updated content. You can take advantage of third party tile libraries, such as ComponentOne Tiles, and use them within a GridView
to deliver animated tiles that flip and rotate to display live data. A second sample with live tiles is attached as well that shows the C1Tile controls in use with the GridViewEx
control.
Update History
- 31st Jan, 2013: First version
- 26th Mar, 2013: Added the simplest data persistence to store screen layout across sessions
- 16th May, 2013: Added support for new group placeholders between groups. To enable it,
GroupStyle.ContainerStyle
style should define custom control template which includes element withNewGroupPlaceHolder
name (or uncomment it in attached samples) - 2nd Sep, 2015: Samples upgraded to Windows 8.1
- 9th Oct, 2015: Added link to new UWP version (Windows 10)