From 7f40b7183647a0e03d1b90834ddda6968f582826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Cobe=C3=B1a=20Mori=C3=A1n?= Date: Mon, 21 Jul 2014 10:23:14 +0200 Subject: [PATCH 01/18] Improved performance when selecting multiple items --- Properties/Resources.Designer.cs | 46 +++++++++++++------------------- Properties/Settings.Designer.cs | 24 +++++++---------- SelectedItemsBindingDemo.csproj | 46 ++++++++++++++++++++++++++++++-- SelectedItemsBindingDemo.sln | 6 +++-- ViewModel.cs | 41 ++++++++++++++++++++-------- Window1.xaml | 31 +++++++++++++-------- app.config | 3 +++ 7 files changed, 130 insertions(+), 67 deletions(-) create mode 100644 app.config diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index e747212..1dd8401 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -1,17 +1,17 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:2.0.50727.3082 +// Runtime Version:4.0.30319.34014 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ -namespace SelectedItemsBindingDemo.Properties -{ - - +namespace SelectedItemsBindingDemo.Properties { + using System; + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -19,51 +19,43 @@ namespace SelectedItemsBindingDemo.Properties // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - + internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { + internal Resources() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SelectedItemsBindingDemo.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { + internal static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } - set - { + set { resourceCulture = value; } } diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs index 4a09fca..f51150d 100644 --- a/Properties/Settings.Designer.cs +++ b/Properties/Settings.Designer.cs @@ -1,28 +1,24 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:2.0.50727.3082 +// Runtime Version:4.0.30319.34014 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ -namespace SelectedItemsBindingDemo.Properties -{ - - +namespace SelectedItemsBindingDemo.Properties { + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { + + public static Settings Default { + get { return defaultInstance; } } diff --git a/SelectedItemsBindingDemo.csproj b/SelectedItemsBindingDemo.csproj index e89874d..a7abbc6 100644 --- a/SelectedItemsBindingDemo.csproj +++ b/SelectedItemsBindingDemo.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -10,10 +10,31 @@ Properties SelectedItemsBindingDemo SelectedItemsBindingDemo - v3.5 + v4.5 512 {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 4 + + + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true true @@ -23,6 +44,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -31,12 +53,15 @@ TRACE prompt 4 + false 3.5 + + 3.5 @@ -53,10 +78,14 @@ MSBuild:Compile Designer + MSBuild:Compile + Designer MSBuild:Compile Designer + MSBuild:Compile + Designer App.xaml @@ -90,12 +119,25 @@ ResXFileCodeGenerator Resources.Designer.cs + SettingsSingleFileGenerator Settings.Designer.cs + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + \ No newline at end of file diff --git a/SelectedItemsSynchronizer/Properties/AssemblyInfo.cs b/SelectedItemsSynchronizer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0b844b0 --- /dev/null +++ b/SelectedItemsSynchronizer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SelectedItemsSynchronizer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SelectedItemsSynchronizer")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6050c108-0753-48eb-8c19-b20516e1c3e7")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SelectedItemsSynchronizer/TwoListSynchronizer.cs b/SelectedItemsSynchronizer/TwoListSynchronizer.cs new file mode 100644 index 0000000..a9cbb2e --- /dev/null +++ b/SelectedItemsSynchronizer/TwoListSynchronizer.cs @@ -0,0 +1,276 @@ +namespace PrimS.SelectedItemsSynchronizer +{ + using System; + using System.Collections; + using System.Collections.Specialized; + using System.Linq; + using System.Windows; + + /// + /// Keeps two lists synchronized. + /// + 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; + } + } + } +} \ No newline at end of file diff --git a/SelectedItemsSynchronizer/packages.config b/SelectedItemsSynchronizer/packages.config new file mode 100644 index 0000000..7ae2d07 --- /dev/null +++ b/SelectedItemsSynchronizer/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Settings.StyleCop b/Settings.StyleCop new file mode 100644 index 0000000..1a17ff1 --- /dev/null +++ b/Settings.StyleCop @@ -0,0 +1,47 @@ + + + False + False + + Autofac + bootstrapper + proxied + + en-GB + + + + + + + True + + + + + False + + + + + True + + + + + True + True + + + + + + + True + + + + + + + \ No newline at end of file diff --git a/TwoListSynchronizer.cs b/TwoListSynchronizer.cs deleted file mode 100644 index ae39258..0000000 --- a/TwoListSynchronizer.cs +++ /dev/null @@ -1,276 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Specialized; -using System.Linq; -using System.Windows; - -namespace FunctionalFun.UI.Behaviours -{ - /// - /// Keeps two lists synchronized. - /// - 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; - } - } - } -} \ No newline at end of file diff --git a/Window1.xaml b/Window1.xaml deleted file mode 100644 index a5c40c3..0000000 --- a/Window1.xaml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - -