Home > Windows Presentation Foundation > Please select your collection

Please select your collection

In this post, I’d like to cover a fairly common scenario that I’ve received various bits of email about as WPF is becoming more and more popular with developers. A common requirement for a lot of developers is to have a ComboBox contain an entry at the top of the list prompting the user to select an item from the list. As the list is normally populated using a collection of some description it would seem, at first glance, that the only way to actually put a selection choice in is to actually modify the list somehow – and this is precisely the advice that several “professional developers” offer.

You can probably guess by the tone of the end of that previous paragraph that I don’t agree with this approach. It just smacks way too much of a hack to my liking. Rejoice though, WPF provides a neat way to get round this problem, and I’m going to demonstrate a limitation of one of the classes we use to get round the limitation.

Right, let’s start off by defining the class that we are going to display in the ComboBox. It’s a variation of our old faithful, the Person class.

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

As you can see, it’s a bog-standard POCO implementation, with no “goo” in place to handle a “magic” item.

Next, we define the XAML that we are going to use. Don’t worry about the complexity at the moment, as we’ll soon break it down to better understand what’s going on.

<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">
  <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>
  <StackPanel>
    <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>
      <ComboBox.ItemsSource>
        <CompositeCollection>
          <ComboBoxItem>Please select...</ComboBoxItem>
          <CollectionContainer
            x:Name="peopleCollection"
            x:FieldModifier="private"/>
        </CompositeCollection>
      </ComboBox.ItemsSource>
    </ComboBox>
  </StackPanel>
</Window>

Obviously we want to put something in place to inform the user that they need to select a person from the list. A good way to do this is to put a validation rule into place, and that’s what we are going to do. Here’s the code:

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

The validation logic is fairly simple. If the value is a ComboBoxItem, this means the user has chosen the Please select… option from the list. Any other selection means the user has chosen a Person from the list.

Now, let’s break the XAML down a little bit.

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

Here we’ve created a data template that we are going to apply to the combo box, and a control template that will be applied when there are validation failures. The use of AdornedElementPlaceHolder is an easy way to place the a decorated control relative to other items in your template.

    <ComboBox
      IsEditable="False"
      SelectedIndex="0"
      Margin="20"
      ItemTemplate="{StaticResource PersonTemplate}"
      Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
      >

Here we’re hooking the item template to the data template, and the error template to the control template we talked about above.

      <ComboBox.SelectedItem>
        <Binding Path="SelectedPerson"
             ElementName="Window"
             Mode="OneWayToSource">
          <Binding.ValidationRules>
            <dt:PersonValidation />
          </Binding.ValidationRules>
        </Binding>
      </ComboBox.SelectedItem>

Whenever the selected item changes, the application is going to perform the validation that we defined in the PersonValidation class. If there’s a failure, the application will apply the validation template. Please note the ElementName element – I’ve defined this as a named attribute in the Window declaration.

      <ComboBox.ItemsSource>
        <CompositeCollection>
          <ComboBoxItem>Please select...</ComboBoxItem>
          <CollectionContainer
            x:Name="peopleCollection"
            x:FieldModifier="private"/>
        </CompositeCollection>
      </ComboBox.ItemsSource>
    </ComboBox>

We finally get to the magic. Where you may be tempted, in normal use, to just set the ItemsSource to a standard Binding, we need to use a CompositeCollection instead. This handy class allows us to put pretty much any type of data into the ItemsSource (within reason). To use it, we add a ComboBoxItem to the CompositeCollection that displays the text we want to appear at the top of the combo box.

Next, we add a collection to the CompositeCollection using a CollectionContainer. In most of the samples you see of this on the web, it binds to a StaticResource and is perfectly happy. If you want to bind to a DataContext, however, you’re kind of stuffed – the CompositeCollection is not Freezable and, in order to bind to the data context, you have to have a chain of Freezable items. So, if you can’t do <CollectionContainer Collection=”{Binding}” /> in your XAML, what can you do?

Well, the answer is to do it in the code behind. All you need to do is set the Collection as in the following class (you need to expose the CollectionContainer to your code behind, hence the use of x:Name on it):

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();
    }
    public object SelectedPerson { get; set; }
    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;
    }
  }
}

Armed with this information, you can now provide Please select functionality in your applications without any real problems now.

Happy coding.

Download: WpfApplication1.zip.

When you download the source, you’ll need to remove the .doc extension.

About these ads
  1. May 22, 2009 at 1:40 pm

    hi, at http://www.xamltemplates.net/ you can find styles/themes for all the wpf and silverlight controls.

  2. camdotnet
    May 24, 2009 at 4:19 pm

    Um, couldn’t you do the same thing by restyling the ComboBox template to add a TextBlock at the top of the dropdown?

    I thought the CompositeCollection was best used for adding a “None” item to the top of a ComboBox.

    • peteohanlon
      May 24, 2009 at 6:42 pm

      You could – either approach works, but the Please select… is just a variant on the None theme, and one that doesn’t require template restyling.

      • camdotnet
        May 24, 2009 at 11:07 pm

        I think I’d prefer a template then for the Please select, so that it’s not really an item and cannot be selected by accident.

    • Christo
      July 16, 2009 at 12:02 pm

      Hi camdotnet.

      Do you have a blog post or something that can help me to do what you proposed?

      I just dont get why this is not standard in wpf and WinForms when it is in asp!

      Thank you in advance.

      To the author of the blog post (seems like peteohanlon), thank you! Well played!

      Regards
      Christo

      • peteohanlon
        September 26, 2009 at 2:12 pm

        Thanks christo. Glad you liked it.

  3. peteohanlon
    May 25, 2009 at 8:25 am

    @camdotnet – That’s one approach, but it is common for LOB applications to use the Please select to force the user to make an informed decision about what they want to do – which means that the select option is in there as a validated item.

  4. camdotnet
    May 25, 2009 at 12:01 pm

    Fair nuff. But what about adding a “Please select” and a “None”? How would the validator handle this?

  5. peteohanlon
    May 25, 2009 at 6:33 pm

    @camdotnet – It wouldn’t (as it stands), although I can’t think of a situation where I’d want to have a Please select and a None, as they seem to be at odds with each other.

  6. August 10, 2010 at 5:35 pm

    @camdotnet
    You could use the Tag property of the ComboBoxItem and parse it in the converter.
    Please select… has the tag “null”, None has the tag “none”;

    {enjoy};

  7. August 10, 2010 at 5:44 pm

    @camdotnet
    You could use the Tag property of the ComboBoxItem and parse it in the validator.
    Please select… has the tag “null”, None has the tag “none”;

    {enjoy};

  8. June 26, 2011 at 5:23 pm

    Nice Post , Liked

    Need help : I am trying to get SelectedValue of combobox, can you share code to do that in above example.

  1. May 22, 2009 at 12:04 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Canny Coder

Java 8 Functional Programming with Lambda Expressions

pihole.org

Adventures in theoretical computer science, with your host, chaiguy1337

Confessions of a coder

Confessions of a WPF lover

WordPress.com

WordPress.com is the best place for your personal blog or business site.

Follow

Get every new post delivered to your Inbox.

Join 38 other followers

%d bloggers like this: