using System; using System.Collections; using System.Collections.Specialized; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; namespace LaDOSE.DesktopApp.Behaviors { /// /// A sync behaviour for a multiselector. /// public static class MultiSelectorBehaviours { public static readonly DependencyProperty SynchronizedSelectedItems = DependencyProperty.RegisterAttached( "SynchronizedSelectedItems", typeof(IList), typeof(MultiSelectorBehaviours), new PropertyMetadata(null, OnSynchronizedSelectedItemsChanged)); private static readonly DependencyProperty SynchronizationManagerProperty = DependencyProperty.RegisterAttached( "SynchronizationManager", typeof(SynchronizationManager), typeof(MultiSelectorBehaviours), new PropertyMetadata(null)); /// /// Gets the synchronized selected items. /// /// The dependency object. /// The list that is acting as the sync list. public static IList GetSynchronizedSelectedItems(DependencyObject dependencyObject) { return (IList)dependencyObject.GetValue(SynchronizedSelectedItems); } /// /// Sets the synchronized selected items. /// /// The dependency object. /// The value to be set as synchronized items. public static void SetSynchronizedSelectedItems(DependencyObject dependencyObject, IList value) { dependencyObject.SetValue(SynchronizedSelectedItems, value); } private static SynchronizationManager GetSynchronizationManager(DependencyObject dependencyObject) { return (SynchronizationManager)dependencyObject.GetValue(SynchronizationManagerProperty); } private static void SetSynchronizationManager(DependencyObject dependencyObject, SynchronizationManager value) { dependencyObject.SetValue(SynchronizationManagerProperty, value); } private static void OnSynchronizedSelectedItemsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { if (e.OldValue != null) { SynchronizationManager synchronizer = GetSynchronizationManager(dependencyObject); synchronizer.StopSynchronizing(); SetSynchronizationManager(dependencyObject, null); } IList list = e.NewValue as IList; Selector selector = dependencyObject as Selector; // check that this property is an IList, and that it is being set on a ListBox if (list != null && selector != null) { SynchronizationManager synchronizer = GetSynchronizationManager(dependencyObject); if (synchronizer == null) { synchronizer = new SynchronizationManager(selector); SetSynchronizationManager(dependencyObject, synchronizer); } synchronizer.StartSynchronizingList(); } } /// /// A synchronization manager. /// private class SynchronizationManager { private readonly Selector _multiSelector; private TwoListSynchronizer _synchronizer; /// /// Initializes a new instance of the class. /// /// The selector. internal SynchronizationManager(Selector selector) { _multiSelector = selector; } /// /// Starts synchronizing the list. /// public void StartSynchronizingList() { IList list = GetSynchronizedSelectedItems(_multiSelector); if (list != null) { _synchronizer = new TwoListSynchronizer(GetSelectedItemsCollection(_multiSelector), list); _synchronizer.StartSynchronizing(); } } /// /// Stops synchronizing the list. /// public void StopSynchronizing() { _synchronizer.StopSynchronizing(); } public static IList GetSelectedItemsCollection(Selector selector) { if (selector is MultiSelector) { return (selector as MultiSelector).SelectedItems; } else if (selector is ListBox) { return (selector as ListBox).SelectedItems; } else { throw new InvalidOperationException("Target object has no SelectedItems property to bind."); } } } } public class TwoListSynchronizer : IWeakEventListener { private static readonly IListItemConverter DefaultConverter = new DoNothingListItemConverter(); private readonly IList _masterList; private readonly IListItemConverter _masterTargetConverter; private readonly IList _targetList; /// /// Initializes a new instance of the class. /// /// The master list. /// The target list. /// The master-target converter. public TwoListSynchronizer(IList masterList, IList targetList, IListItemConverter masterTargetConverter) { _masterList = masterList; _targetList = targetList; _masterTargetConverter = masterTargetConverter; } /// /// Initializes a new instance of the class. /// /// The master list. /// The target list. public TwoListSynchronizer(IList masterList, IList targetList) : this(masterList, targetList, DefaultConverter) { } private delegate void ChangeListAction(IList list, NotifyCollectionChangedEventArgs e, Converter converter); /// /// Starts synchronizing the lists. /// public void StartSynchronizing() { ListenForChangeEvents(_masterList); ListenForChangeEvents(_targetList); // Update the Target list from the Master list SetListValuesFromSource(_masterList, _targetList, ConvertFromMasterToTarget); // In some cases the target list might have its own view on which items should included: // so update the master list from the target list // (This is the case with a ListBox SelectedItems collection: only items from the ItemsSource can be included in SelectedItems) if (!TargetAndMasterCollectionsAreEqual()) { SetListValuesFromSource(_targetList, _masterList, ConvertFromTargetToMaster); } } /// /// Stop synchronizing the lists. /// public void StopSynchronizing() { StopListeningForChangeEvents(_masterList); StopListeningForChangeEvents(_targetList); } /// /// Receives events from the centralized event manager. /// /// The type of the calling this method. /// Object that originated the event. /// Event data. /// /// true if the listener handled the event. It is considered an error by the handling in WPF to register a listener for an event that the listener does not handle. Regardless, the method should return false if it receives an event that it does not recognize or handle. /// public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) { HandleCollectionChanged(sender as IList, e as NotifyCollectionChangedEventArgs); return true; } /// /// Listens for change events on a list. /// /// The list to listen to. protected void ListenForChangeEvents(IList list) { if (list is INotifyCollectionChanged) { CollectionChangedEventManager.AddListener(list as INotifyCollectionChanged, this); } } /// /// Stops listening for change events. /// /// The list to stop listening to. protected void StopListeningForChangeEvents(IList list) { if (list is INotifyCollectionChanged) { CollectionChangedEventManager.RemoveListener(list as INotifyCollectionChanged, this); } } private void AddItems(IList list, NotifyCollectionChangedEventArgs e, Converter converter) { int itemCount = e.NewItems.Count; for (int i = 0; i < itemCount; i++) { int insertionPoint = e.NewStartingIndex + i; if (insertionPoint > list.Count) { list.Add(converter(e.NewItems[i])); } else { list.Insert(insertionPoint, converter(e.NewItems[i])); } } } private object ConvertFromMasterToTarget(object masterListItem) { return _masterTargetConverter == null ? masterListItem : _masterTargetConverter.Convert(masterListItem); } private object ConvertFromTargetToMaster(object targetListItem) { return _masterTargetConverter == null ? targetListItem : _masterTargetConverter.ConvertBack(targetListItem); } private void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { IList sourceList = sender as IList; switch (e.Action) { case NotifyCollectionChangedAction.Add: PerformActionOnAllLists(AddItems, sourceList, e); break; case NotifyCollectionChangedAction.Move: PerformActionOnAllLists(MoveItems, sourceList, e); break; case NotifyCollectionChangedAction.Remove: PerformActionOnAllLists(RemoveItems, sourceList, e); break; case NotifyCollectionChangedAction.Replace: PerformActionOnAllLists(ReplaceItems, sourceList, e); break; case NotifyCollectionChangedAction.Reset: UpdateListsFromSource(sender as IList); break; default: break; } } private void MoveItems(IList list, NotifyCollectionChangedEventArgs e, Converter converter) { RemoveItems(list, e, converter); AddItems(list, e, converter); } private void PerformActionOnAllLists(ChangeListAction action, IList sourceList, NotifyCollectionChangedEventArgs collectionChangedArgs) { if (sourceList == _masterList) { PerformActionOnList(_targetList, action, collectionChangedArgs, ConvertFromMasterToTarget); } else { PerformActionOnList(_masterList, action, collectionChangedArgs, ConvertFromTargetToMaster); } } private void PerformActionOnList(IList list, ChangeListAction action, NotifyCollectionChangedEventArgs collectionChangedArgs, Converter converter) { StopListeningForChangeEvents(list); action(list, collectionChangedArgs, converter); ListenForChangeEvents(list); } private void RemoveItems(IList list, NotifyCollectionChangedEventArgs e, Converter converter) { int itemCount = e.OldItems.Count; // for the number of items being removed, remove the item from the Old Starting Index // (this will cause following items to be shifted down to fill the hole). for (int i = 0; i < itemCount; i++) { list.RemoveAt(e.OldStartingIndex); } } private void ReplaceItems(IList list, NotifyCollectionChangedEventArgs e, Converter converter) { RemoveItems(list, e, converter); AddItems(list, e, converter); } private void SetListValuesFromSource(IList sourceList, IList targetList, Converter converter) { StopListeningForChangeEvents(targetList); targetList.Clear(); foreach (object o in sourceList) { targetList.Add(converter(o)); } ListenForChangeEvents(targetList); } private bool TargetAndMasterCollectionsAreEqual() { return _masterList.Cast().SequenceEqual(_targetList.Cast().Select(item => ConvertFromTargetToMaster(item))); } /// /// Makes sure that all synchronized lists have the same values as the source list. /// /// The source list. private void UpdateListsFromSource(IList sourceList) { if (sourceList == _masterList) { SetListValuesFromSource(_masterList, _targetList, ConvertFromMasterToTarget); } else { SetListValuesFromSource(_targetList, _masterList, ConvertFromTargetToMaster); } } /// /// An implementation that does nothing in the conversions. /// internal class DoNothingListItemConverter : IListItemConverter { /// /// Converts the specified master list item. /// /// The master list item. /// The result of the conversion. public object Convert(object masterListItem) { return masterListItem; } /// /// Converts the specified target list item. /// /// The target list item. /// The result of the conversion. public object ConvertBack(object targetListItem) { return targetListItem; } } public interface IListItemConverter { /// /// Converts the specified master list item. /// /// The master list item. /// The result of the conversion. object Convert(object masterListItem); /// /// Converts the specified target list item. /// /// The target list item. /// The result of the conversion. object ConvertBack(object targetListItem); } } }