I have a WPF Combobox which is filled with, say, Customer objects. I have a DataTemplate:
<DataTemplate DataType="{x:Type MyAssembly:Customer}"> <StackPanel> <TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding Address}" /> </StackPanel> </DataTemplate>
This way, when I open my ComboBox, I can see the different Customers with their Name and, below that, the Address.
But when I select a Customer, I only want to display the Name in the ComboBox. Something like:
<DataTemplate DataType="{x:Type MyAssembly:Customer}"> <StackPanel> <TextBlock Text="{Binding Name}" /> </StackPanel> </DataTemplate>
Can I select another Template for the selected item in a ComboBox?
Solution
With help from the answers, I solved it like this:
<UserControl.Resources> <ControlTemplate x:Key="SimpleTemplate"> <StackPanel> <TextBlock Text="{Binding Name}" /> </StackPanel> </ControlTemplate> <ControlTemplate x:Key="ExtendedTemplate"> <StackPanel> <TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding Address}" /> </StackPanel> </ControlTemplate> <DataTemplate x:Key="CustomerTemplate"> <Control x:Name="theControl" Focusable="False" Template="{StaticResource ExtendedTemplate}" /> <DataTemplate.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}"> <Setter TargetName="theControl" Property="Template" Value="{StaticResource SimpleTemplate}" /> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </UserControl.Resources>
Then, my ComboBox:
<ComboBox ItemsSource="{Binding Customers}" SelectedItem="{Binding SelectedCustomer}" ItemTemplate="{StaticResource CustomerTemplate}" />
The important part to get it to work was Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}"
(the part where value should be x:Null, not True).
Selected and Current Item Text property of ComboBox represents the text of the current selected item in a ComboBox. SelectedItem represents the first item in the currently selected items in a ComboBox. SelectedValue represents the value of the currently selected item in a ComboBox.
Data binding the ComboBox Each item, as defined by the ItemTemplate, consists of a StackPanel with a Rectangle and a TextBlock, each bound to the color value.
In visual studio, open WPF designer, select combo box control, then right click combo box control and select Edit template, then select Edit a Copy. This will create a style template, you can modify it as you need.
Advertisements. A ComboBox represents a selection control that combines a non-editable textbox and a drop-down list box that allows users to select an item from a list. It either displays the current selection or is empty if there is no selected item.
The issue with using the DataTrigger/Binding solution mentioned above are two-fold. The first is you actually end up with a binding warning that you can't find the relative source for the selected item. The bigger issue however is that you've cluttered up your data templates and made them specific to a ComboBox.
The solution I present better follows WPF designs in that it uses a DataTemplateSelector
on which you can specify separate templates using its SelectedItemTemplate
and DropDownItemsTemplate
properties as well as ‘selector’ variants for both.
Note: Updated for C#9 with nullability enabled and using pattern matching during the search
public class ComboBoxTemplateSelector : DataTemplateSelector { public DataTemplate? SelectedItemTemplate { get; set; } public DataTemplateSelector? SelectedItemTemplateSelector { get; set; } public DataTemplate? DropdownItemsTemplate { get; set; } public DataTemplateSelector? DropdownItemsTemplateSelector { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { var itemToCheck = container; // Search up the visual tree, stopping at either a ComboBox or // a ComboBoxItem (or null). This will determine which template to use while(itemToCheck is not null and not ComboBox and not ComboBoxItem) itemToCheck = VisualTreeHelper.GetParent(itemToCheck); // If you stopped at a ComboBoxItem, you're in the dropdown var inDropDown = itemToCheck is ComboBoxItem; return inDropDown ? DropdownItemsTemplate ?? DropdownItemsTemplateSelector?.SelectTemplate(item, container) : SelectedItemTemplate ?? SelectedItemTemplateSelector?.SelectTemplate(item, container); } }
To make it easier to use in XAML, I've also included a markup extension that simply creates and returns the above class in its ProvideValue
function.
public class ComboBoxTemplateSelectorExtension : MarkupExtension { public DataTemplate? SelectedItemTemplate { get; set; } public DataTemplateSelector? SelectedItemTemplateSelector { get; set; } public DataTemplate? DropdownItemsTemplate { get; set; } public DataTemplateSelector? DropdownItemsTemplateSelector { get; set; } public override object ProvideValue(IServiceProvider serviceProvider) => new ComboBoxTemplateSelector(){ SelectedItemTemplate = SelectedItemTemplate, SelectedItemTemplateSelector = SelectedItemTemplateSelector, DropdownItemsTemplate = DropdownItemsTemplate, DropdownItemsTemplateSelector = DropdownItemsTemplateSelector }; }
And here's how you use it. Nice, clean and clear and your templates stay 'pure'
Note: 'is:' here is my xmlns mapping for where I put the class in code. Make sure to import your own namespace and change 'is:' as appropriate.
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Items}" ItemTemplateSelector="{is:ComboBoxTemplateSelector SelectedItemTemplate={StaticResource MySelectedItemTemplate}, DropdownItemsTemplate={StaticResource MyDropDownItemTemplate}}" />
You can also use DataTemplateSelectors if you prefer...
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Items}" ItemTemplateSelector="{is:ComboBoxTemplateSelector SelectedItemTemplateSelector={StaticResource MySelectedItemTemplateSelector}, DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />
Or mix and match! Here I'm using a template for the selected item, but a template selector for the DropDown items.
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Items}" ItemTemplateSelector="{is:ComboBoxTemplateSelector SelectedItemTemplate={StaticResource MySelectedItemTemplate}, DropdownItemsTemplateSelector={StaticResource MyDropDownItemTemplateSelector}}" />
Additionally, if you don't specify a Template or a TemplateSelector for the selected or dropdown items, it simply falls back to the regular resolving of data templates based on data types, again, as you would expect. So, for instance, in the below case, the selected item has its template explicitly set, but the dropdown will inherit whichever data template applies for the DataType of the object in the data context.
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Items}" ItemTemplateSelector="{is:ComboBoxTemplateSelector SelectedItemTemplate={StaticResource MyTemplate} />
Enjoy!
Simple solution:
<DataTemplate> <StackPanel> <TextBlock Text="{Binding Name}"/> <TextBlock Text="{Binding Address}"> <TextBlock.Style> <Style TargetType="TextBlock"> <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}" Value="{x:Null}"> <Setter Property="Visibility" Value="Collapsed"/> </DataTrigger> </Style.Triggers> </Style> </TextBlock.Style> </TextBlock> </StackPanel> </DataTemplate>
(Note that the element that is selected and displayed in the box and not the list is not inside a ComboBoxItem
hence the trigger on Null
)
If you want to switch out the whole template you can do that as well by using the trigger to e.g. apply a different ContentTemplate
to a ContentControl
. This also allows you to retain a default DataType
-based template selection if you just change the template for this selective case, e.g.:
<ComboBox.ItemTemplate> <DataTemplate> <ContentControl Content="{Binding}"> <ContentControl.Style> <Style TargetType="ContentControl"> <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ComboBoxItem}}" Value="{x:Null}"> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <!-- ... --> </DataTemplate> </Setter.Value> </Setter> </DataTrigger> </Style.Triggers> </Style> </ContentControl.Style> </ContentControl> </DataTemplate> </ComboBox.ItemTemplate>
Note that this method will cause binding errors as the relative source is not found for the selected item. For an alternate approach see MarqueIV's answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With