Introduction
In this blog, I will be explaining how to manage an editable combobox using the WPF concept of behaviours. The user can edit and add new values to the combobox control and, using behaviours, execute their logic with it.
Implementation
Let's create on small WPF application with a data grid using MVVM, in order to understand how it works
1. Create an XAML with two columns, name and departments, where the 'departments' column has a datatemplate of an editable combobox. The user can edit and add new items to it, as mentioned in XAML.
- <Grid>
- <DataGrid x:Name="grid1" Margin="0" AutoGenerateColumns="False" ItemsSource="{Binding MainItems}">
- <DataGrid.Columns>
- <DataGridTextColumn Width="*" Binding="{Binding Name}" Header="Name" IsReadOnly="True"/>
- <DataGridTemplateColumn MinWidth="200" Width="Auto" Header="Departments">
- <DataGridTemplateColumn.CellTemplate>
- <DataTemplate>
- <ComboBox IsEditable="True" ItemsSource="{Binding Items}"
- SelectedValue="{Binding Department}"
- Text="{Binding NewDepartment, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
- StaysOpenOnEdit="False" IsTextSearchCaseSensitive="False"
- local:EditableComboBoxBehavior.AcceptsEnterKey="True"
- ></ComboBox>
- </DataTemplate>
- </DataGridTemplateColumn.CellTemplate>
- </DataGridTemplateColumn>
- </DataGrid.Columns>
- </DataGrid>
- </Grid>
Behaviours in WPF
The idea is to set an attached property on an element so that you can gain access to the element from the class that exposes the attached property. Once that class has access to the element, it can hook events to it and, in response to those events firing, make the element do things it would not normally do. It is a convenient alternative to creating and using subclasses and is very XAML-friendly.
2. I have created an editableComboboxBehaviour class and attached it to the combobox control.
- public static class EditableComboBoxBehavior
- {
- public static readonly DependencyProperty AcceptsEnterKeyProperty =
- DependencyProperty.RegisterAttached("AcceptsEnterKey", typeof(bool), typeof(EditableComboBoxBehavior), new PropertyMetadata(default(bool), OnAcceptsEnterKey));
-
- public static void SetAcceptsEnterKey(DependencyObject element, bool value)
- {
- element.SetValue(AcceptsEnterKeyProperty, value);
- }
-
- public static bool GetAcceptsEnterKey(DependencyObject element)
- {
- return (bool)element.GetValue(AcceptsEnterKeyProperty);
- }
-
- private static void OnAcceptsEnterKey(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
- {
- var element = (UIElement)dependencyObject;
-
- if ((bool)eventArgs.NewValue)
- {
- element.PreviewKeyDown += KeyDownPreview;
- element.KeyUp += KeyDown;
- }
- else
- {
- element.PreviewKeyDown -= KeyDownPreview;
- element.KeyUp -= KeyDown;
- }
- }
-
- private static void KeyDownPreview(object sender, KeyEventArgs e)
- {
- if (e.Key == Key.Enter || e.Key == Key.Return)
- {
-
- }
- }
-
- private static void KeyDown(object sender, KeyEventArgs e)
- {
- if (e.Key == Key.Enter || e.Key == Key.Return)
- {
-
- }
- else
- {
- var comboBox = (ComboBox)sender;
- var text = comboBox.Text;
- comboBox.IsDropDownOpen = false;
- }
- }
- }
3. I have created View Models to load the static data for testing purpose where departments combobox list few items and new item can be added on it. the mainViewModel is DataContext and each row represents EmployeVM.
- public class MainViewModel
- {
- public ObservableCollection<string> Departments = new ObservableCollection<string> { "Admin", "Development", "Tranining", "Others" };
-
- public ObservableCollection<EmployeeVM> MainItems { get; set; }
-
- public MainViewModel()
- {
- MainItems = new ObservableCollection<EmployeeVM> { new EmployeeVM("Emp1", this), new EmployeeVM("Emp2", this), new EmployeeVM("Emp3", this) };
- }
- }
-
- public class EmployeeVM : INotifyPropertyChanged
- {
- private string newDep;
-
- public EmployeeVM(string name, MainViewModel parent)
- {
- Name = name;
- Parent = parent;
- department = newDep = "Development";
- }
-
- public ObservableCollection<string> Items => Parent.Departments;
-
- public string Name { get; set; }
- public MainViewModel Parent { get; }
-
- private string department;
-
- public string Department
- {
- get
- {
- return department;
- }
- set
- {
- department = value;
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Department)));
- }
- }
-
- public string NewDepartment
- {
- get
- {
- return newDep;
- }
- set
- {
- if (Parent.Departments.FirstOrDefault(s => s.Equals(value)) == null)
- {
- Parent.Departments.Add(value);
- }
-
- Department = value;
- NewDepartment = value;
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewDepartment)));
- }
- }
-
- public event PropertyChangedEventHandler PropertyChanged;
- }
Now the behaviour is attached to control. When the user enters it, it listens to keypreview and keydown events and executes the logic.