请选择您的集合






4.67/5 (5投票s)
随着 WPF 在开发者中越来越受欢迎,
在这篇文章中,我想介绍一个相当常见的场景,我收到了很多关于它的邮件,因为 WPF 在开发者中越来越受欢迎。许多开发者都需要一个 ComboBox
,在列表顶部包含一个条目,提示用户从列表中选择一个项目。由于列表通常使用某种描述的集合填充,因此乍一看,实际放置选择选项的唯一方法就是以某种方式修改列表——而这正是许多“专业开发者”提供的建议。
您可能从上一段末尾的语气猜到我不同意这种方法。在我看来,这太像是一种变通方法了。不过,令人欣慰的是,WPF 提供了一种巧妙的方法来解决这个问题,我将演示我们用来解决限制的类之一的局限性。
好的,让我们首先定义将在 ComboBox
中显示的类。它是我们老朋友 Person
类的变体。
namespace CompositeTest
{
using System;
using System.ComponentModel;
/// <summary>
/// This class defines the person.
/// </summary>
public class Person : INotifyPropertyChanged
{
private string _name;
/// <summary>
/// The name of the person.
/// </summary>
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnChange("Name");
}
}
}
/// <summary>
/// Raise change notifications.
/// </summary>
///
<param name="property">The property to raise the
/// notification on.</param>
protected virtual void OnChange(string property)
{
PropertyChangedEventHandler handler = propertyChanged;
if (handler != null)
{
handler(null, new PropertyChangedEventArgs(property));
}
}
#region INotifyPropertyChanged Members
private event PropertyChangedEventHandler propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add { propertyChanged += value; }
remove { propertyChanged -= value; }
}
#endregion
}
}
正如你所看到的,这是一个标准的 POCO 实现,没有“粘合剂”来处理“神奇”项目。
接下来,我们定义将要使用的 XAML。暂时不要担心它的复杂性,因为我们很快会分解它,以便更好地理解正在发生的事情。
<Window x:Class="CompositeTest.Window1"
xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">
http://schemas.microsoft.com/winfx/2006/xaml</a>"
Title="Selection Sample" Height="200" Width="300"
xmlns:dt="clr-namespace:CompositeTest"
x:Name="Window">
显然,我们想放入一些东西来告知用户他们需要从列表中选择一个人。一个好的方法是将验证规则放入到位,这就是我们要做的。这是代码:
namespace CompositeTest
{
using System;
using System.Windows.Controls;
using System.Globalization;
/// <summary>
/// Validate the ComboBox to see whether or not the
/// user has chosen a person.
/// </summary>
public class PersonValidation : ValidationRule
{
/// <summary>
/// Validate the item to see if it's a ComboBoxItem or not.
/// If it is a ComboBoxItem, this means that the user has
/// chosen the please select item.
/// </summary>
/// <returns>A ValidationResult based on the test</returns>
public override ValidationResult Validate(object value,
CultureInfo cultureInfo)
{
if (value is ComboBoxItem)
return new ValidationResult(false,
"Selection is invalid");
return new ValidationResult(true, null);
}
}
}
验证逻辑非常简单。如果值是 ComboBoxItem
,则表示用户已从列表中选择了“请选择…”选项。任何其他选择都表示用户已从列表中选择了 Person
。
现在,让我们稍微分解一下 XAML。
<Window.Resources>
<DataTemplate x:Key="PersonTemplate">
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
<ControlTemplate x:Key="ValidationTemplate">
<DockPanel>
<AdornedElementPlaceholder />
<TextBlock Foreground="Red"
FontSize="20"
ToolTip="You must choose an item">*</TextBlock>
</DockPanel>
</ControlTemplate>
</Window.Resources>
在这里,我们创建了一个数据模板,我们将应用于组合框,以及一个控件模板,将在出现验证失败时应用。 使用 AdornedElementPlaceHolder
是一种简单的方法,可以将修饰的控件相对于模板中的其他项目放置。
<ComboBox
IsEditable="False"
SelectedIndex="0"
Margin="20"
ItemTemplate="{StaticResource PersonTemplate}"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
>
在这里,我们将项目模板连接到数据模板,并将错误模板连接到我们上面讨论的控件模板。
<ComboBox.SelectedItem>
<Binding Path="SelectedPerson"
ElementName="Window"
Mode="OneWayToSource">
<Binding.ValidationRules>
<dt:PersonValidation />
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedItem>
每当选择的项目发生变化时,应用程序将执行我们在 PersonValidation
类中定义的验证。 如果出现故障,应用程序将应用验证模板。
<ComboBox.ItemsSource>
<CompositeCollection>
<ComboBoxItem>Please select...</ComboBoxItem>
<CollectionContainer
x:Name="peopleCollection"
x:FieldModifier="private"/>
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
我们终于得到了魔力。在正常使用中,您可能很想简单地将 ItemsSource
设置为标准绑定,但我们需要使用 CompositeCollection
代替。 这个方便的类允许我们将几乎任何类型的数据放入 ItemsSource
中(在合理范围内)。要使用它,我们将一个 ComboBoxItem
添加到 CompositeCollection
,它显示我们想要出现在组合框顶部的文本。
接下来,我们使用 CollectionContainer
将一个集合添加到 CompositeCollection
。在你在网上看到的大多数示例中,它绑定到 StaticResource
并且运行良好。但是,如果你想绑定到 DataContext
,那你可能会遇到麻烦 - CompositeCollection
不是 Freezable,并且为了绑定到数据上下文,你必须有一个 Freezable 项目链。那么,如果你不能在 XAML 中做 <CollectionContainer Collection=”{Binding}” />
,你能做什么?
答案是在代码背后完成。您只需要像在以下类中一样设置 Collection
(您需要将 CollectionContainer
公开给您的代码,因此在它上面使用 x:Name
)。
namespace CompositeTest
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.Specialized;
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
private List<Person> list = new List<Person>();
public Window1()
{
InitializeComponent();
BindData();
}
private void BindData()
{
list.Add(new Person { Name = "Peter" });
list.Add(new Person { Name = "Karl" });
list.Add(new Person { Name = "Sacha" });
list.Add(new Person { Name = "Bill" });
list.Add(new Person { Name = "Josh" });
peopleCollection.Collection = list;
}
}
}
有了这些信息,您现在可以在您的应用程序中提供“请选择”功能,而不会有任何实际问题。
祝您编码愉快!