![]() |
Platforms, Frameworks & Libraries »
Windows Presentation Foundation »
Data Binding
Beginner
License: The Code Project Open License (CPOL)
LINQ, group by, and WPF Data BindingBy r.stropekWPF data binding works great with LINQ! This article shows how to create a hierarchical result set by using LINQ's group by clause and how to consume it in WPF data binding. |
C# (C# 1.0, C# 2.0, C# 3.0), Windows (WinXP, Vista), .NET (.NET 3.0, .NET 3.5), WPF, LINQ, Silverlight, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

LINQ introduced great things to the C# programming language, things that database developers have known for years. In some areas, LINQ goes far beyond what is available from SQL. One example is the group by clause. In SQL, the result of a group operation returns a table - nothing else is possible since SQL does not know the notion of classes. In contrast, LINQ's group by operation can create hierarchical result structures. This article shows how to write a LINQ query using group by and - even more importantly - demonstrates how you can consume the result in WPF using data binding.
In the sample, we assume that we collect status events from programs running on a computer. For every event, we could collect the process ID, a process description (e.g., the file name of the exe), and the event time. The following class acts as a container for events:
public class Event
{
public int PID { get; set; }
public string Desc { get; set; }
public DateTime EventTime { get; set; }
}
The following line of code generates some demo data:
var data = new List<Event>()
{
new Event() { PID = 1, Desc="Process A", EventTime = DateTime.Now.AddHours(-1) },
new Event() { PID = 1, Desc="Process A", EventTime = DateTime.Now.AddHours(-2) },
new Event() { PID = 2, Desc="Process B", EventTime = DateTime.Now.AddHours(-3) },
new Event() { PID = 2, Desc="Process B", EventTime = DateTime.Now.AddHours(-4) },
new Event() { PID = 3, Desc="Process C", EventTime = DateTime.Now.AddHours(-5) }
};
As you can see, the master data about the processes is stored multiple times (i.e., the data structure is not in normal form). Our LINQ query should return a hierarchical result in which every process is included only once. Additionally, for every process, we want to have a collection of the corresponding events. The LINQ query solving this problem looks like this:
var result =
from d in data
group d by new { d.PID, d.Desc } into pg
select new { Process = pg.Key, Items = pg };
Note how the group by clause is written and how the result (anonymous type) is built. The query groups the result by process ID and process description. Both fields together make up the composite group expression (new { d.PID, d.Desc }). pg is of type IGrouping<TKey, TElement>. TKey represents the group expression mentioned before. IGrouping implements IEnumerable. Therefore, pg can be used in the select clause to embed the list of corresponding Event objects for each group.
Note that the anonymous type for the result contains names for each column (Process = pg.Key, Items = pg). This is important because without the names, data binding in WPF is much harder (in fact, I do not know whether it is possible without names at all).
Here is how the result looks like in the Visual Studio debugger:
In my example, I want to represent the hierarchical result structure in the user interface, too. Therefore, the following sample should create an expander control for each key. Inside the expander, it should display a listbox with the event details for the corresponding key. Here is a screenshot of the result I want to achieve:

Using data binding to create the expander controls is quite straightforward:
<Window x:Class="WpfApplication5.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="550">
<Grid>
<ItemsControl x:Name="TopLevelListBox" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander ExpandDirection="Down" Width="175">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Process.PID}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Path=Process.Desc}" />
</StackPanel>
</Expander.Header>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
As you can see, I use a custom ItemsPanel to display the expander controls horizontally. The data template converts each result object into the expander control. To make data binding work, we must not forget to set the data context for the ItemsControl:
var result =
from d in data
group d by new { d.PID, d.Desc } into pg
select new { Process = pg.Key, Items = pg };
TopLevelListBox.DataContext = result;
Based on that, we can use the hierarchical result generated by the LINQ query to insert the list of events per expander control, without writing a single line of extra C# code:
<Window x:Class="WpfApplication5.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="550">
<Grid>
<ItemsControl x:Name="TopLevelListBox" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander ExpandDirection="Down" Width="175">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Process.PID}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Path=Process.Desc}" />
</StackPanel>
</Expander.Header>
<ListBox x:Name="SubListBox" ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=EventTime}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Note that the listbox SubListBox is bound to Items. Items has been defined as a field in the result type of the LINQ query. It contains the list of events corresponding to each group key. By binding like this, we can access the properties of Event inside the DataTemplate of the listbox.
In my opinion, the important takeaways of this sample are:
| You must Sign In to use this message board. | ||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 14 Oct 2008 Editor: Smitha Vijayan |
Copyright 2008 by r.stropek Everything else Copyright © CodeProject, 1999-2009 Web18 | Advertise on the Code Project |