I created a combo box with an item template. The template includes a status icon and text:
Here is the XAML:
<DataTemplate x:Key="ItemTemplate">
<WrapPanel>
<Image Width="24" Height="24" Stretch="Fill" Source="{Binding StateImage}" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0,0,15,0"/>
<Label Content="{Binding Address}" VerticalAlignment="Center" HorizontalAlignment="Center" />
</WrapPanel>
</DataTemplate>
<ComboBox x:Name="CbxClients" HorizontalAlignment="Center" VerticalAlignment="Top" ItemTemplate="{StaticResource ItemTemplate}" Width="320" Height="24" IsEditable="False" />
Now I want to make the the combo box editable, so that the user can enter new strings, which hould then be added to the list. Thus I set "IsEditable" to true. This is the result:
This change causes a problem: the icon is now no longer shown in the combo box (only in the drop down area). While editing/entering a string this would be fine, but after the input has been committed, I would expect the new item being added to the list and an icon to be shown.
Is there any way I can achieve this behavior?
You are encountering two problems:
Problem: The ComboBox
is not containing simple string
items. Instead you have defined a DataTemplate
to render a more complex item model.
Solution: The point is that the ComboBox
has no chance to know how to use the input value to construct a new instance of the item model. You have to do it explicitly.
Problem: Because your ComboBox
is configured to be in edit mode, the TextBox will only contain a text representation of your item model (the ComboBox
will call object.ToString
on the model to obtain a text representation).
Solution: There is no out-of-the-box solution. If ComboBox.IsEditable
returns false
the ComboBox
will display the ComboBox.SelectedItem
using a ContentPresenter
. If ComboBox.IsEditable
returns true
the ComboBox
will replace the ContentPresenter
with a TextBox
in order to allow the editing. You can now override the original ControlTemplate
and toggle between both content sites. For example, on GotFocus
you show the TextBox
and on LostFocus
you switch back to the ContentPresenter
.
You can use the XAML Designer to extract the ControlTemplate
or use visit Microsoft Dos: ComboBox Styles and Templates and copy the template and references from there.
To enable text search set ComboBox.IsTextSearchEnabled
to true
and set the attached property TextSearch.TextPath
to the actual property on the item model.
For performance reasons you should replace the Label
with a TextBlock
, especially when the displayed text is dynamic.
Given is the following item model:
class NetworkAddress : INotifyPropertyChanged
{
public ImageSource StateImage { get; set; }
public string Address { get; set; }
}
To enable searching and item creation you must configure the ComboBox
as follows:
<ComboBox ItemsSource="{Binding NetworkAddresses}"
IsEditable="True"
IsTextSearchEnabled="True"
TextSearch.TextPath="Address"
PreviewKeyUp="ComboBox_PreviewKeyUp">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type NetworkAddress}">
<WrapPanel>
<Image Source="{Binding StateImage}" />
<TextBlock Text="{Binding Address}" />
</WrapPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Then handle the the ComboBox.PreviewKeyUp
event to add a new item:
partial class MainWindow : Window
{
// TODO::Property must be a dependency property because MainWindow is a DependencyObject
public ObservableCollection<NetworkAddress> NetworkAddresses { get; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.NetworkAddresses = new ObservableCollection<NetworkAddress>
{
new NetworkAddress() { ... },
new NetworkAddress() { ... },
};
}
private void ComboBox_PreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key is not Key.Enter or not Key.Return)
{
return;
}
var comboBox = sender as ComboBox;
string inputText = comboBox.Text;
if (comboBox.IsTextSearchEnabled
&& comboBox.SelectedItem is null
|| !this.NetworkAddresse.Any(address => address.Address.Equals(inputText, StringComparison.OrdinalIgnoreCase)))
{
AddNewItemToCollection(inputText);;
comboBox.SelectedIndex = comboBox.Items.Count - 1;
// Refresh is only required for the ComboBox flyout to render properly
comboBox.Items.Refresh();
}
}
private void AddNewItemToCollection(string text)
=> this.NetworksAddresses.Add(new NetworkAddress() { ... });