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.
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 to true.
- Set the
CanReorderItems property to true.
- Bind to a data source that supports data modification, or specifically reordering. For example, you could use something like
ObservableCollection or IList (Note: unbound GridViews 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, and VirtualizingStackPanel.
- 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
{
public GridViewEx()
{
}
private void GridViewEx_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
{
}
protected virtual void OnDragStarting(DragItemsStartingEventArgs e)
{
}
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.
protected override void OnDragOver(DragEventArgs e)
{
private int GetDragOverIndex(DragEventArgs e)
{
}
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.
protected override async void OnDrop(DragEventArgs e)
{
}
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.
public event EventHandler<BeforeDropItemsEventArgs> BeforeDrop;
protected virtual void OnBeforeDrop(BeforeDropItemsEventArgs e)
{
}
The BeforeDropItemEventArgs carries important information about the item being dragged so that it can be accessed later in the OnDrop event.
public sealed class BeforeDropItemsEventArgs : System.ComponentModel.CancelEventArgs
{
public object Item
{
get;
}
public int OldIndex
{
get;
}
public int NewIndex
{
get;
}
public bool RequestCreateNewGroup
{
get;
}
public int OldGroupIndex
{
get;
}
public int NewGroupIndex
{
get;
}
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.
public bool AllowNewGroup
{
get { return (bool)GetValue(AllowNewGroupProperty); }
set { SetValue(AllowNewGroupProperty, value); }
}
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 of VariableSizedWrapGrid.
- Override the
PrepareContainerForItemOverride method on the GridView. In this method you set the RowSpan or ColumnSpan 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.
public class MyGridView : GridViewSamples.Controls.GridViewEx
{
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 a CollectionViewSource 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. The CollectionViewSource acts as a proxy over the collection class to enable grouping.
- Specify a
GroupStyle to determine how groups are displayed. GroupStyle includes a HeaderTempate 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 the 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.
private void UpdateDataContext()
{
CollectionViewSource source = new CollectionViewSource();
source.Source = _groups;
source.ItemsPath = new PropertyPath("Items");
source.IsSourceGrouped = true;
this.DataContext = source;
}
private void MyGridView_BeforeDrop(object sender, Controls.BeforeDropItemsEventArgs e)
{
if (e.RequestCreateNewGroup)
{
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.
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):
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
base.LoadState(navigationParameter, pageState);
if (pageState != null && pageState.Count > 0 && pageState.ContainsKey("Groups"))
{
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
{
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();
}
protected override void SaveState(Dictionary<String, Object> pageState)
{
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);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
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)
{
var frameState = GridViewSamples.Common.SuspensionManager.SessionStateForFrame(this.Frame);
var pageState = new Dictionary<String, Object>();
this.SaveState(pageState);
frameState["TilePageData"] = pageState;
}
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 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
- 31 Jan 2013: The first version
- 26 Mar 2013: Added the simplest data persistence to store screen layout across sessions.
- 16 May 2013: Added support for new group placeholders between groups. To enable it,
GroupStyle.ContainerStyle style should define custom control template which includes element with NewGroupPlaceHolder name (or uncomment it in attached samples).
I am the ComponentOne product manager for Studio for WPF, Silverlight, Windows Phone and WinRT XAML. You'll find me blogging about these awesome XAML technologies and speaking around the country at various code camps, techfests and tradeshows.