Introduction
It is a quite common situation when the UI shows a lot of different kinds of elements (text blocks, images, graphics, etc.) structured in different ways (lists, trees etc.), but only one of these elements could be selected at the same time.
In this article, I will try to create a class which will help to deal with selection. WPF is used here for the demo. However, this approach can be used in UWP, Xamarin, Windows Forms, and maybe some other technologies.
Source code can also be found on
GitHub.
Interfaces
To be handled by Selection Manager, the object should implement ISelectableElement interface.
-
-
-
-
- public interface ISelectableElement: INotifyPropertyChanged
- {
-
-
-
- bool Selected { get; set; }
- }
SelectionManager implements ISelectionManager interface to be able to use DependancyInjection pattern.
-
-
-
- public interface ISelectionManager: INotifyPropertyChanged
- {
-
-
-
- ISelectableElement SelectedElement { get; set; }
-
-
-
-
-
- void AddCollection(INotifyCollectionChanged collection);
-
-
-
-
-
- void RemoveCollection(INotifyCollectionChanged collection);
- }
Helpers
PropertyHelper is used to get the property name.
- internal class PropertyHelper
- {
- public static string GetPropertyName<T>(Expression<Func<T>> propertyLambda)
- {
- var me = propertyLambda.Body as MemberExpression;
-
- if (me == null)
- {
- throw new ArgumentException(
- "You must pass a lambda of the form: '() => Class.Property' or '() => object.Property'");
- }
-
- return me.Member.Name;
- }
- }
ObservableCollection does not fire
CollectionChanged with the list of removed (old) items after calling
Clear(). It is possible to use
ObservableCollection and not use
Clear() method or use
ObservableCollectionEx to be able to use
Clear() method
.
-
-
-
-
-
-
- public class ObservableCollectionEx<T> : ObservableCollection<T>
- {
-
-
-
- protected override void ClearItems()
- {
- var items = new List<T>(Items);
- base.ClearItems();
- OnCollectionChanged(
- new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items));
- }
- }
SelectionManager
AddCollection adds all elements in the collection to the internal list and searchs for subelements using reflection (if some of the element properties implement ObservableCollection<> and elements of this collection implement ISelectableElement this collection will also be managed by SelectionManager).
RemoveCollection removes all elements and subelements from SelectionManager.
SelectionManager will handle adding and removing subelements automatically.
Tests
Demo
This demo application contains a list and a tree. Selection is managed by SelectionManager.
MVVM Light framework is used to make the code more compact and clear.
There are two types of the objects which support selection.
- class ListElementViewModel : ViewModelBase, ISelectableElement
- {
- private string _description;
- public string Description
- {
- get { return _description; }
- set { Set(ref _description, value); }
- }
-
- private bool _selected;
- public bool Selected
- {
- get { return _selected; }
- set { Set(ref _selected, value); }
- }
- }
-
-
- class HierarchicalElementViewModel: ViewModelBase, ISelectableElement
- {
- private string _name;
- public string Name
- {
- get { return _name; }
- set { Set(ref _name, value); }
- }
-
- public ObservableCollection<HierarchicalElementViewModel> Subitems { get; set; }
-
- private bool _selected;
- public bool Selected
- {
- get { return _selected; }
- set { Set(ref _selected, value); }
- }
-
- public ICommand AddSubitemCommand { get; }
- public ICommand RemoveCommand { get; }
-
- public HierarchicalElementViewModel ParentViewModel { get; }
-
- public HierarchicalElementViewModel(HierarchicalElementViewModel parentViewModel)
- {
- ParentViewModel = parentViewModel;
- Subitems = new ObservableCollection<HierarchicalElementViewModel>();
- AddSubitemCommand = new RelayCommand(Add);
- RemoveCommand = new RelayCommand(Remove, () => ParentViewModel != null);
- }
-
- private void Add()
- {
- Subitems.Add(new HierarchicalElementViewModel(this) { Name = "Child Element" });
- }
- private void Remove()
- {
- ParentViewModel.Subitems.Remove(this);
- }
- }
MainViewModel contains two collections of these elements.
- class MainViewModel : ViewModelBase
- {
- public ObservableCollection<HierarchicalElementViewModel> HierarchicalElements { get; }
-
- public ObservableCollection<ListElementViewModel> ListElements { get; }
-
- public RelayCommand AddHierarchicalElementCommand { get; }
-
- public RelayCommand RemoveHierarchicalElementCommand { get; }
-
- public RelayCommand AddListElementCommand { get; }
-
- public RelayCommand RemoveListElementCommand { get; }
-
- public ISelectionManager Manager { get; }
-
- public MainViewModel()
- {
- HierarchicalElements = new ObservableCollection<HierarchicalElementViewModel>();
- ListElements = new ObservableCollection<ListElementViewModel>();
- AddHierarchicalElementCommand = new RelayCommand(AddHierarchicalElement);
- RemoveHierarchicalElementCommand = new RelayCommand(
- RemoveHierarchicalElement,
- () => Manager.SelectedElement is HierarchicalElementViewModel);
- AddListElementCommand = new RelayCommand(AddListElement);
- RemoveListElementCommand = new RelayCommand(
- RemoveListElement,
- () => Manager.SelectedElement is ListElementViewModel);
- Manager = new SelectionManager.SelectionManager();
- Manager.PropertyChanged += ManagerOnPropertyChanged;
- Manager.AddCollection(HierarchicalElements);
- Manager.AddCollection(ListElements);
- }
-
- private void AddHierarchicalElement()
- {
- var selectedHierarchicalElement = Manager.SelectedElement as HierarchicalElementViewModel;
- if (selectedHierarchicalElement != null)
- {
- var newItem = new HierarchicalElementViewModel(selectedHierarchicalElement) { Name = "Child Element" };
- selectedHierarchicalElement.Subitems.Add(newItem);
- newItem.Selected = true;
- }
- else
- {
- var newItem = new HierarchicalElementViewModel(null) { Name = "Root Element" };
- HierarchicalElements.Add(newItem);
- newItem.Selected = true;
- }
- }
-
- private void RemoveHierarchicalElement()
- {
- var hierarchicalElement = Manager.SelectedElement as HierarchicalElementViewModel;
-
- if (hierarchicalElement?.ParentViewModel != null)
- {
- hierarchicalElement.ParentViewModel.Subitems.Remove(hierarchicalElement);
- }
- else
- {
- HierarchicalElements.Remove(hierarchicalElement);
- }
- }
-
- private void AddListElement()
- {
- var newItem = new ListElementViewModel { Description = "List Element" };
- ListElements.Add(newItem);
- newItem.Selected = true;
- }
-
- private void RemoveListElement()
- {
- ListElements.Remove((ListElementViewModel)Manager.SelectedElement);
- }
- private void ManagerOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
- {
- RemoveHierarchicalElementCommand.RaiseCanExecuteChanged();
- RemoveListElementCommand.RaiseCanExecuteChanged();
- }
- }
MainForm xaml code.
Now, our demo looks like this.