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).

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.

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.

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>protectedoverrideasyncvoid 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.

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 <seecref="GridViewEx.BeforeDrop"/> event.
///</summary>publicsealedclass BeforeDropItemsEventArgs : System.ComponentModel.CancelEventArgs
{
///<summary>/// Gets the item which is being dragged.
///</summary>publicobject Item
{
get;
}
///<summary>/// Gets the current item index in the underlying data source.
///</summary>publicint OldIndex
{
get;
}
///<summary>/// Gets the index in the underlying data source where
/// the item will be inserted by the drop operation.
///</summary>publicint 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
///<seecref="BeforeDropItemsEventArgs.NewGroupIndex"/> property value.
/// Then the <seecref="GridViewEx"/> will insert dragged item
/// into the newly added group.
///</remarks>publicbool 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>publicint 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>publicint NewGroupIndex
{
get;
}
///<summary>/// Gets the original <seecref="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.

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.

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.

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.

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.

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.

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):

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

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 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.

Comments and Discussions

Nice post. I have downloaded your sample source code and try to open in VS2015 community edition but it's not open the solution. It asks to install the windows phone 8.1 or 8.0 SDK. I have tried to do that but unable to install it. So, I have downloaded VS2017 community edition and tried the same with the phone 8.1 SDK but still it's not working.

Could you please let me know what are the SDK required to install to open your source and review the codes?

it is based on MS GridView control which is not present in desktop platforms. Sure it's possible to do on other platforms, but it's not an easy task to make it as an open source sample.
As far as I know, some 3rd-party control vendors do have similar things for WinForms and WPF if you are ready to pay.

I have a question: I want to have 4 groups in the customized version of yours. 3 groups are normal draggable as you have shown.
But one group should contain subgroups arranged vertically similar to an expandable grid or tree.
Further, each of the subgroups contain dragable items that can be moved to one of the other 3 groups. The headers of the subgroups should be fixed.

Question is: How to prevent specific items from being dragged? I have just found the global CanDragItems and I cannot avoid the VisualStates for that item in particular.

It works for me.
Can you explain what exactly wrong? Which sample, on which platform (Windows 8 or 8.1), did you port project to W8.1? It would be nice to have exact repro steps, expected and actual results.

Sample without live tiles, customized.xaml page. Drag the tile to the right edge of the screen, it might scroll now and then a little bit (if you move tile constantly back and forth), but it won't scroll properly. This is with a mouse, I am unsure if it works with a touch.

I see. You are right. But I did nothing about scrolling, all you see is implemented in MS GridView. If you move item too far, scrolling stops. When scrolling starts, don't move tile farther, just hold it in the same place, GridView will continue scrolling.

- reordering works randomly, sometimes the item gets inserted in a totally different place than where it was dropped (usually on top of next column)
- sometimes 2x2 tiles gets reduced to 1x1 tiles when inserted between 1x2 tiles. This bug makes it impossible to use in any pro application.

I suppose, our drag&drop implementation might not honor something in your scenario. But it definitely doesn't change tile size, it should be something in your code. If you give me the sample, reproducing the problem, and exact repro steps, I'll play with it when I have some time. You can write me and attach zipped sample with source code to IrinaP at ComponentOne.com

First of all, congratulations for this awesome control!
I've been working on some project and this is exactly what I needed, but I've encountered a small bug and I can't seem to fix it!

I'm storing an UserControl as a Item property, so I can dynamically load diferent content based on the Item size. The problem is that, when I try to reorder an Item, after the drop, somethimes the UserControl doesn't load, even though the item is there!

I've also added some Strings as an Item's property, and that string allways shows, but the UserControl doesn't!

Miguel, it's hard to tell something without seeing the code. It would be nice if you can send me the simple sample, reproducing your issue, so that I can play with it. Or maybe just your application if it's not too complex. Zip source code and send it to IrinaP at ComponentOne.com

apparently, when GridViewItemsSource is updated after dragging, your custom control gets unloaded and doesn't loded again in the newly created GridViewItem. Try to use DataTemplates instead of UserControls in the Item class:

Thanks a lot for helping me with this issue!
For now, your solution will do just fine, the only problem is that I will need to dynamically load the widgets without modifying my container class (in this case that classe is the Customized.xaml), wich is not possible if i need to define the DataTemplates in the xaml.

you can define templates in some other place. Consider the next scenario:
- place all DataTemplates into application resources (App.xaml);
- move all initialization of the Item class instances out of Customized.xaml.cs. In the current version, it can be done in some method in the Group class. But in real application you might decide to do that in some other place.
- initialize properties of Item class with DataTemplates from application resources:

good question. Should be doable, something like this:
- define custom control template for GroupItem, inlcuding placeholder for new group. So, you'll have placeholders after every group;
- handle placeholders in GridViewEx.OnDrop method.
I made very simple implementation. Will update attached samples today, so you can take a look.