I’ve seen examples of the new WinRT GridView in a bunch of the samples and demos from Microsoft, but had never used the control for my own app before. During the past couple weeks of RECESS, I’ve been writing a Ping Pong results tracking app with a couple co-workers, and set out to use the GridView in grouped mode. (Yes, PING PONG – we got a new table at the office a few months ago, and the competition is getting intense. We need to start recording these results!)
As with almost all demos from Microsoft, the app uses some not-so-real-world techniques: manipulating UI from code-behind, and creating the data source from in-memory data stores. I’m using an MVVM design and getting real data from a real (MongoDB backed) web service. This is just a quick post on what did and did not work for me when learning the GridView via trial and error.
For all the code below, I’m using a simple hierarchical data structure: Teams that contain a collection Players.
Bind GridView Directly to a VM Collection – Does Not Work
Teams = DataModel.GetTeams();
<GridView ItemsSource="{Binding Path=Teams}" Margin="5" >
This works (well, it displays the outermost collection of objects, but doesn’t do any grouping). For the GridView to use grouped data, you must provide a CollectionViewSource with IsSourceGrouped set to true. I can’t find anywhere that you can tell the GridView directly that it’s ItemsSource contains grouped data.
Create CollectionViewSource Manually – Does Not Work
I’ve seen the CollectionViewSource created as a resource in the UI View in all the samples, but since I’m using MVVM, I want to bind to VM properties. I try to create and store a CollectionViewSource in the VM and bind straight to it.
Teams = DataModel.GetTeams(); CVS = new CollectionViewSource { Source = Teams, IsSourceGrouped = true, };
<GridView ItemsSource="{Binding Path=CVS}" Margin="50" >
I get an ArgumentException in the CollectionViewSource property setter (via the NotifyPropertyChanged event). This occurs when the GridView that is bound to that CVS doesn’t like what it sees.
Bind Resource to VM Property – Works
Here’s a compromise, I never realized you could do this. Declare the CollectionViewSource in XAML (set IsSourceGrouped), but bind it’s source to the VM property. Notice I’m also able to set ItemsPath to describe what property in the parent type holds the children.
<Page.Resources> <CollectionViewSource x:Key="GroupedData" IsSourceGrouped="True" Source="{Binding Teams}" ItemsPath="Players" /> </Page.Resources> <GridView ItemsSource="{Binding Source={StaticResource GroupedData}}" Margin="50" SelectionMode="Single" >
Teams = DataModel.GetTeams();
Use a LINQ Group By Directly – Works
All the samples I’ve seen use a LINQ query to fetch grouped results, and then push those results into a custom data structure. I originally thought you had to do this for the GridView to understand the grouping, but it’s not required. I see a bunch of discussions where the collection must implement a certain interface, or where you must write your own wrapper classes that derive from IGrouping, etc. but those don’t seem to be required either.
Here I’m setting my grouped LINQ query as the property that the GridView will bind to (again via the CollectionViewSource resource). Notice a couple things:
- I’m unnecessarily using LINQ to group my results even though my data is already in a hierarchy (this is just to prove a point)
- You can project the LINQ results into either concrete types or anonymous types. The GridView, as with any other WinRT UI element, is happy to bind to anonymous types (I remember this was a problem in early versions of WinRT, but seems to be fixed now)
- The LINQ group container is a string/collection dictionary (Team.Name to Teams in my case), so I cheat a little and call .First() to get to the one and only Team in each group, since I know I have unique team names.
Teams = DataModel.GetTeams(); GroupedLinq = from team in Teams group team by team.Name into g //select new Team //{ // Name = g.Key, // Players = new List<Player>( g.First().Players ) //}; select new { Name = g.Key, Players = g.First().Players };
<CollectionViewSource x:Key="GroupedLinqData" IsSourceGrouped="True" Source="{Binding GroupedLinq}" ItemsPath="Players" /> <GridView ItemsSource="{Binding Source={StaticResource GroupedLinqData}}" Margin="50" SelectionMode="Single" > <GridView.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Path=Name}" Width="200" Margin="5" /> </DataTemplate> </GridView.ItemTemplate> <GridView.GroupStyle> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> <TextBlock Text="{Binding Path=Name}" FontSize="36" /> </DataTemplate> </GroupStyle.HeaderTemplate> <GroupStyle.Panel> <ItemsPanelTemplate> <VariableSizedWrapGrid Orientation="Vertical" Height="200" /> </ItemsPanelTemplate> </GroupStyle.Panel> </GroupStyle> </GridView.GroupStyle> </GridView>
Summary
I hope these examples help you get through the learning curve of the GridView faster than I did.