65.9K
CodeProject 正在变化。 阅读更多。
Home

LINQ、group by 和 WPF 数据绑定

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.57/5 (8投票s)

2008年10月14日

CPOL

3分钟阅读

viewsIcon

69057

downloadIcon

620

WPF 数据绑定与 LINQ 配合得非常好!本文将介绍如何使用 LINQ 的 group by 子句创建分层结果集,以及如何在 WPF 数据绑定中使用它。

引言

LINQ 为 C# 编程语言带来了许多出色的功能,而这些功能是数据库开发人员多年来一直知晓的。在某些方面,LINQ 远远超出了 SQL 所提供的功能。其中一个例子是 group by 子句。在 SQL 中,group 操作的结果返回一个表——除此之外不可能有其他结果,因为 SQL 不了解类的概念。相反,LINQ 的 group by 操作可以创建分层结果结构。本文将介绍如何使用 group by 编写 LINQ 查询,更重要的是,还将演示如何在 WPF 中使用数据绑定来使用该结果。

LINQ 查询

在示例中,我们假设我们收集计算机上运行的程序的事件状态。对于每个事件,我们可以收集进程 ID、进程描述(例如,exe 的文件名)以及事件时间。以下类充当事件的容器

public class Event
{
  public int PID { get; set; }
  public string Desc { get; set; }
  public DateTime EventTime { get; set; }
}

以下代码行生成了一些演示数据

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) }
};

正如你所见,进程的主数据被多次存储(即,数据结构不处于范式)。我们的 LINQ 查询应该返回一个分层结果,其中每个进程只包含一次。此外,对于每个进程,我们希望有一个相应的事件集合。解决此问题的 LINQ 查询如下所示

var result =
  from d in data
  group d by new { d.PID, d.Desc } into pg
  select new { Process = pg.Key, Items = pg };

注意 group by 子句的写法以及结果(匿名类型)的构建方式。该查询按进程 ID 和进程描述对结果进行分组。这两个字段共同构成了复合分组表达式(new { d.PID, d.Desc })。pg 的类型为 IGrouping<TKey, TElement>TKey 表示前面提到的分组表达式。IGrouping 实现 IEnumerable。因此,pg 可以在 select 子句中使用,以便为每个组嵌入相应的 Event 对象列表。

请注意,结果的匿名类型包含每个列的名称(Process = pg.Key, Items = pg)。这很重要,因为没有名称,WPF 中的数据绑定会困难得多(事实上,我不知道没有名称是否可能)。

以下是在 Visual Studio 调试器中查看结果的方式

WPF 数据绑定

在我的示例中,我也想在用户界面中表示分层结果结构。因此,以下示例应该为每个键创建一个展开器控件。在展开器内部,它应该显示一个列表框,其中包含相应键的事件详细信息。以下是我想要实现的结果的屏幕截图

LinqGroupWpfScreen.png

使用数据绑定创建展开器控件非常简单

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

正如你所见,我使用自定义 ItemsPanel 水平显示展开器控件。数据模板将每个结果对象转换为展开器控件。为了使数据绑定生效,我们不能忘记设置 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;

在此基础上,我们可以使用 LINQ 查询生成的层级结果,为每个展开器控件插入事件列表,而无需编写任何额外的 C# 代码

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

请注意,列表框 SubListBox 绑定到 ItemsItems 已定义为 LINQ 查询结果类型中的一个字段。它包含每个分组键对应的事件列表。通过这样绑定,我们可以在列表框的 DataTemplate 中访问 Event 的属性。

摘要

在我看来,此示例的重要收获是

  1. LINQ 是一个强大的工具,不仅在与数据库结合使用时有用。它还可以更轻松地处理内存中的对象结构。
  2. LINQ 查询可以创建比 SQL 更复杂的结果结构。
  3. WPF 数据绑定与 LINQ 结果配合得非常好,即使是分层结果结构。
© . All rights reserved.