# Multiselect-ComboBox
**Repository Path**: loongeeker/Multiselect-ComboBox
## Basic Information
- **Project Name**: Multiselect-ComboBox
- **Description**: No description available
- **Primary Language**: C#
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2025-05-15
- **Last Updated**: 2025-05-15
## Categories & Tags
**Categories**: Uncategorized
**Tags**: WPF, ComboBox, MultiSelect
## README
# Sdl.MultiSelectComboBox (WPF Custom Control)
[](https://www.nuget.org/packages/Sdl.MultiSelectComboBox/)
## Overview
The multi selection combo box is a WPF custom control with multiple [item selection](#item-selection) capabilities, along with customizable features to [group](#item-group), [sort](#item-sorting) and [filter](#filter-criteria) items in the collection.

## Components and Features

## Selected Items Panel
The [selected items](#selecteditems) are displayed like tags with a remove button They can be added or removed from the [selected items](#selecteditems) collection, by selecting them from the items present in the [Dropdown Menu](#dropdown-menu) list. Additionally, items can also be removed by interacting with them directly from the *Selected Items Panel*, as follows:
| Device | Action | Description |
| ------ | ------ | ------ |
| Keyboard | Backspace or Delete key | If the Filter Criteria is empty, then the last item in the selected items collection is removed. |
| Mouse | Remove Item Button | The item is removed from the selected items collection |
### Visual States
The control has two visual states, [Readonly](#readonlymode) and [EditMode](#editmode), which is identified by the [IsEditMode](#iseditmode) property. When the control is in readonly mode, the items cannot be edited from the view. To switch to edit mode, select the control in the view or hit (*F2*) when the control has focus. Once the control is in edit mode, the items can be filtered, selected or removed. To switch back to readonly, move focus away from the control by selecting any other control in the view or by deactivating the parent window.
| Visual state | Description | Example |
| ------ | ------ | ------ |
| Readonly | The [Dropdown Menu](#dropdown-menu) is collapsed and items present in the [Selected Items Panel](#selected-items-panel) cannot be edited. |  |
| EditMode | The [Dropdown Menu](#dropdown-menu) can be expanded or collapsed when the button is clicked and items present in the [Selected Items Panel](#selected-items-panel) can be edited. |  |
### Filter Criteria
When you type a character in the text area, the control applies a filter on the collection and suggests a list of items matching that criteria in the [Dropdown Menu](#dropdown-menu) list. The developer can override the default [filter service](#filterservice), based on their own business logic requirements, that implements [IFilterService](#ifilterservice).

Depending on whether or not the [ClearFilterOnDropdownClosing](#clearfilterondropdownclosing) property is set to true and [Dropdown Menu](#dropdown-menu) list as keyboard focus, the *Filter Criteria* is cleared automatically as the [Dropdown Menu](#dropdown-menu) is closing.
| Keyboard focus | Action |
| ------ | ------ |
| True | The *Filter Criteria* is cleared as the [Dropdown Menu](#dropdown-menu) is closing. |
| False | No attempt is made to change the current filter. |
## Dropdown Menu
Presents a list of suggestions that can be selected by the user. If no [Filter Criteria](#filter-criteria) is applied, then all items in the collection are displayed
### Visibility
The [Dropdown Menu](#dropdown-menu) can be expanded only when the control is in EditMode.
Visibility
Actions
Expand
Left mouse click anywhere within the control area, with exception to the Remove Item Button of the item.
Down arrow key on the keyboard when the control has focus.
Change the *Filter Criteria*, by typing characters in the text area.
Collapse
Left mouse click anywhere within the control area, with exception to the Remove Item Button of the item.
Return key.
The item that has focus in the list is selected
The *Filter Criteria* is removed
Esc key
Move focus away from the control by selecting any other control in the view or by deactivating the parent window.
### Item Group
The items in the collection can be grouped by implementing [IItemGroupAware](#iitemgroupaware). In addition to the header name, this interface exposes a property to manage the order in which the group headers are displayed.

### Item Sorting
The sort order is based on the order of the items in the collection that was received when the [ItemsSource](#itemssource) is set.
### Item Selection
Items can be added or removed from the [selected items](#selecteditems) collection, by selecting them from the items present in the [Dropdown Menu](#dropdown-menu) list.
| Device | Actions | Description |
| ------ | ------ | ------ |
| Mouse | Left mouse click | The item that has selection focus in the list is selected/unselected. |
| Mouse | Shift + Left mouse click | All items between the previous and current item that has focus are selected. |
| Keyboard | Return key |- The item that has focus in the list is selected. - The [Filter Criteria](#filter-criteria) is removed. - The [Dropdown Menu](#dropdown-menu) is closed |
| Keyboard | Space key | The item that has focus in the list is selected/unselected. |
| Keyboard | Shift + Up or Down key | The item that has focus in the list is selected/unselected. |
### Disabled Item
Implement [IItemEnabledAware](#iitemenabledaware) to identify whether or not the items are enabled. When an item is not enabled, then it will not be selectable from the [Dropdown Menu](#dropdown-menu) list and removed from the [Selected Items Panel](#selected-items-panel) automatically.
### Selected Item
Items in the [Dropdown Menu](#dropdown-menu) list can be selected/unselected via the Mouse or Keyboard. When the item is selected, the style is updated to reflect a selected state.
## Example Project
The example project demonstrates how to implement the *Sdl.MultiSelectComboBox* custom control to your project, and provides good examples in understanding the controls behaviours.

## Examples
The following example creates an *Sdl.MultiSelectComboBox*. The example populates the control by binding the [ItemsSource](#itemssource) property to a collection of type object, that implements [IItemEnabledAware](#iitemenabledaware) and [IItemGroupAware](#iitemgroupaware). The example also binds the [SelectedItemsChangedBehaviour](#selecteditemschangedbehaviour) to a command to receive a notification of [SelectedItemsChangedEventArgs](#selecteditemschangedeventargs) whenever the [selected items](#selecteditems) collection changes and then display those results in a TextBlock on the view.
Additionally, both the selected and dropdown list item templates are customized to display an image along with the item name.
**Example.xaml**
```c#
```
**DataTemplate: MultiSelectComboBox.Dropdown.ListBox.ItemTemplate**
```c#
```
**DataTemplate: MultiSelectComboBox.SelectedItems.ItemTemplate**
```c#
```
**Style: MultiSelectComboBox.Image.Style**
```c#
```
**Style: MultiSelectComboBox.DefaultTextBlock.Style**
```c#
```
The following example defines the data context (i.e. *models:LanguageItems*) that the *Sdl.MultiSelectComboBox* binds to from the previous example.
**LanguageItems : INotifyPropertyChanged**
```c#
public class LanguageItems : INotifyPropertyChanged
{
private List _items;
public LanguageItems()
{
SelectedItemsChangedCommand = new SelectedItemsChangedCommand(UpdateEventLog, UpdateSelectedItems);
var group = new LanguageItemGroup(0, "All Items");
Items = new List
{
new LanguageItem
{
Id = "en-US",
Name= "English (United States)",
Group = group
},
new LanguageItem
{
Id = "it-IT",
Name= "Italian (Italy)",
Group = group
},
new LanguageItem
{
Id = "de-DE",
Name= "German (Germany)",
Group = group
}
};
}
public ICommand SelectedItemsChangedCommand { get; }
public List Items
{
get => _items ?? (_items = new List());
set => _items = value;
}
public string EventLog { get; set; }
private void UpdateEventLog(string action, string text)
{
EventLog += action + " => " + text + "\r\n";
OnPropertyChanged(nameof(EventLog));
}
private void UpdateSelectedItemsCount(int count)
{
SelectedItemsCount = count;
OnPropertyChanged(nameof(SelectedItemsCount));
}
private void UpdateSelectedItems(ICollection selected)
{
foreach (var item in _items)
{
var selectedItemIndex = selected.Cast().ToList().IndexOf(item);
item.SelectedOrder = selectedItemIndex > -1 ? selectedItemIndex + 1 : -1;
}
UpdateSelectedItemsCount(selected.Count);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
```
**LanguageItem : [IItemEnabledAware](#iitemenabledaware), [IItemGroupAware](#iitemgroupaware), INotifyPropertyChanged**
```c#
public class LanguageItem : IItemEnabledAware, IItemGroupAware, INotifyPropertyChanged
{
private string _id;
private string _name;
private bool _isEnabled;
private int _selectedOrder;
private IItemGroup _group;
private BitmapImage _image;
private Size _imageSize;
public LanguageItem()
{
_isEnabled = true;
_selectedOrder = -1;
}
///
/// Unique id in the collection
///
public string Id
{
get => _id;
set
{
if (_id != null && string.Compare(_id, value, StringComparison.InvariantCulture) == 0)
{
return;
}
_id = value;
OnPropertyChanged(nameof(Id));
}
}
///
/// The item name.
///
/// The filter criteria is applied on this property when using the default filter service.
///
public string Name
{
get => _name;
set
{
if (_name != null && string.Compare(_name, value, StringComparison.InvariantCulture) == 0)
{
return;
}
_name = value;
OnPropertyChanged(nameof(Name));
}
}
///
/// Identifies whether the item is enabled or not.
///
/// When the item is not enabled, then it will not be selectable from the dropdown list and removed
/// from the selected items automatically.
///
public bool IsEnabled
{
get => _isEnabled;
set
{
if (_isEnabled.Equals(value))
{
return;
}
_isEnabled = value;
OnPropertyChanged(nameof(IsEnabled));
}
}
///
/// The order in which the items are added to the selected collection.
///
/// This order is independent to the group and sort order of the items in the collection. This selected
/// order is visible in each of the selected items from the dropdown list and visually represented by
/// the order of the items in the Selected Items Panel.
///
public int SelectedOrder
{
get => _selectedOrder;
set
{
if (_selectedOrder.Equals(value))
{
return;
}
_selectedOrder = value;
OnPropertyChanged(nameof(SelectedOrder));
}
}
///
/// Identifies the name and order of the group header
///
public IItemGroup Group
{
get => _group;
set
{
if (_group != null && _group.Equals(value))
{
return;
}
_group = value;
OnPropertyChanged(nameof(Group));
}
}
///
/// The item Image.
///
/// Use the ImageSize to identify the space required to display the image in the view.
///
public BitmapImage Image
{
get => _image;
set
{
_image = value;
OnPropertyChanged(nameof(Image));
}
}
///
/// The image size.
///
/// Measures the width and height that is required to display the image.
///
public Size ImageSize
{
get => _imageSize;
set
{
if (_imageSize.Equals(value))
{
return;
}
_imageSize = value;
OnPropertyChanged(nameof(ImageSize));
}
}
public CultureInfo CultureInfo { get; set; }
public override string ToString()
{
return Name;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
```
**SelectedItemsChangedCommand : ICommand**
```c#
public class SelectedItemsChangedCommand : ICommand
{
private readonly Action _updateEventLog;
private readonly Action _updateSelectedItems;
public SelectedItemsChangedCommand(Action updateEventLog, Action)
{
_updateEventLog = updateEventLog;
_updateSelectedItems = updateSelectedItems;
}
public bool CanExecute(object parameter)
{
return parameter is SelectedItemsChangedEventArgs && _updateEventLog != null;
}
public void Execute(object parameter)
{
if (parameter is SelectedItemsChangedEventArgs args)
{
_updateSelectedItems?.Invoke(args.Selected);
var addedItems = GetAggregatedText(args.Added);
var removedItems = GetAggregatedText(args.Removed);
var selectedItems = GetAggregatedText(args.Selected);
var report = "Added - " + args.Added?.Count + (!string.IsNullOrEmpty(addedItems) ? " (" + TrimToLength(addedItems, 100) + ") " : string.Empty)
+ ", Removed - " + args.Removed?.Count + (!string.IsNullOrEmpty(removedItems) ? " (" + TrimToLength(removedItems, 100) + ") " : string.Empty)
+ ", Selected - " + args.Selected?.Count + (!string.IsNullOrEmpty(selectedItems) ? " (" + TrimToLength(selectedItems, 100) + ") " : string.Empty);
_updateEventLog?.Invoke("Selected Changed", report);
}
}
public event EventHandler CanExecuteChanged;
private string TrimToLength(string text, int length)
{
if (text?.Length > length)
{
text = text.Substring(0, length) + "...";
}
return text;
}
private static string GetAggregatedText(ICollection items)
{
var itemsText = string.Empty;
return items?.Cast().Aggregate(itemsText, (current, item) => current + ((!string.IsNullOrEmpty(current) ? ", " : string.Empty) + item.Id));
}
}
```
**LanguageItemGroup : [IItemGroup](#iitemgroup)**
```c#
public class LanguageItemGroup : IItemGroup
{
public int Order { get; set; }
public string Name { get; }
public LanguageItemGroup(int index, string name)
{
Order = index;
Name = name;
}
public override string ToString()
{
return Name;
}
}
```
## Remarks
The *Sdl.MultiSelectComboBox* allows the user to select an item from a [Dropdown Menu](#dropdown-menu) list and optionally apply a filter on the list by typing text in the text box of the [Selected Items Panel](#selected-items-panel). The [SelectionMode](#selectionmode) and [IsEditable](#iseditable) properties specify how the [Item Selection](#item-selection) behaves, especially when the user is interacting with items from the [Selected Items Panel](#selected-items-panel), as follows:
| | SelectionMode.**Multiple** _(default)_ | SelectionMode.**Single** |
| ------ | ------ | ------ |
| [IsEditable](#iseditable) is **true** _(default)_ | Multiple items can be selected/unselected from the [Dropdown Menu](#dropdown-menu) list
The **Remove Item Button** is displayed in each of the selected items in the [Selected Items Panel](#selected-items-panel).
Items in the [Selected Items Panel](#selected-items-panel) can be removed when the user hits the Delete or Back key | A single item can be selected from the [Dropdown Menu](#dropdown-menu) list. When a new item is selected, it substitutes the previously selected item in the [Selected Items Panel](#selected-items-panel).
The **Remove Item Button** is displayed in each of the selected items in the [Selected Items Panel](#selected-items-panel).
Items in the [Selected Items Panel](#selected-items-panel) can be removed when the user hits the Delete or Back key |
| [IsEditable](#iseditable) is **false** | Multiple items can be selected/unselected from the [Dropdown Menu](#dropdown-menu) list.
The **Remove Item Button** is **not** displayed in each of the selected items in the [Selected Items Panel](#selected-items-panel).
Items in the [Selected Items Panel](#selected-items-panel) **cannot** be removed when the user hits the Delete or Back key | A single item can be selected from the [Dropdown Menu](#dropdown-menu) list. When a new item is selected, it substitutes the previously selected item in the [Selected Items Panel](#selected-items-panel).
The **Remove Item Button** is **not** displayed in each of the selected items in the [Selected Items Panel](#selected-items-panel).
Items in the [Selected Items Panel](#selected-items-panel) **cannot** be removed when the user hits the Delete or Back key |
### Customizing the Style and template
You can modify the default [Style](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.style) and [ControlTemplate](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.controltemplate) to give the control a unique appearance. For information about modifying a controls style and template, see [Customizing the Appearance of an Existing Control by Creating a ControlTemplate](https://docs.microsoft.com/en-us/dotnet/framework/wpf/controls/customizing-the-appearance-of-an-existing-control?view=netframework-4.7.2) and [Styling controls](https://msdn.microsoft.com/windows/uwp/controls-and-patterns/styling-controls). The default style, template, and resources that define the look of the control are included in the generic.xaml file.
This table shows the resources used by the *Sdl.MultiSelectComboBox* control.
| Resource key | Description |
| ------ | ------ |
MultiSelectComboBox.DropDown.Button.Background | Drop down button background color at rest
MultiSelectComboBox.DropDown.Button.Border | Drop down button border brush at rest
MultiSelectComboBox.DropDown.Button.Disabled.Background | Drop down button background color when disabled
MultiSelectComboBox.DropDown.Button.Disabled.Border | Drop down button border brush when disabled
MultiSelectComboBox.DropDown.Button.Disabled.Foreground | Drop down button foreground color when disabled
MultiSelectComboBox.DropDown.Button.MouseOver.Background | Drop down button background color when the mouse is hovering
MultiSelectComboBox.DropDown.Button.MouseOver.Border | Drop down button border brush when the mouse is hovering
MultiSelectComboBox.DropDown.Button.Pressed.Background | Drop down button background color when the button is pressed
MultiSelectComboBox.DropDown.Button.Pressed.Border | Drop down button border brush when the button is pressed
MultiSelectComboBox.DropDown.ListBox.GroupHeader.Background | Drop down list group header background color
MultiSelectComboBox.DropDown.ListBox.GroupHeader.Foreground | Drop down list group header foreground color
MultiSelectComboBox.DropDown.ListBoxItem.Selector.Background | Drop down list item background color of the item when it has selection focus
MultiSelectComboBox.DropDown.ListBoxItem.Selector.Border | Drop down list item border brush of the item when it has selection focus
MultiSelectComboBox.DropDown.ListBoxItem.Selected.Background | Drop down list item background color of the item when it is selected
MultiSelectComboBox.DropDown.ListBoxItem.Selected.Border | Drop down list item border brush of the item when it is selected
MultiSelectComboBox.SelectedItem.Border| Selected item border brush that surrounds the selected item in the [Selected Items Panel](#selected-items-panel)
MultiSelectComboBox.SelectedItem.Button.Foreground | Selected Item button foreground color
MultiSelectComboBox.SelectedItem.Button.Hover.Background | Selected Item button foreground when the mouse if hovering over it
MultiSelectComboBox.SelectedItem.Button.Light.Foreground | Selected Item button light foreground color
MultiSelectComboBox.SelectedItemsPanel.Border | [Selected Items Panel](#selected-items-panel) border brush
MultiSelectComboBox.Text.Disabled.Foreground | Default text foreground color when disabled
MultiSelectComboBox.Text.FontFamily | Default FontFamily for all text in the control
MultiSelectComboBox.Text.Foreground | Default text foreground color
The sample project includes an example of a customized version of control template style for the *Sdl.MultiSelectComboBox* control, where the [item selection](#item-selection) styles are modified and a popup control is displayed with the CultureInfo properties when hovering over the language items in the [Selected Items Panel](#selected-items-panel). Make reference to the following.

## API
**interface IFilterService**
```c#
///
/// The filter service that is used to apply a custom filter on the items that are displayed
/// from the collection.
///
public interface IFilterService
{
///
/// The filter criteria should be set before applying the Filter
///
/// The filter criteria that is applied
void SetFilter(string criteria);
///
/// Gets or sets a callback used to determine if an item is suitable for inclusion in the view.
///
Predicate