다음을 통해 공유


WPF: Best ComboBox Tutorial Ever

 

Introduction

This article explains one of my most popular MSDN Samples Gallery projects and covers everything you need to know about ComboBoxes and so much more! It covers populating data from many sources and types, as well as binding and generating list items with code-behind and MVVM style sources. This applies to any ItemsControl like ListBoxes and also DataGridComboBoxColumns.

http://i1.code.msdn.s-msft.com/best-combobox-tutorial-5cc27f82/image/file/67001/1/combos.png

 

Building the Sample

Just download from here, unzip, open and run!

Description

This project contains 12 distinct examples of ComboBox creation.

1. List<string> SelectedItem

This ComboBox is simply populated with a string collection. As a string object is not a Class, SelectedItem and SelectedValue are the same. We bind SelectedItem to a property that is also shared with a label to show the result.

<TextBlock Text="Example 1" VerticalAlignment="Center"/>
  <ComboBox  ItemsSource="{Binding MyStringOptions}" Grid.Column="1" SelectedItem="{Binding SelectedOption1}" Margin="5"/> 
<TextBlock  Text="{Binding SelectedOption1}" Grid.Column="2" Margin="10,5,5,0" VerticalAlignment="Center"/>
 

 

2. List<Class> SelectedItem

This ComboBox's ItemsSource is a collection of MyClass, which has Name and Age properties. SelectedItem is again used, but as it is a class, we can now use all the properties of the SelectedItem. Notice we use DisplayMemberPath to define which property is shown in the list.

<TextBlock Grid.Row="1" Text="Example 2" VerticalAlignment="Center"/>
  <ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding MyClassOptions}" SelectedItem="{Binding SelectedClass}" DisplayMemberPath="Name" Margin="5"/>
  <TextBlock Grid.Row="1" Grid.Column="2" Margin="10,5,5,0" VerticalAlignment="Center"><Run Text="{Binding SelectedClass.Name}"/><Run Text=" - "/><Run Text="{Binding SelectedClass.Age}"/>
</TextBlock>
 

 

3. List<Class> SelectedValue

This example is similar to the previous, but captures one SelectedValue from the Class, instead of the whole object. When you use SelectedValue on a class object, you need to specify SelectedValuePath.

<TextBlock Grid.Row="2" Text="Example 3" VerticalAlignment="Center"/>
  <ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding MyClassOptions}" SelectedValuePath="Age" SelectedValue="{Binding SelectedAge}" DisplayMemberPath="Name" Margin="5"/>
  <TextBlock Grid.Row="2" Grid.Column="2" Margin="10,5,5,0" VerticalAlignment="Center"><Run Text="Selected age: "/><Run Text="{Binding SelectedAge}"/>
</TextBlock>

 

 

4. ComboBox ItemTemplate

What if you want to display more than one property? Instead of using DisplayMemberPath, you can define your own ItemTemplate, and build your list item however you want.

 

<ComboBox . . . ItemTemplate="{StaticResource Example4ItemTemplate}" />
 
<DataTemplate x:Key="Example4ItemTemplate">
      <StackPanel Orientation="Horizontal">
          <TextBlock Text="{Binding Name}" />
          <TextBlock Text=", Aged "/>
          <TextBlock Text="{Binding Age}" Grid.Column="1" />
      </StackPanel>
</DataTemplate>
 

 

5. XAML Array - Static XAML Data

 

If your ComboBox options are limited, static and not worth coding, or if you are in a hurry, then you can just dump the data into the actual XAML.

 

<ComboBox . . . ItemsSource="{StaticResource Example9_XamlArray}" />
<x:Array x:Key="Example9_XamlArray" Type="sys:String"          xmlns:sys="clr-namespace:System;assembly=mscorlib">      <sys:String>Bear</sys:String>      <sys:String>Bird</sys:String>      <sys:String>Cat</sys:String>      <sys:String>Cow</sys:String>      <sys:String>Dog</sys:String>      <sys:String>Elephant</sys:String>      <sys:String>Fish</sys:String>      <sys:String>Goat</sys:String>      <sys:String>Hamster</sys:String></x:Array>
 

6. XML Data - XmldataProvider

 

There are many reasons why your data may be in XML. ComboBoxes can consume your XMl through an XmlDataProvider.

 

<ComboBox . . . ItemsSource="{Binding Source={StaticResource WorkmenData}}" DisplayMemberPath="@Name" SelectedValuePath="@Name" />
<XmlDataProvider x:Key="WorkmenData" XPath="Workmen/Man">
      <x:XData>
          <Workmen xmlns="">
              <Man Name="Bob" />
              <Man Name="Charles" />
              <Man Name="Harry" />
              <Man Name="Mark" />
              <Man Name="Nick" />
              <Man Name="William" />
          </Workmen>
      </x:XData>  
</XmlDataProvider>
 

7. Static Class Property

 

ItemSource binding can even target a static class property, which can either contain preformatted data, or could be used to retrieve data from database or file sources.

 

<ComboBox Grid.Row="6" Grid.Column="1" DisplayMemberPath="Model" Margin="5" ItemsSource="{Binding Source={x:Static model:StaticData.MyCarsStatic}}" />
 

8. Composite Collection

 

Composite Collections allow you to group together any collections or objects of different types, into one listable collection.

 

<CompositeCollection x:Key="Example7_CompositeCollection">
      <CollectionContainer Collection="{Binding Source={StaticResource WorkmenData}}" />
      <CollectionContainer Collection="{Binding Source={StaticResource MyCarsCollection}}"/>
      <ListBoxItem Background="Yellow">Another ListBoxItem</ListBoxItem>
</CompositeCollection>


How does it know how to display each type of data? You define DataTemplates for each DataType.

 

<ComboBox.Resources>
      <DataTemplate DataType="Man">
          <TextBlock Background="LightBlue" Text="{Binding XPath=@Name}"/>
      </DataTemplate>
      <DataTemplate DataType="{x:Type model:MyCar}">
          <TextBlock Background="LightGreen" Text="{Binding Model}"/>
      </DataTemplate>
</ComboBox.Resources>
 

 

9. DataGridTemplateColumn CellTemplate

 

With a CellTemplate, you can add your own physical ComboBox to a DataGrid column. This SHOWS the ComboBox, and allows single-click access.

 

<DataGridTemplateColumn Header="Example 9">
      <DataGridTemplateColumn.CellTemplate>
          <DataTemplate>
              <ComboBox ItemsSource="{Binding PartIds, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding PartId, UpdateSourceTrigger=PropertyChanged}" />
          </DataTemplate>
      </DataGridTemplateColumn.CellTemplate>  
</DataGridTemplateColumn>

 

Note the ItemsSource is pointing to a separate source, using RelativeSource AncestorType=Window to get back to the DataContext/ViewModel.

 

10. DataGridTemplateColumn CellEditingTemplate

 

This example has both CellTemplate, for a custom label style, and CellEditingTemplate which shows the ComboBox, similar to the default DataGridComboBoxCoilumn, but customisable.

<DataGridTemplateColumn.CellTemplate>
      <DataTemplate>
          <TextBlock Background="LightBlue" Foreground="BlueViolet" Text="{Binding PartId}" />
      </DataTemplate>
  </DataGridTemplateColumn.CellTemplate>
  <DataGridTemplateColumn.CellEditingTemplate>
      <DataTemplate>
          <ComboBox ItemsSource="{Binding PartIds, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding PartId, UpdateSourceTrigger=PropertyChanged}" />
      </DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>

 

 

11. DataGridComboBoxColumn

 

This is the standard autogenerated ComboBox column of a DataGrid. CellTemplate defaults to a label, CellEditingTemplate is a ComboBox.

<DataGridComboBoxColumn Header="Example 11" SelectedItemBinding="{Binding PartId, UpdateSourceTrigger=PropertyChanged}">
      <DataGridComboBoxColumn.ElementStyle>
          <Style TargetType="ComboBox">
              <Setter Property="ItemsSource" Value="{Binding Path=PartIds, RelativeSource={RelativeSource AncestorType=Window}}" />
          </Style>
      </DataGridComboBoxColumn.ElementStyle>
      <DataGridComboBoxColumn.EditingElementStyle>
          <Style TargetType="ComboBox">
              <Setter Property="ItemsSource" Value="{Binding Path=PartIds, RelativeSource={RelativeSource AncestorType=Window}}" />
          </Style>
      </DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>

 

Note we have to help wire back in the ItemsSource, because DataGrid.Columns property doesn't have DataContext.

 

12. Using an Object Collection for Options

 

The previous DataGrid examples used a List<string> for the ComboBox Items. This final example assumes the options are themselves objects with properties.

 

<DataGridTemplateColumn.CellTemplate>
      <DataTemplate>
          <ComboBox ItemsSource="{Binding CarParts, RelativeSource={RelativeSource AncestorType=Window}}" DisplayMemberPath="PartName" SelectedValuePath="PartID"  SelectedValue="{Binding PartId, UpdateSourceTrigger=PropertyChanged}" />
      </DataTemplate>
</DataGridTemplateColumn.CellTemplate>
 

This final DataGrid also allows inputting new rows of data. When you add a new row of data, you will notice the Model and registration columns don't update with new values. This is because they do not implement INotifyPropertyChanged.

Further reading specifically on ComboBox in DataGrid:
WPF ComboBox Binding in DataGrid

Final word

This article was copied over from MSDN Samples into TechNet wiki, as it is a suitable learning resource for new developers.

This article has been submitted to the TechNet Guru Competition for August as an example of how to contribute. You don't need to think of something new, simply copy in your own work from another source, like an old blog of yours or in my example, an MSDN Samples project.

I hope this sample helps many others. If you like this project, please browse my other MSDN contributions : http://code.msdn.microsoft.com/site/search?f%5B0%5D.Type=User&f%5B0%5D.Value=XAML%20guy