#1,015 – Typing Text to Select an Item in a ComboBox, Part III

You set the TextSearch.TextPath property on a ComboBox to refer to the property on the bound object that contains the text that you can type in order to select an item.

You can change the ComboBox so that the text that you enter to select an item is visible as you enter the text.  To do this, you set the IsEditable property to true.

        <ComboBox ItemsSource="{Binding ActorList}" Margin="20"
                  IsEditable="True"
                  SelectedItem="{Binding SelectedActor}"
                  TextSearch.TextPath="LastName">
            <ComboBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Image Source="{Binding Image}" Height="100"/>
                                <StackPanel Margin="10,0">
                                    <TextBlock Text="{Binding FullName}" FontWeight="Bold" />
                                    <TextBlock Text="{Binding Dates}"/>
                                    <TextBlock Text="{Binding KnownFor}" FontStyle="Italic"/>
                                </StackPanel>
                            </StackPanel>
                        </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>

        <Label Content="{Binding SelectedActor.NameAndDates}"/>

In this example, we can now type the actor’s last name in order to change the selected item.  We can demonstrate this by binding a Label to the selector item (actor).  Notice that the selected item changes as we enter text.
1015-001

#1,014 – Typing Text to Select an Item in a ComboBox, Part II

If a ComboBox has focus, you can normally just start typing text in order to select an item.  If the DisplayMemberPath property is set, you can enter text that matches the property on the bound object specified by DisplayMemberPath.  If you are using an item template to set content for each item, rather than DisplayMemberPath, you can specify the property used when typing text by setting the TextSearch.TextPath property.

In the example below, we set TextSearch.TextPath to LastName, which is a property of the Actor object that each item is bound to.  The user can then just type the actor’s last name in order to select that actor within the ComboBox.

        <ComboBox ItemsSource="{Binding ActorList}" Margin="20"
                  SelectedItem="{Binding SelectedActor}"
                  TextSearch.TextPath="LastName">
            <ComboBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Image Source="{Binding Image}" Height="100"/>
                                <StackPanel Margin="10,0">
                                    <TextBlock Text="{Binding FullName}" FontWeight="Bold" />
                                    <TextBlock Text="{Binding Dates}"/>
                                    <TextBlock Text="{Binding KnownFor}" FontStyle="Italic"/>
                                </StackPanel>
                            </StackPanel>
                        </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>

1014-001

#1,013 – Typing Text to Select an Item in a ComboBox

If a ComboBox has focus, you can just type some text in order to select an item.  By default, the text that you enter will be matched against the property specified by the DisplayMemberPath property, or by the value of the bound object’s ToString method, if DisplayMemberPath is not specified.

In the example below, we don’t specify DisplayMemberPath, but instead specify an ItemTemplate.  If the ComboBox has focus and we start typing, the ToString method of the underlying Actor object is used to determine the object to navigate to.

        <ComboBox ItemsSource="{Binding ActorList}" Margin="20"
                  SelectedItem="{Binding SelectedActor}">
            <ComboBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Image Source="{Binding Image}" Height="100"/>
                                <StackPanel Margin="10,0">
                                    <TextBlock Text="{Binding FullName}" FontWeight="Bold" />
                                    <TextBlock Text="{Binding Dates}"/>
                                    <TextBlock Text="{Binding KnownFor}" FontStyle="Italic"/>
                                </StackPanel>
                            </StackPanel>
                        </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>

If we’ve defined Actor.ToString() to return the actor’s name in a LastName, FirstName format, we can then navigate to an actor by typing their last name.  For example, entering “L” might select Hedy Lamarr.

1013-001

#1,012 – Using a Different Data Template for the Face of a ComboBox

When you define a data template to use as the item template for a ComboBox, this item template is used to render each item in the dropdown list when it appears.  The same item template is used to display the currently selected item on the face of the ComboBox.

1012-001

You can define a different template for the face of the ComboBox using a DataTemplateSelector.  You start by defining a data template selector that allows setting one of two templates and can determine whether the parent of the current item is the ComboBox itself (face of button) or a ComboBoxItem (dropdown).

    public class ComboBoxItemTemplateSelector : DataTemplateSelector
    {
        // Can set both templates from XAML
        public DataTemplate SelectedItemTemplate { get; set; }
        public DataTemplate ItemTemplate { get; set; }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            bool selected = false;

            // container is the ContentPresenter
            FrameworkElement fe = container as FrameworkElement;
            if (fe != null)
            {
                DependencyObject parent = fe.TemplatedParent;
                if (parent != null)
                {
                    ComboBox cbo = parent as ComboBox;
                    if (cbo != null)
                        selected = true;
                }
            }

            if (selected)
                return SelectedItemTemplate;
            else
                return ItemTemplate;
        }
    }

You can now define two different templates in your XAML.

        <ComboBox ItemsSource="{Binding ActorList}" Margin="20"
                  SelectedItem="{Binding SelectedActor}">
            <ComboBox.ItemTemplateSelector>
                <local:ComboBoxItemTemplateSelector>
                    <local:ComboBoxItemTemplateSelector.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Image Source="{Binding Image}" Height="100"/>
                                <StackPanel Margin="10,0">
                                    <TextBlock Text="{Binding FullName}" FontWeight="Bold" />
                                    <TextBlock Text="{Binding Dates}"/>
                                    <TextBlock Text="{Binding KnownFor}" FontStyle="Italic"/>
                                </StackPanel>
                            </StackPanel>
                        </DataTemplate>
                    </local:ComboBoxItemTemplateSelector.ItemTemplate>
                    <local:ComboBoxItemTemplateSelector.SelectedItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Image Source="{Binding Image}" Height="40" Margin="5,0"/>
                                <TextBlock Text="{Binding FullName}"/>
                            </StackPanel>
                        </DataTemplate>
                    </local:ComboBoxItemTemplateSelector.SelectedItemTemplate>
                </local:ComboBoxItemTemplateSelector>
            </ComboBox.ItemTemplateSelector>
        </ComboBox>

We now have a different layout on the face of the ComboBox, showing the currently selected item.

1012-002

#1,011 – ComboBox Data Binding Basics, Part III

To display the items in a ComboBox using something more than a simple string, you set the ItemTemplate of the ComboBox to define the layout of each item.  When you set the ItemTemplate, you don’t set the DisplayMemberPath property.  DisplayMemberPath defines the template for each item in the ComboBox to be a TextBlock that displays a single string.

Below, we set an item template for a ComboBox that binds to a collection of Actor objects so that we display an image and some information about the actor, for each item.

        <ComboBox ItemsSource="{Binding ActorList}" Margin="20"
                  SelectedItem="{Binding SelectedActor}">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Image Source="{Binding Image}" Height="100"/>
                        <StackPanel Margin="10,0">
                            <TextBlock Text="{Binding FullName}" FontWeight="Bold" />
                            <TextBlock Text="{Binding Dates}"/>
                            <TextBlock Text="{Binding KnownFor}" FontStyle="Italic"/>
                        </StackPanel>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
        <Label Content="{Binding SelectedActor.NameAndDates}"/>

As we select an item, notice that the ComboBox changes size, so that the surface can show the entire item.

1011-001

1011-002

#1,010 – ComboBox Data Binding Basics, Part II

When using data binding, you specify the collection of items to fill the ComboBox by setting the ItemsSource property.

You can also bind the currently selected item in the ComboBox to an instance of an object in code.  You do this by binding the SelectedItem property to a property in your code that represents an instance of the appropriate type.  When the user selects an item in the ComboBox, the corresponding object is updated to refer to the selected object.

Assume that we have the following in our code behind:

  • Actor class, representing an actor
  • ActorList property, which is an ObservableCollection<Actor>
  • SelectedActor property, of type Actor

In the example below, we bind the ComboBox to the list of actors and the currently selected actor to the SelectedActor property.  As we select an actor, the Label updates, since it also binds to SelectedActor.

        <ComboBox ItemsSource="{Binding ActorList}" Margin="20"
                  DisplayMemberPath="FullName"
                  SelectedItem="{Binding SelectedActor}"/>
        <Label Content="{Binding SelectedActor.NameAndDates}"/>

1010-001
1010-002

#1,009 – ComboBox Data Binding Basics, Part I

As with a ListBox, you can use data binding to load and manage the items displayed in a ComboBox control.

You set the ItemsSource property of the ComboBox to a collection that implements the IEnumerable interface.  The collection bound to can contain any type of object.

If you want the ComboBox to display simple strings, you can set the DisplayMemberPath property to the string-typed property of a bound object  that should be used to get the display string for each item.

        <ComboBox ItemsSource="{Binding ActorList}" Margin="20"
                  DisplayMemberPath="FullName"/>

1009-001

#1,008 – ComboBox Basics

The ComboBox control is similar to the ListBox, in that the user can select an item from a list of items.  While the ListBox displays a subset of its items and allows the user to scroll through them, the ComboBox only displays a currently selected item.  The user can click on the control, causing it to expand, to see a list containing a subset of the items.  They can then scroll through that list to pick an item.

The other big difference between the two controls is that the ComboBox allows selecting no more than a single item.

Below, both the ListBox and ComboBox are bound to a list of actors by setting the control’s ItemSource property.

        <ListBox ItemsSource="{Binding ActorList}" Margin="10"/>

        <ComboBox Grid.Row="1" ItemsSource="{Binding ActorList}" Margin="10"/>

1008-001

#506 – Using a ComboBox as GroupBox Header

Because the GroupBox.Header property can be set to any object, you could host a control in the header of the GroupBox.  In the example below, we specify a ComboBox for the Header and then bind the various fields to the selected item.

    <GroupBox Margin="15">
        <GroupBox.Header>
            <ComboBox Name="cboDogs" ItemsSource="{Binding Dogs}" DisplayMemberPath="Name"
                      SelectedIndex="0"
                      SelectedValue="{Binding SelectedDog}"
                      SelectedValuePath=""/>
        </GroupBox.Header>
        <Grid Margin="10" DataContext="{Binding SelectedDog}">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>

            <Label Grid.Row="0" Grid.Column="0" Content="Name:" FontWeight="Bold" HorizontalAlignment="Right"/>
            <Label Grid.Row="0" Grid.Column="1" Content="{Binding Name}"/>
            <Label Grid.Row="1" Grid.Column="0" Content="Age:" FontWeight="Bold" HorizontalAlignment="Right"/>
            <Label Grid.Row="1" Grid.Column="1" Content="{Binding Age}" />
            <Label Grid.Row="2" Grid.Column="0" Content="Hobby:" FontWeight="Bold" HorizontalAlignment="Right"/>
            <Label Grid.Row="2" Grid.Column="1" Content="{Binding Hobby}"/>
        </Grid>
    </GroupBox>
    public partial class Window1 : Window, INotifyPropertyChanged
    {
        public Window1()
        {
            InitializeComponent();
            this.DataContext = this;

            Dogs = new ObservableCollection<Dog>();
            Dogs.Add(new Dog("Lassie", 12, "Saving people"));
            Dogs.Add(new Dog("Rin Tin Tin", 52, "War dog"));
            Dogs.Add(new Dog("Benji", 18, "Befriends strays"));
            OnPropertyChanged("Dogs");
        }

        public ObservableCollection<Dog> Dogs { get; protected set; }

        private Dog selectedDog;
        public Dog SelectedDog {
            get
            {
                return selectedDog;
            }
            set
            {
                if (value != selectedDog)
                {
                    selectedDog = value;
                    OnPropertyChanged("SelectedDog");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        private void OnPropertyChanged(string prop)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }

#474 – Hiding a ComboBox Selection Highlight When the Mouse Moves off Item

By default, when you have a ComboBox open in WPF, a highlight is shown as you hover over different items in the ComboBox.

The last item that you hovered over with the mouse will remain selected even if you move the mouse off of the ComboBox.

If you’d rather have an item selected in the ComboBox only when you hover over it, you can modify the ItemContainerStyle for the ComboBox.

You can use Blend to make a copy of the existing template and then make changes to the copy.  In the template, you’ll find a trigger that sets the background of the ComboBoxItem when the IsHighlighted property is true.  You can change this to a MultiTrigger that also checks IsMouseOver.

<MultiTrigger>
    <MultiTrigger.Conditions>
        <Condition Property="IsHighlighted" Value="true"/>
        <Condition Property="IsMouseOver" Value="true"/>
    </MultiTrigger.Conditions>
    <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</MultiTrigger>