目前为止,你已经看到一些示例将控件绑定到一个单独的对象。然而,更复杂的使用是绑定到一个对象列表。例如,想象一下,我们的对象数据源可以创建一个新类型表示Person对象的列表,正如示例4-19:

示例4-19

《Programming WPF》翻译 第4章 3.绑定到数据列表using System.Collections.Generic; // List<T>
《Programming WPF》翻译 第4章 3.绑定到数据列表
《Programming WPF》翻译 第4章 3.绑定到数据列表
}

我们可以挂起这个新的数据源列表,按照同样的方式绑定到它,就像绑定到一个单独的对象数据源上,如示例4-20

示例4-20

《Programming WPF》翻译 第4章 3.绑定到数据列表<!-- Window1.xaml -->
《Programming WPF》翻译 第4章 3.绑定到数据列表
<?Mapping XmlNamespace="local" ClrNamespace="PersonBinding" ?>
《Programming WPF》翻译 第4章 3.绑定到数据列表
<Window 《Programming WPF》翻译 第4章 3.绑定到数据列表 xmlns:local="local">
《Programming WPF》翻译 第4章 3.绑定到数据列表  
<Window.Resources>
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<local:People x:Key="Family">
《Programming WPF》翻译 第4章 3.绑定到数据列表      
<local:Person Name="Tom" Age="9" />
《Programming WPF》翻译 第4章 3.绑定到数据列表      
<local:Person Name="John" Age="11" />
《Programming WPF》翻译 第4章 3.绑定到数据列表      
<local:Person Name="Melissa" Age="36" />
《Programming WPF》翻译 第4章 3.绑定到数据列表    
</local:People>
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<local:AgeToForegroundConverter
《Programming WPF》翻译 第4章 3.绑定到数据列表      
x:Key="AgeToForegroundConverter" />
《Programming WPF》翻译 第4章 3.绑定到数据列表  
</Window.Resources>
《Programming WPF》翻译 第4章 3.绑定到数据列表  
<Grid DataContext="{StaticResource Family}">
《Programming WPF》翻译 第4章 3.绑定到数据列表    《Programming WPF》翻译 第4章 3.绑定到数据列表
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<TextBlock 《Programming WPF》翻译 第4章 3.绑定到数据列表>Name:</TextBlock>
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<TextBox Text="{Binding Path=Name}" 《Programming WPF》翻译 第4章 3.绑定到数据列表 />
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<TextBox
《Programming WPF》翻译 第4章 3.绑定到数据列表      
Text="{Binding Path=Age}"
《Programming WPF》翻译 第4章 3.绑定到数据列表      Foreground
="{Binding Path=Age, Converter=《Programming WPF》翻译 第4章 3.绑定到数据列表}" 《Programming WPF》翻译 第4章 3.绑定到数据列表 />
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<Button 《Programming WPF》翻译 第4章 3.绑定到数据列表>Birthday</Button>
《Programming WPF》翻译 第4章 3.绑定到数据列表  
</Grid>
《Programming WPF》翻译 第4章 3.绑定到数据列表
</Window>

在示例4-20中,我们创建了一个People集合的示例而且通过三个Person对象导入它。然而,运行它将会如图4-6

4.3.1当前项

尽管文本框属性每次仅能被绑定到一个单独的对象上,在可能的被绑定到的对象列表中,绑定引擎提供了一个名为当前项的概念,正如图4-6所解释的。

缺省地,列表的第一项作为当前项的开始。由于我们列表示例的第一项与我们之前绑定的单独对象一样,所以看起来和图4-11显示的一样——Birthday按钮除外。

4-11

《Programming WPF》翻译 第4章 3.绑定到数据列表

4.3.1.1获取当前项

回想当前Birthday按钮的click事件句柄(示例4-21)。

示例4-21

}

我们的Birthday按钮应该总是产生向当前人士祝贺生日的效果,但是到目前为止,当前人士却总是一样的,因此我们只能简化事情为直接到达单独的Person对象。既然我们已经得到了对象的列表,这个机制就不再使用了(除非你认为一个包含单词“InvalidCastException”消息框是可以接受的方式)。进一步而言,转换到People,我们的集合类,不会告诉我们那一个Person对象会在当前UI中显示,因为它不知道这些事情(也不需要知道)。由于这一点,我们将要必须建立“经纪人”在数据绑定的控件和集合项上,这个“经纪人”在这里被称为视图。

视图的工作是在数据之上提供服务,包括排序,过滤,以及此刻对于我们的意图来说最重要的:控制当前项。视图是详细数据的接口实现,在我们这种情形,就是ICollectionView接口。我们可以通过BindingOperations类的静态GetDefaultView方法访问这个数据上的视图,正如示例4-22所示:

示例4-22

}

为了取回联合了Family集合的视图,示例4-22BindingOperationsGetDefaultView方法进行了一次调用,提供了一个ICollectionView接口的实现。基于此,我们可以得到当前项,将它从集合中的一项转换为我们需要的对象(CurrentItem属性返回一个object对象),以及用它来显示。

4.3.1.2在数据项中导航

出了获取当前项外,我们也能改变当前项的位置,通过ICollectionView接口的MoveCurrentToXX方法,正如示例4-23所示。

示例4-23

 

}

ICollectionView接口的MoveCurrentToPrevious方法和MoveCurrentToNext方法,通过在集合中向后和向前的动作改变当前的选中项。如果我们沿着一个方向移动到列表的尽头或另一个尽头,IsCurrentBeforeFirstIsCurrentAfterLast属性将会告诉我们这一点。MoveCurrentToFirstMoveCurrentToLast方法帮助我们复原在到达列表的尽头之后,对于在途4-12中实现BackForward按钮,这将是很有用的。同样适用于FirstLast两个按钮(这将是你的一个机会,将学到的运用上去)。

4-12显示了从集合中第一个Person元素开始,向前移动的效果,包括基于Person对象的Age属性导致的颜色改变(这仍然以同样的方式工作)。

4-12

《Programming WPF》翻译 第4章 3.绑定到数据列表

4.3.2数据列表目标

当然,目前为止,我们仅能做的是把用户列表数据推出来,而没有为这些数据提供一个控件可以准确的一次性显示多条数据,正如示例4-24中的ListBox控件。

示例4-24

 

《Programming WPF》翻译 第4章 3.绑定到数据列表<!-- Window1.xaml -->
《Programming WPF》翻译 第4章 3.绑定到数据列表
<?Mapping XmlNamespace="local" ClrNamespace="PersonBinding" ?>
《Programming WPF》翻译 第4章 3.绑定到数据列表
<Window 《Programming WPF》翻译 第4章 3.绑定到数据列表 xmlns:local="local">
《Programming WPF》翻译 第4章 3.绑定到数据列表  
<Window.Resources>
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<local:People x:Key="Family">《Programming WPF》翻译 第4章 3.绑定到数据列表</local:People>
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<local:AgeToForegroundConverter
《Programming WPF》翻译 第4章 3.绑定到数据列表      
x:Key="AgeToForegroundConverter" />
《Programming WPF》翻译 第4章 3.绑定到数据列表  
</Window.Resources>
《Programming WPF》翻译 第4章 3.绑定到数据列表  
<Grid DataContext="{StaticResource Family}">
《Programming WPF》翻译 第4章 3.绑定到数据列表    《Programming WPF》翻译 第4章 3.绑定到数据列表
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<ListBox
《Programming WPF》翻译 第4章 3.绑定到数据列表      
ItemsSource="{Binding}"
《Programming WPF》翻译 第4章 3.绑定到数据列表      IsSynchronizedWithCurrentItem
="True" 《Programming WPF》翻译 第4章 3.绑定到数据列表 />
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<TextBlock 《Programming WPF》翻译 第4章 3.绑定到数据列表>Name:</TextBlock>
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<TextBox Text="{Binding Path=Name}" 《Programming WPF》翻译 第4章 3.绑定到数据列表 />
《Programming WPF》翻译 第4章 3.绑定到数据列表    《Programming WPF》翻译 第4章 3.绑定到数据列表
《Programming WPF》翻译 第4章 3.绑定到数据列表
</Window>

在示例4-24中,ListBoxItemSource属性没有绑定到路径,等于是说:绑定到当前整个对象。注意到,这里也没有源,因此绑定会从找到的第一个非空的数据上下文开始工作。在这种情形中,第一个非空的数据上下文来自Grid,就是那个在nameage的文本框中共享的上下文。我们还设置了IsSynchronizedWithCurrentItem属性为true,以确保listbox中的选中项也能发生改变——这会在视图中更新当前项;反之亦然。

4-13

《Programming WPF》翻译 第4章 3.绑定到数据列表

正如你可能看到的,图
4-13中的每件事物都很完美。所发生的是,当你绑定一个完整对象时,数据绑定尽其所能显示每一个Person对象。无需特殊的指令,它会使用一个类型转换器来得到一个字符串表示。对于nameage,都是内嵌类型,具有内嵌转换,这将工作良好;但是也有不能很好工作的时候,对于一个不具备可视化生成的自定义类型,正如Person类型这种情形。

4.3.3数据模板

正确解决这个问题的做法是使用数据模板。数据模板是一棵元素树,可以在特定的上下文扩展。例如,对于每一个Person对象,我们希望能够像以下方式将nameage连接在一起:

    Tom(age: 9)

我们可以把它想象成一个合乎逻辑的模板,如下:

    Name(age: Age)

为了在listbox中为数据项定义模板,我们创建了一个DataElement元素,正如示例4-25

示例4-25

《Programming WPF》翻译 第4章 3.绑定到数据列表<ListBox 《Programming WPF》翻译 第4章 3.绑定到数据列表 ItemsSource="{Binding}">
《Programming WPF》翻译 第4章 3.绑定到数据列表  
<ListBox.ItemTemplate>
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<DataTemplate>
《Programming WPF》翻译 第4章 3.绑定到数据列表      
<StackPanel Orientation="Horizontal">
《Programming WPF》翻译 第4章 3.绑定到数据列表        
<TextBlock TextContent="{Binding Path=Name}" />
《Programming WPF》翻译 第4章 3.绑定到数据列表        
<TextBlock TextContent=" (age: " />
《Programming WPF》翻译 第4章 3.绑定到数据列表        
<TextBlock
《Programming WPF》翻译 第4章 3.绑定到数据列表          
TextContent="{Binding Path=Age}"
《Programming WPF》翻译 第4章 3.绑定到数据列表          Foreground
="
《Programming WPF》翻译 第4章 3.绑定到数据列表            {Binding
《Programming WPF》翻译 第4章 3.绑定到数据列表              Path=Age,
《Programming WPF》翻译 第4章 3.绑定到数据列表              Converter={StaticResource AgeToForegroundConverter}}"
 />
《Programming WPF》翻译 第4章 3.绑定到数据列表        
<TextBlock TextContent=")" />
《Programming WPF》翻译 第4章 3.绑定到数据列表      
</StackPanel>
《Programming WPF》翻译 第4章 3.绑定到数据列表    
</DataTemplate>
《Programming WPF》翻译 第4章 3.绑定到数据列表  
</ListBox.ItemTemplate>
《Programming WPF》翻译 第4章 3.绑定到数据列表
</ListBox>

在这种情形中, ListBox控件有一个ItemTemplate属性,它接受一个DataTemplate对象示例。DataTemplate允许我们详细指出一个单独的子元素,用于绑定重复显示在ListBox控件的每一个数据项。在我们的例子中,使用了StackPanel将四个TextBlock控件放在一行中:2个绑定到每个Person对象的属性,两个是常量文本。注意到,我们使用AgeToForegroundConverter已经将Foreground绑定到Age属性,为了Age属性显示为黑色或红色,为了列表框和age文本框是一致的。

通过使用数据模板,我们经历了从图4-13到图4-14

4-14

《Programming WPF》翻译 第4章 3.绑定到数据列表

注意到,列表框显示了集合中所有的条目,而且保持了视图同步于当前条目,当选择向前或向后的按钮按下时(实际上,你并不会从图
4-14的部分截图真正“注意到”,但是相信我,确实是发生了)。此外,当Person对象的数据改变的时候,列表框以及文本框会保持同步,还包括Age的颜色。

4.3.1类型化数据模板

在示例4-25中,我们显示地为ListBox列表设置了数据模板。然而,如果一个Person对象显示在一个按钮或是其它什么元素中,我们最好分别详细指出那些Person对象的数据模板。另一方面,如果你想要Person对象有一个特殊的模板而不论其显示在哪里,你可以通过类型化的数据模板来实现。

示例4-26

《Programming WPF》翻译 第4章 3.绑定到数据列表<Window.Resources>
《Programming WPF》翻译 第4章 3.绑定到数据列表  
<local:AgeToForegroundConverter
《Programming WPF》翻译 第4章 3.绑定到数据列表    
x:Key="AgeToForegroundConverter" />
《Programming WPF》翻译 第4章 3.绑定到数据列表  
<local:People x:Key="Family">《Programming WPF》翻译 第4章 3.绑定到数据列表</local:People>
《Programming WPF》翻译 第4章 3.绑定到数据列表  
<DataTemplate DataType="{x:Type local:Person}">
《Programming WPF》翻译 第4章 3.绑定到数据列表    
<StackPanel Orientation="Horizontal">
《Programming WPF》翻译 第4章 3.绑定到数据列表      
<TextBlock TextContent="{Binding Path=Name}" />
《Programming WPF》翻译 第4章 3.绑定到数据列表      
<TextBlock TextContent=" (age: " />
《Programming WPF》翻译 第4章 3.绑定到数据列表      
<TextBlock TextContent="{Binding Path=Age}" 《Programming WPF》翻译 第4章 3.绑定到数据列表 />
《Programming WPF》翻译 第4章 3.绑定到数据列表      
<TextBlock TextContent=")" />
《Programming WPF》翻译 第4章 3.绑定到数据列表    
</StackPanel>
《Programming WPF》翻译 第4章 3.绑定到数据列表  
</DataTemplate>
《Programming WPF》翻译 第4章 3.绑定到数据列表
</Window.Resources>
《Programming WPF》翻译 第4章 3.绑定到数据列表《Programming WPF》翻译 第4章 3.绑定到数据列表
《Programming WPF》翻译 第4章 3.绑定到数据列表
<!-- no need for an ItemTemplate setting -->
《Programming WPF》翻译 第4章 3.绑定到数据列表
<ListBox ItemsSource="{Binding}" 《Programming WPF》翻译 第4章 3.绑定到数据列表>

在示例4-26中,我们将数据模板的定义提升到资源模块,并且使用标签的DataType属性标志这个数据模板是类型化的。现在,除非另外通知,每当WPF看到Person对象的一个实例,就会应用相应的数据模板。这是一条便利之路,保证数据以一致的方式显示,遍及于你的应用程序,而不用担心显示的位置。

4.3.4列表的改变

迄今,我们已经得到一个对象的列表,我们可以适当的进行编辑,以及在其中建立导航,甚至轻而易举地高亮显示某些数据,以及提供了一个自动搜索,表现那些没有装载的来自厂商的数据。考虑到我们已经到达的程度,你可能怀疑提供一个Add按钮是一件轻而易举的事情,正如示例4-27所示。

示例4-27

 

}

这个实现的问题在于,尽管视图可以判断出新条目的存在当你移动到这里的时候,而列表框本身却并不知道新增加的集合中的条目,正如图4-15

4-15

《Programming WPF》翻译 第4章 3.绑定到数据列表

为了与图
4-15显示的应用程序状态交互,我运行了这个程序,点击了Add按钮并使用Forward按钮导航到图中所示。然而,即使新人显示在文本框中,列表框仍然不知道添加了什么事物。同样地,如果有对象被删除,它也不会知道。就像数据绑定需要事先INotifyPropertyChanged接口,使用数据绑定的列表需要实现INotifyPropertyChanged这个接口,正如示例4-28

示例4-28

 

}

INotifyCollectionChanged接口用于通知数据绑定控件,有条目在绑定列表中添加或删除。尽管在你的自定义类型中实现INotifyPropertyChanged,从而支持两种方式的数据绑定在你的类型化属性上——这很普通;不普通的是实现你自己的集合类,这些类给你很少的机会实现INotifyCollectionChanged接口。取代之,你更加更能依赖于集合类的一项在.NET 框架类库中,用来实现INotifyCollectionChanged。这样的类数量很少,而且不幸的是,我们使用的保持着Person对象的集合类,并不在其中。当你受欢迎的度过你的夜晚和周末实现了INotifyPropertyChangedWPF提供了ObservableCollection<T>类,用于我们那些紧迫的职责,如示例4-29所示。

示例4-29

}

既然ObservableCollection<T>派生于Collection<T>,而且实现了INotifyCollectionChanged接口,我们可以使用它代替List<T>作为我们的Person集合,正如示例4-30

示例4-30

}

现在,当一个条目添加到或删除自Person集合,这些变化将要在数据绑定列表中反映出来,正如图4-6所示。

4-16

《Programming WPF》翻译 第4章 3.绑定到数据列表

4.3.5排序

一旦我们适当地使数据目标每次显示多于一个事物,一个年轻人的爱好变得更多,当然,是喜欢的事物,正如对数据视图排序或者过滤。回忆视图经常位于数据绑定目标和数据源之间。这意味着可以越过我们不要显示的数据(被称为过滤,而且可以被直接覆盖),而且可以改变数据显示的顺序,又名排序。最简单的排序方法是通过操作视图的Sort属性,正如示例4-31所示。

示例4-31

}

这里我们通过检测SortDescriptionCollection暴露在外的ICollectionView.Sort属性,将排序视图和未排序视图拴在一起。如果没有排序方式的描述,我们首先对Name属性按上升方式排序,然后对Age属性按下降方式排序。如果有排序方式的描述,我们将其清除,重新排序——无论之前是如何排序的。虽然排序描述在适当的位置,任意新添加到集合中的对象将被添加到它们已经排好序的适当位置,正如4-17所示。

一个SortDescription对象集合应该覆盖大多数的情形,但是如果你需要更多一点的控件,你可以提供自定义排序对象的视图,通过实现IComparer接口,正如示例4-32

4-17

《Programming WPF》翻译 第4章 3.绑定到数据列表

示例
4-32

}

在设置了自定义排序的情况,我们必须做一个假设——详细明确地实现了ICollectionView,这里使用的是ListCollectionView,是WPF包装在IList的实现(由ObserverableCollection提供),来提供视图的功能性。此外还有其它没有提供自定义排序的ICollectionView接口实现,因此你要在想*使用这段代码前先测试一下。

希望你在使用前也测试一下其它代码,但是指出这些事情并没有什么危害。

尽管我肯定,当我们使用WPF1.0时,这将变得更好。从现在开始,视图实现了联合详细数据特征,正如在ListCollectionViewIList间进行匹配并没有文本化(至少现在我这么说)。这看起来有点有趣,CustomSort是视图实现类的一部分,并不是ICollectionView接口的一部分,因此让我们为之祈祷:Microsoft发布新的WPF版本改变这一点。

4.3.6过滤

正因为所有的对象按顺序显示使你快乐,这并不意味着你想要显示所有的对象。对于这些没用的出现在数据中的对象,却不属于这个视图,我们需要提供这个实现了CollectionFilterCallback委托*的视图,需要一个单独的对象作为参数并返回一个Boolean值表明这个对象是否应该被显示,正如示例4-33

排序使用一个单方法的接口实现,是由于历史原因;而过滤使用一个委托,是因为在C#2.0中另外使用匿名委托机制,这是一个很流行的机制。

示例4-33

}

正如排序,通过使用一个恰当的过滤器,新条目被适当的过滤掉了,正如图4-18所示。

4-18

《Programming WPF》翻译 第4章 3.绑定到数据列表
4-18中最上面的窗体显示了没有过滤器,中间的窗体显示了过滤了初始的列表,底部的窗体显示了添加一个成年人,过滤器仍然在恰当的位置。

相关文章:

  • 2022-03-07
  • 2021-12-29
  • 2021-06-18
  • 2021-06-07
  • 2021-10-22
  • 2021-12-12
  • 2021-08-26
猜你喜欢
  • 2021-09-06
  • 2021-11-04
  • 2021-12-30
  • 2021-10-21
  • 2022-02-04
  • 2021-10-21
  • 2021-08-29
相关资源
相似解决方案