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

请选择您的集合

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (5投票s)

2009年5月21日

CPOL

3分钟阅读

viewsIcon

25072

随着 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;
    }
  }
}

有了这些信息,您现在可以在您的应用程序中提供“请选择”功能,而不会有任何实际问题。

祝您编码愉快!

© . All rights reserved.