diff --git a/IListItemConverter.cs b/IListItemConverter.cs
deleted file mode 100644
index 6fbb349..0000000
--- a/IListItemConverter.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace FunctionalFun.UI.Behaviours
-{
- ///
- /// Converts items in the Master list to Items in the target list, and back again.
- ///
- 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);
- }
-}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 68a49da..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,24 +0,0 @@
-This is free and unencumbered software released into the public domain.
-
-Anyone is free to copy, modify, publish, use, compile, sell, or
-distribute this software, either in source code form or as a compiled
-binary, for any purpose, commercial or non-commercial, and by any
-means.
-
-In jurisdictions that recognize copyright laws, the author or authors
-of this software dedicate any and all copyright interest in the
-software to the public domain. We make this dedication for the benefit
-of the public at large and to the detriment of our heirs and
-successors. We intend this dedication to be an overt act of
-relinquishment in perpetuity of all present and future rights to this
-software under copyright law.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
-
-For more information, please refer to
diff --git a/License.txt b/License.txt
new file mode 100644
index 0000000..d17e443
--- /dev/null
+++ b/License.txt
@@ -0,0 +1,9 @@
+The MIT License (MIT)
+
+Copyright (c) SelectedItemsSynchronizer contributors. (SelectedItemsSynchronizer@prims.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/MultiSelectorBehaviours.cs b/MultiSelectorBehaviours.cs
deleted file mode 100644
index 2e7b907..0000000
--- a/MultiSelectorBehaviours.cs
+++ /dev/null
@@ -1,134 +0,0 @@
-using System.Collections;
-using System.Windows;
-using System.Windows.Controls.Primitives;
-using System.Windows.Controls;
-using System;
-
-namespace FunctionalFun.UI.Behaviours
-{
- ///
- /// 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.");
- }
- }
-
- }
- }
-}
diff --git a/NextVersion.txt b/NextVersion.txt
new file mode 100644
index 0000000..9325c3c
--- /dev/null
+++ b/NextVersion.txt
@@ -0,0 +1 @@
+0.3.0
\ No newline at end of file
diff --git a/Psync.xcf b/Psync.xcf
new file mode 100644
index 0000000..8a971ff
Binary files /dev/null and b/Psync.xcf differ
diff --git a/Psync128.png b/Psync128.png
new file mode 100644
index 0000000..5f64c23
Binary files /dev/null and b/Psync128.png differ
diff --git a/Psync256.png b/Psync256.png
new file mode 100644
index 0000000..f24a7ca
Binary files /dev/null and b/Psync256.png differ
diff --git a/README.md b/README.md
index ed55975..afafce4 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,101 @@
+[![][build-img]][build]
+[![][nuget-img]][nuget]
+
+[build]: https://ci.appveyor.com/project/9swampy/selecteditemsbindingdemo
+[build-img]: https://ci.appveyor.com/api/projects/status/aa8mw3opmlyxc68u?svg=true
+
+[nuget]: https://badge.fury.io/nu/SelectedItemsSynchronizer
+[nuget-img]: https://badge.fury.io/nu/SelectedItemsSynchronizer.svg
+
SelectedItemsBindingDemo
========================
An example of how to bind the SelectedItems property of an ItemsControl in WPF to a ViewModel
+
+The origin of all this and a good explanation of how it all works can be found at
+http://blog.functionalfun.net/2009/02/how-to-databind-to-selecteditems.html
+
+The original example demo is still included in the code base, but the code has been reorganised a bit so that it can be published as a utility library in a NuGet package. I'm hoping you'll find it as useful as I have done, and making it available as a NuGet package should ease integration with your production code!
+
+Example usage
+-------------
+```C#
+List names;
+ObservableCollection selectedNames;
+ListView listView;
+
+[TestInitialize]
+public void TestInitialize()
+{
+ names = new List() { "Abraham", "Lincoln", "James", "Buchanan" };
+ selectedNames = new ObservableCollection();
+ listView = new ListView();
+ listView.ItemsSource = names;
+ listView.SelectionMode = SelectionMode.Extended;
+ MultiSelectorBehaviours.SetSynchronizedSelectedItems(listView, (IList)selectedNames);
+}
+
+[TestMethod]
+public void InitialiseToNoSelection()
+{
+ this.selectedNames.Count().Should().Be(0);
+}
+
+[TestMethod]
+public void ShouldSynchroniseListViewSelectAll()
+{
+ // Act
+ this.listView.SelectAll();
+
+ // Assert
+ this.selectedNames.Count().Should().Be(this.names.Count());
+}
+
+[TestMethod]
+public void ShouldSynchroniseListViewSetSelectedIndex()
+{
+ // Act
+ this.listView.SelectedIndex = 0;
+
+ // Assert
+ this.selectedNames.Count().Should().Be(1);
+}
+
+[TestMethod]
+public void ShouldSynchroniseListViewSetSelectedItem()
+{
+ //Arrange
+ Random random = new Random(DateTime.Now.Millisecond);
+ object itemToSelect = this.listView.Items.GetItemAt(random.Next(this.names.Count));
+
+ // Act
+ this.listView.SelectedItem = itemToSelect;
+
+ // Assert
+ this.selectedNames.Count().Should().Be(1);
+}
+
+[TestMethod]
+public void ShouldSynchroniseListViewAddSingleSelectedItem()
+{
+ //Arrange
+ Random random = new Random(DateTime.Now.Millisecond);
+ object itemToSelect = this.listView.Items.GetItemAt(random.Next(this.names.Count));
+
+ // Act
+ this.listView.SelectedItems.Add(itemToSelect);
+
+ // Assert
+ this.selectedNames.Count().Should().Be(1);
+}
+
+[TestMethod]
+public void ShouldSynchroniseListViewAddMultipleSelectedItems()
+{
+ // Act
+ this.listView.SelectedItems.Add(this.listView.Items.GetItemAt(0));
+ this.listView.SelectedItems.Add(this.listView.Items.GetItemAt(1));
+
+ // Assert
+ this.selectedNames.ShouldBeEquivalentTo(this.listView.SelectedItems, opt => opt.WithStrictOrdering());
+}
diff --git a/SelectedItemsBindingDemo.sln b/SelectedItemsBindingDemo.sln
deleted file mode 100644
index 8a34742..0000000
--- a/SelectedItemsBindingDemo.sln
+++ /dev/null
@@ -1,20 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 10.00
-# Visual C# Express 2008
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SelectedItemsBindingDemo", "SelectedItemsBindingDemo.csproj", "{7DA5A107-B474-4AC0-B861-63A489DB0C02}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {7DA5A107-B474-4AC0-B861-63A489DB0C02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7DA5A107-B474-4AC0-B861-63A489DB0C02}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7DA5A107-B474-4AC0-B861-63A489DB0C02}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7DA5A107-B474-4AC0-B861-63A489DB0C02}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
diff --git a/App.xaml b/SelectedItemsBindingDemo/App.xaml
similarity index 100%
rename from App.xaml
rename to SelectedItemsBindingDemo/App.xaml
diff --git a/App.xaml.cs b/SelectedItemsBindingDemo/App.xaml.cs
similarity index 100%
rename from App.xaml.cs
rename to SelectedItemsBindingDemo/App.xaml.cs
diff --git a/Properties/AssemblyInfo.cs b/SelectedItemsBindingDemo/Properties/AssemblyInfo.cs
similarity index 100%
rename from Properties/AssemblyInfo.cs
rename to SelectedItemsBindingDemo/Properties/AssemblyInfo.cs
diff --git a/Properties/Resources.Designer.cs b/SelectedItemsBindingDemo/Properties/Resources.Designer.cs
similarity index 84%
rename from Properties/Resources.Designer.cs
rename to SelectedItemsBindingDemo/Properties/Resources.Designer.cs
index e747212..1dd8401 100644
--- a/Properties/Resources.Designer.cs
+++ b/SelectedItemsBindingDemo/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/Resources.resx b/SelectedItemsBindingDemo/Properties/Resources.resx
similarity index 100%
rename from Properties/Resources.resx
rename to SelectedItemsBindingDemo/Properties/Resources.resx
diff --git a/Properties/Settings.Designer.cs b/SelectedItemsBindingDemo/Properties/Settings.Designer.cs
similarity index 78%
rename from Properties/Settings.Designer.cs
rename to SelectedItemsBindingDemo/Properties/Settings.Designer.cs
index 4a09fca..f51150d 100644
--- a/Properties/Settings.Designer.cs
+++ b/SelectedItemsBindingDemo/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/Properties/Settings.settings b/SelectedItemsBindingDemo/Properties/Settings.settings
similarity index 100%
rename from Properties/Settings.settings
rename to SelectedItemsBindingDemo/Properties/Settings.settings
diff --git a/RelayCommand.cs b/SelectedItemsBindingDemo/RelayCommand.cs
similarity index 100%
rename from RelayCommand.cs
rename to SelectedItemsBindingDemo/RelayCommand.cs
diff --git a/SelectedItemsBindingDemo.csproj b/SelectedItemsBindingDemo/SelectedItemsBindingDemo.csproj
similarity index 64%
rename from SelectedItemsBindingDemo.csproj
rename to SelectedItemsBindingDemo/SelectedItemsBindingDemo.csproj
index e89874d..e85dc50 100644
--- a/SelectedItemsBindingDemo.csproj
+++ b/SelectedItemsBindingDemo/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
@@ -68,8 +97,6 @@
-
-
Code
@@ -84,18 +111,36 @@
True
-
ResXFileCodeGenerator
Resources.Designer.cs
+
SettingsSingleFileGenerator
Settings.Designer.cs
+
+
+ False
+ .NET Framework 3.5 SP1 Client Profile
+ false
+
+
+ False
+ .NET Framework 3.5 SP1
+ true
+
+
+
+
+ {558e4d9f-ccb2-4da8-82b3-3ad41f4637e4}
+ PrimS.SelectedItemsSynchronizer.Net45
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Window1.xaml.cs b/SelectedItemsBindingDemo/Window1.xaml.cs
similarity index 100%
rename from Window1.xaml.cs
rename to SelectedItemsBindingDemo/Window1.xaml.cs
diff --git a/SelectedItemsBindingDemo/app.config b/SelectedItemsBindingDemo/app.config
new file mode 100644
index 0000000..51278a4
--- /dev/null
+++ b/SelectedItemsBindingDemo/app.config
@@ -0,0 +1,3 @@
+
+
+
diff --git a/SelectedItemsSynchronizer.0.5.1-beta.nupkg b/SelectedItemsSynchronizer.0.5.1-beta.nupkg
new file mode 100644
index 0000000..8a7c039
Binary files /dev/null and b/SelectedItemsSynchronizer.0.5.1-beta.nupkg differ
diff --git a/SelectedItemsSynchronizer.CiTests/MultiSelectorBehavioursCalendarTests.cs b/SelectedItemsSynchronizer.CiTests/MultiSelectorBehavioursCalendarTests.cs
new file mode 100644
index 0000000..9aa2c98
--- /dev/null
+++ b/SelectedItemsSynchronizer.CiTests/MultiSelectorBehavioursCalendarTests.cs
@@ -0,0 +1,81 @@
+namespace PrimS.SelectedItemsSynchronizer.CiTests
+{
+ using System;
+ using System.Collections;
+ using System.Collections.ObjectModel;
+ using System.Linq;
+ using System.Windows.Controls;
+ using FluentAssertions;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class MultiSelectorBehavioursCalendarTests
+ {
+ ObservableCollection selectedDates;
+ Calendar calendar;
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ selectedDates = new ObservableCollection();
+ calendar = new Calendar();
+ calendar.SelectionMode = CalendarSelectionMode.SingleRange;
+ MultiSelectorBehaviours.SetSynchronizedSelectedItems(calendar, (IList)selectedDates);
+ }
+
+ [TestMethod]
+ public void InitialiseToNoSelection()
+ {
+ this.selectedDates.Count().Should().Be(0);
+ }
+
+ [TestMethod]
+ public void SelectInCollectionShouldReflectOnCalendar()
+ {
+ // Arrange
+ this.calendar.MonitorEvents();
+
+ // Act
+ this.selectedDates.Add(DateTime.Today);
+
+ // Assert
+ this.calendar.ShouldRaise("SelectedDatesChanged");
+ }
+
+ [TestMethod]
+ public void SelectOnCalendarShouldReflectInCollection()
+ {
+ // Act
+ this.calendar.SelectedDates.Add(DateTime.Today);
+
+ // Assert
+ this.selectedDates.Count().Should().Be(1);
+ }
+
+ [TestMethod]
+ public void SelectConsecutiveOnCalendarShouldReflectInCollection()
+ {
+ // Act
+ this.calendar.SelectedDates.AddRange(DateTime.Today, DateTime.Today.AddDays(1));
+
+ // Assert
+ this.selectedDates.Count().Should().Be(2);
+ }
+
+ [Ignore]
+ [TestMethod]
+ public void SelectConsecutiveInCollectionShouldReflectOnCalendar()
+ {
+ //This test is failing to highlight the fact that the implementation currently only works OneWayToSource due to the
+ //odd behaviour of SelectedDateCollection.Add.
+
+ // Act
+ this.selectedDates.Add(DateTime.Today);
+ this.selectedDates.Add(DateTime.Today.AddDays(1));
+
+ // Assert
+ this.selectedDates.Count().Should().Be(2);
+ this.calendar.SelectedDates.Count().Should().Be(2);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer.CiTests/MultiSelectorBehavioursListViewTests.cs b/SelectedItemsSynchronizer.CiTests/MultiSelectorBehavioursListViewTests.cs
new file mode 100644
index 0000000..7968edf
--- /dev/null
+++ b/SelectedItemsSynchronizer.CiTests/MultiSelectorBehavioursListViewTests.cs
@@ -0,0 +1,136 @@
+namespace PrimS.SelectedItemsSynchronizer.CiTests
+{
+ using FluentAssertions;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Linq;
+ using System.Windows.Controls;
+
+ [TestClass]
+ public class MultiSelectorBehavioursListViewTests
+ {
+ List names;
+ ObservableCollection selectedNames;
+ ListView listView;
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ names = new List() { "Abraham", "Lincoln", "James", "Buchanan" };
+ selectedNames = new ObservableCollection();
+ listView = new ListView();
+ listView.ItemsSource = names;
+ listView.SelectionMode = SelectionMode.Extended;
+ MultiSelectorBehaviours.SetSynchronizedSelectedItems(listView, (IList)selectedNames);
+ }
+
+ [TestMethod]
+ public void InitialiseToNoSelection()
+ {
+ this.selectedNames.Count().Should().Be(0);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseListViewSelectAll()
+ {
+ // Act
+ this.listView.SelectAll();
+
+ // Assert
+ this.selectedNames.Count().Should().Be(this.names.Count());
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseListViewSetSelectedIndex()
+ {
+ // Act
+ this.listView.SelectedIndex = 0;
+
+ // Assert
+ this.selectedNames.Count().Should().Be(1);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseListViewSetSelectedItem()
+ {
+ //Arrange
+ Random random = new Random(DateTime.Now.Millisecond);
+ object itemToSelect = this.listView.Items.GetItemAt(random.Next(this.names.Count));
+
+ // Act
+ this.listView.SelectedItem = itemToSelect;
+
+ // Assert
+ this.selectedNames.Count().Should().Be(1);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseListViewAddSingleSelectedItem()
+ {
+ //Arrange
+ Random random = new Random(DateTime.Now.Millisecond);
+ object itemToSelect = this.listView.Items.GetItemAt(random.Next(this.names.Count));
+
+ // Act
+ this.listView.SelectedItems.Add(itemToSelect);
+
+ // Assert
+ this.selectedNames.Count().Should().Be(1);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseListViewAddMultipleSelectedItems()
+ {
+ // Act
+ this.listView.SelectedItems.Add(this.listView.Items.GetItemAt(0));
+ this.listView.SelectedItems.Add(this.listView.Items.GetItemAt(1));
+
+ // Assert
+ this.selectedNames.ShouldBeEquivalentTo(this.listView.SelectedItems, opt => opt.WithStrictOrdering());
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseListAddMultipleSelectedItems()
+ {
+ // Act
+ this.selectedNames.Add(this.names.First());
+ this.selectedNames.Add(this.names.Last());
+
+ // Assert
+ this.listView.SelectedItems.ShouldBeEquivalentTo(this.selectedNames, opt => opt.WithStrictOrdering());
+ }
+
+ [TestMethod]
+ public void ShouldNotSynchroniseListAddMultipleSelectedItems()
+ {
+ //Arrange
+ ObservableCollection secondSelectedNames = new ObservableCollection();
+
+ // Act
+ MultiSelectorBehaviours.SetSynchronizedSelectedItems(listView, (IList)secondSelectedNames);
+ this.selectedNames.Add(this.names.First());
+ this.selectedNames.Add(this.names.Last());
+
+ // Assert
+ this.listView.SelectedItems.Should().NotBeEquivalentTo(this.selectedNames);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseChangedListAddMultipleSelectedItems()
+ {
+ //Arrange
+ ObservableCollection secondSelectedNames = new ObservableCollection();
+
+ // Act
+ MultiSelectorBehaviours.SetSynchronizedSelectedItems(listView, (IList)secondSelectedNames);
+ secondSelectedNames.Add(this.names.First());
+ secondSelectedNames.Add(this.names.Last());
+
+ // Assert
+ this.listView.SelectedItems.ShouldBeEquivalentTo(secondSelectedNames);
+ }
+ }
+}
diff --git a/SelectedItemsSynchronizer.CiTests/PrimS.SelectedItemsSynchronizer.CiTests.csproj b/SelectedItemsSynchronizer.CiTests/PrimS.SelectedItemsSynchronizer.CiTests.csproj
new file mode 100644
index 0000000..02b30ed
--- /dev/null
+++ b/SelectedItemsSynchronizer.CiTests/PrimS.SelectedItemsSynchronizer.CiTests.csproj
@@ -0,0 +1,113 @@
+
+
+
+ Debug
+ AnyCPU
+ {918FE624-1E12-48BE-A3F0-FC1B5CF1412F}
+ Library
+ Properties
+ PrimS.SelectedItemsSynchronizer.CiTests
+ PrimS.SelectedItemsSynchronizer.CiTests
+ v4.5.1
+ 512
+ {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
+ False
+ UnitTest
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ False
+ ..\packages\FluentAssertions.4.0.0\lib\net45\FluentAssertions.dll
+
+
+ False
+ ..\packages\FluentAssertions.4.0.0\lib\net45\FluentAssertions.Core.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {6fd3314f-9a43-4fcf-a073-6c97ca2d1e81}
+ PrimS.SelectedItemsSynchronizer
+
+
+
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+ False
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer.CiTests/Properties/AssemblyInfo.cs b/SelectedItemsSynchronizer.CiTests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..e9aabdd
--- /dev/null
+++ b/SelectedItemsSynchronizer.CiTests/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("PrimS.SelectedItemsSynchronizer.CiTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PrimS.SelectedItemsSynchronizer.CiTests")]
+[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("5726167d-d6a5-4e8b-930f-e5d3009afd66")]
+
+// 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.CiTests/TwoListSynchronizerAdditionBehaviour.cs b/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerAdditionBehaviour.cs
new file mode 100644
index 0000000..769d152
--- /dev/null
+++ b/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerAdditionBehaviour.cs
@@ -0,0 +1,65 @@
+namespace PrimS.SelectedItemsSynchronizer.CiTests
+{
+ using System;
+ using System.Collections.ObjectModel;
+ using System.Linq;
+ using FluentAssertions;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class TwoListSynchronizerAdditionBehaviour
+ {
+ private ObservableCollection masterList;
+ private ObservableCollection targetList;
+ private TwoListSynchronizer sut;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext context)
+ {
+ }
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ this.masterList = new ObservableCollection();
+ this.targetList = new ObservableCollection();
+ this.sut = new TwoListSynchronizer(this.masterList, this.targetList);
+ }
+
+ [TestMethod]
+ public void ShouldNotSynchroniseAddition()
+ {
+ // Act
+ this.masterList.Add("testString");
+
+ // Assert
+ this.targetList.Count().Should().Be(0);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseAddition()
+ {
+ // Arrange
+ this.sut.StartSynchronizing();
+
+ // Act
+ this.masterList.Add("testString");
+
+ // Assert
+ this.targetList.Count().Should().Be(1);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseAdditionOnTarget()
+ {
+ // Arrange
+ this.sut.StartSynchronizing();
+
+ // Act
+ this.targetList.Add("testString");
+
+ // Assert
+ this.masterList.Count().Should().Be(1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerDeletionBehaviour.cs b/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerDeletionBehaviour.cs
new file mode 100644
index 0000000..8e68ab2
--- /dev/null
+++ b/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerDeletionBehaviour.cs
@@ -0,0 +1,118 @@
+namespace PrimS.SelectedItemsSynchronizer.CiTests
+{
+ using System.Collections.ObjectModel;
+ using System.Linq;
+ using FluentAssertions;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class TwoListSynchronizerDeletionBehaviour
+ {
+ private ObservableCollection masterList;
+ private ObservableCollection targetList;
+ private TwoListSynchronizer sut;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext context)
+ {
+ }
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ this.masterList = new ObservableCollection();
+ this.masterList.Add("InitialisedFirst");
+ this.masterList.Add("InitialisedSecond");
+ this.targetList = new ObservableCollection();
+ this.sut = new TwoListSynchronizer(this.masterList, this.targetList);
+ this.sut.StartSynchronizing();
+ }
+
+ [TestMethod]
+ public void ShouldBeSynchronised()
+ {
+ // Assert
+ this.targetList.ShouldBeEquivalentTo(this.masterList, opt => opt.WithStrictOrdering());
+ }
+
+ [TestMethod]
+ public void ShouldNotSynchroniseDeletion()
+ {
+ // Arrange
+ this.sut.StopSynchronizing();
+
+ // Act
+ this.masterList.Remove("InitialisedFirst");
+
+ // Assert
+ this.targetList.Count().Should().Be(this.masterList.Count() + 1);
+ }
+
+ [TestMethod]
+ public void ShouldNotSynchroniseDeletionByIndex()
+ {
+ // Arrange
+ this.sut.StopSynchronizing();
+
+ // Act
+ this.masterList.RemoveAt(0);
+
+ // Assert
+ this.targetList.Count().Should().Be(this.masterList.Count() + 1);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseDeletion()
+ {
+ // Act
+ this.masterList.Remove("InitialisedFirst");
+
+ // Assert
+ this.targetList.ShouldBeEquivalentTo(this.masterList);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseDeletionByIndex()
+ {
+ // Act
+ this.masterList.RemoveAt(0);
+
+ // Assert
+ this.targetList.ShouldBeEquivalentTo(this.masterList);
+ }
+
+ [TestMethod]
+ public void ShouldNotSynchroniseClearing()
+ {
+ // Arrange
+ this.sut.StopSynchronizing();
+ int priorCount = this.masterList.Count();
+
+ // Act
+ this.masterList.Clear();
+
+ // Assert
+ this.targetList.Count().Should().Be(priorCount);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseClearing()
+ {
+ // Act
+ this.masterList.Clear();
+
+ // Assert
+ this.targetList.ShouldBeEquivalentTo(this.masterList);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseClearingOfTarget()
+ {
+ // Act
+ this.targetList.Clear();
+
+ // Assert
+ this.masterList.ShouldBeEquivalentTo(this.targetList);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerInitialization.cs b/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerInitialization.cs
new file mode 100644
index 0000000..3b3682b
--- /dev/null
+++ b/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerInitialization.cs
@@ -0,0 +1,37 @@
+namespace PrimS.SelectedItemsSynchronizer.CiTests
+{
+ using System.Collections.ObjectModel;
+ using FluentAssertions;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class TwoListSynchronizerInitialization
+ {
+ [TestMethod]
+ public void ShouldCopyInitialMembersToTarget()
+ {
+ ObservableCollection masterList = new ObservableCollection();
+ masterList.Add("Initial");
+ ObservableCollection targetList = new ObservableCollection();
+
+ TwoListSynchronizer sut = new TwoListSynchronizer(masterList, targetList);
+ sut.StartSynchronizing();
+
+ targetList.ShouldBeEquivalentTo(masterList, "ctor should initialise targetList.");
+ }
+
+ [TestMethod]
+ public void ShouldCopyInitialMembersFromTarget()
+ {
+ ObservableCollection masterList = new ObservableCollection();
+ ObservableCollection targetList = new ObservableCollection();
+ targetList.Add("Initial");
+
+ TwoListSynchronizer sut = new TwoListSynchronizer(masterList, targetList);
+ sut.StartSynchronizing();
+
+ targetList.ShouldBeEquivalentTo(masterList, "ctor should initialise targetList.");
+ targetList.Should().BeEmpty("ctor should overwrite targetList with masterList content.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerInsertionBehaviour.cs b/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerInsertionBehaviour.cs
new file mode 100644
index 0000000..bed3d20
--- /dev/null
+++ b/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerInsertionBehaviour.cs
@@ -0,0 +1,70 @@
+namespace PrimS.SelectedItemsSynchronizer.CiTests
+{
+ using System.Collections.ObjectModel;
+ using System.Linq;
+ using FluentAssertions;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class TwoListSynchronizerInsertionBehaviour
+ {
+ private ObservableCollection masterList;
+ private ObservableCollection targetList;
+ private TwoListSynchronizer sut;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext context)
+ {
+ }
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ this.masterList = new ObservableCollection();
+ this.masterList.Add("Initialised");
+ this.targetList = new ObservableCollection();
+ this.sut = new TwoListSynchronizer(this.masterList, this.targetList);
+ this.sut.StartSynchronizing();
+ }
+
+ [TestMethod]
+ public void ShouldBeSynchronised()
+ {
+ // Assert
+ this.targetList.ShouldBeEquivalentTo(this.masterList);
+ }
+
+ [TestMethod]
+ public void ShouldNotSynchroniseInsertion()
+ {
+ // Arrange
+ this.sut.StopSynchronizing();
+
+ // Act
+ this.masterList.Insert(0, "Insertion");
+
+ // Assert
+ this.targetList.Count().Should().Be(this.masterList.Count() - 1);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseInsertion()
+ {
+ // Act
+ this.masterList.Insert(0, "Insertion");
+
+ // Assert
+ this.targetList.ShouldBeEquivalentTo(this.masterList, opt => opt.WithStrictOrdering());
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseInsertionOnTarget()
+ {
+ // Act
+ this.targetList.Insert(0, "Insertion");
+
+ // Assert
+ this.masterList.ShouldBeEquivalentTo(this.targetList, opt => opt.WithStrictOrdering());
+ }
+ }
+}
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerMoveBehaviour.cs b/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerMoveBehaviour.cs
new file mode 100644
index 0000000..24ab3f1
--- /dev/null
+++ b/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerMoveBehaviour.cs
@@ -0,0 +1,72 @@
+namespace PrimS.SelectedItemsSynchronizer.CiTests
+{
+ using System.Collections.ObjectModel;
+ using System.Linq;
+ using FluentAssertions;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class TwoListSynchronizerMoveBehaviour
+ {
+ private ObservableCollection masterList;
+ private ObservableCollection targetList;
+ private TwoListSynchronizer sut;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext context)
+ {
+ }
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ this.masterList = new ObservableCollection();
+ this.masterList.Add("InitialisedFirst");
+ this.masterList.Add("InitialisedSecond");
+ this.targetList = new ObservableCollection();
+ this.sut = new TwoListSynchronizer(this.masterList, this.targetList);
+ this.sut.StartSynchronizing();
+ }
+
+ [TestMethod]
+ public void ShouldBeSynchronised()
+ {
+ // Assert
+ this.targetList.ShouldBeEquivalentTo(this.masterList);
+ }
+
+ [TestMethod]
+ public void ShouldNotSynchroniseMove()
+ {
+ // Arrange
+ this.sut.StopSynchronizing();
+
+ // Act
+ this.masterList.Move(0, 1);
+
+ // Assert
+ this.targetList.First().Should().Be(this.masterList.Last());
+ this.targetList.ShouldBeEquivalentTo(this.masterList.Reverse(), opt => opt.WithStrictOrdering());
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseMove()
+ {
+ // Act
+ this.masterList.Move(0, 1);
+
+ // Assert
+ this.targetList.ShouldBeEquivalentTo(this.masterList, opt => opt.WithStrictOrdering());
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseMoveOnTarget()
+ {
+ // Act
+ this.targetList.Move(0, 1);
+
+ // Assert
+ this.targetList.ShouldBeEquivalentTo(this.masterList, opt => opt.WithStrictOrdering());
+ }
+ }
+}
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerReplacementBehaviour.cs b/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerReplacementBehaviour.cs
new file mode 100644
index 0000000..ece4917
--- /dev/null
+++ b/SelectedItemsSynchronizer.CiTests/TwoListSynchronizerReplacementBehaviour.cs
@@ -0,0 +1,70 @@
+namespace PrimS.SelectedItemsSynchronizer.CiTests
+{
+ using System.Collections.ObjectModel;
+ using FluentAssertions;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class TwoListSynchronizerReplacementBehaviour
+ {
+ private ObservableCollection masterList;
+ private ObservableCollection targetList;
+ private TwoListSynchronizer sut;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext context)
+ {
+ }
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ this.masterList = new ObservableCollection();
+ this.masterList.Add("InitialisedFirst");
+ this.masterList.Add("InitialisedSecond");
+ this.targetList = new ObservableCollection();
+ this.sut = new TwoListSynchronizer(this.masterList, this.targetList);
+ this.sut.StartSynchronizing();
+ }
+
+ [TestMethod]
+ public void ShouldBeSynchronised()
+ {
+ // Assert
+ this.targetList.ShouldBeEquivalentTo(this.masterList);
+ }
+
+ [TestMethod]
+ public void ShouldNotSynchroniseReplacement()
+ {
+ // Arrange
+ this.sut.StopSynchronizing();
+
+ // Act
+ this.masterList[0] = "Replaced";
+
+ // Assert
+ this.targetList.Should().NotBeEquivalentTo(this.masterList);
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseReplacement()
+ {
+ // Act
+ this.masterList[0] = "Replaced";
+
+ // Assert
+ this.targetList.ShouldBeEquivalentTo(this.masterList, opt => opt.WithStrictOrdering());
+ }
+
+ [TestMethod]
+ public void ShouldSynchroniseReplacementOnTarget()
+ {
+ // Act
+ this.targetList[0] = "Replaced";
+
+ // Assert
+ this.targetList.ShouldBeEquivalentTo(this.masterList, opt => opt.WithStrictOrdering());
+ }
+ }
+}
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer.CiTests/packages.config b/SelectedItemsSynchronizer.CiTests/packages.config
new file mode 100644
index 0000000..dec037f
--- /dev/null
+++ b/SelectedItemsSynchronizer.CiTests/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer.Net45/PrimS.SelectedItemsSynchronizer.Net45.csproj b/SelectedItemsSynchronizer.Net45/PrimS.SelectedItemsSynchronizer.Net45.csproj
new file mode 100644
index 0000000..9b6b9a6
--- /dev/null
+++ b/SelectedItemsSynchronizer.Net45/PrimS.SelectedItemsSynchronizer.Net45.csproj
@@ -0,0 +1,83 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {558E4D9F-CCB2-4DA8-82B3-3AD41F4637E4}
+ Library
+ Properties
+ PrimS.SelectedItemsSynchronizer
+ PrimS.SelectedItemsSynchronizer.Net45
+ v4.5
+ 512
+ 52132b50
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ bin\Release\PrimS.SelectedItemsSynchronizer.Net45.XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IListItemConverter.cs
+
+
+ ISynchronizationManager.cs
+
+
+ MultiSelectorBehaviours.cs
+
+
+ TwoListSynchronizer.cs
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer.Net45/Properties/AssemblyInfo.cs b/SelectedItemsSynchronizer.Net45/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..787c6d0
--- /dev/null
+++ b/SelectedItemsSynchronizer.Net45/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("PrimS.SelectedItemsSynchronizer.NET45")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PrimS.SelectedItemsSynchronizer.NET45")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[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("c3060751-daef-4bba-a3af-fdbceea2bf5c")]
+
+// 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.Net45/packages.config b/SelectedItemsSynchronizer.Net45/packages.config
new file mode 100644
index 0000000..818b649
--- /dev/null
+++ b/SelectedItemsSynchronizer.Net45/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer.nuspec b/SelectedItemsSynchronizer.nuspec
new file mode 100644
index 0000000..17b61e3
--- /dev/null
+++ b/SelectedItemsSynchronizer.nuspec
@@ -0,0 +1,27 @@
+
+
+
+ SelectedItemsSynchronizer
+ 0.5.0-beta
+ SelectedItemsSynchronizer
+ SelectedItemsSynchronizer contributors
+ 9swampy
+ https://raw.githubusercontent.com/9swampy/SelectedItemsBindingDemo/SelectedItemsSynchronizer/License.txt
+ https://github.com/9swampy/SelectedItemsBindingDemo/tree/SelectedItemsSynchronizer
+ https://raw.githubusercontent.com/9swampy/SelectedItemsBindingDemo/SelectedItemsSynchronizer/Psync128.png
+ false
+ Facilitates two-way binding of the readonly MultiSelector.SelectedItems property in ListBox/ListView on WPF. There's also a working prototype/example for binding Calendar.SelectedDates using a similar mechanism.
+
+ Add Net4.0 package.
+
+ Copyright (c) 9swampy, SelectedItemSynchronizer contributors
+ SelectedItems WPF binding ViewModel MVVM multiselector selector ListView ListBox
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer.sln b/SelectedItemsSynchronizer.sln
new file mode 100644
index 0000000..2227ff7
--- /dev/null
+++ b/SelectedItemsSynchronizer.sln
@@ -0,0 +1,45 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.31101.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SelectedItemsBindingDemo", "SelectedItemsBindingDemo\SelectedItemsBindingDemo.csproj", "{7DA5A107-B474-4AC0-B861-63A489DB0C02}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrimS.SelectedItemsSynchronizer", "SelectedItemsSynchronizer\PrimS.SelectedItemsSynchronizer.csproj", "{6FD3314F-9A43-4FCF-A073-6C97CA2D1E81}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrimS.SelectedItemsSynchronizer.CiTests", "SelectedItemsSynchronizer.CiTests\PrimS.SelectedItemsSynchronizer.CiTests.csproj", "{918FE624-1E12-48BE-A3F0-FC1B5CF1412F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrimS.SelectedItemsSynchronizer.Net45", "SelectedItemsSynchronizer.NET45\PrimS.SelectedItemsSynchronizer.Net45.csproj", "{558E4D9F-CCB2-4DA8-82B3-3AD41F4637E4}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{3BA1A4D4-A636-4B35-8DD8-2EC985FC174A}"
+ ProjectSection(SolutionItems) = preProject
+ SelectedItemsSynchronizer.nuspec = SelectedItemsSynchronizer.nuspec
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {7DA5A107-B474-4AC0-B861-63A489DB0C02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7DA5A107-B474-4AC0-B861-63A489DB0C02}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7DA5A107-B474-4AC0-B861-63A489DB0C02}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7DA5A107-B474-4AC0-B861-63A489DB0C02}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6FD3314F-9A43-4FCF-A073-6C97CA2D1E81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6FD3314F-9A43-4FCF-A073-6C97CA2D1E81}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6FD3314F-9A43-4FCF-A073-6C97CA2D1E81}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6FD3314F-9A43-4FCF-A073-6C97CA2D1E81}.Release|Any CPU.Build.0 = Release|Any CPU
+ {918FE624-1E12-48BE-A3F0-FC1B5CF1412F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {918FE624-1E12-48BE-A3F0-FC1B5CF1412F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {918FE624-1E12-48BE-A3F0-FC1B5CF1412F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {918FE624-1E12-48BE-A3F0-FC1B5CF1412F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {558E4D9F-CCB2-4DA8-82B3-3AD41F4637E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {558E4D9F-CCB2-4DA8-82B3-3AD41F4637E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {558E4D9F-CCB2-4DA8-82B3-3AD41F4637E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {558E4D9F-CCB2-4DA8-82B3-3AD41F4637E4}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/SelectedItemsSynchronizer.sln.GhostDoc.xml b/SelectedItemsSynchronizer.sln.GhostDoc.xml
new file mode 100644
index 0000000..0c987e9
--- /dev/null
+++ b/SelectedItemsSynchronizer.sln.GhostDoc.xml
@@ -0,0 +1,6 @@
+
+
+ *.min.js
+ jquery*.js
+
+
diff --git a/SelectedItemsSynchronizer/IListItemConverter.cs b/SelectedItemsSynchronizer/IListItemConverter.cs
new file mode 100644
index 0000000..d3ef54d
--- /dev/null
+++ b/SelectedItemsSynchronizer/IListItemConverter.cs
@@ -0,0 +1,22 @@
+namespace PrimS.SelectedItemsSynchronizer
+{
+ ///
+ /// Converts items in the Master list to Items in the target list, and back again.
+ ///
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer/ISynchronizationManager.cs b/SelectedItemsSynchronizer/ISynchronizationManager.cs
new file mode 100644
index 0000000..771a79f
--- /dev/null
+++ b/SelectedItemsSynchronizer/ISynchronizationManager.cs
@@ -0,0 +1,20 @@
+namespace PrimS.SelectedItemsSynchronizer
+{
+ using System;
+
+ ///
+ /// Provides functionality to interact with the Synchronization Manager.
+ ///
+ public interface ISynchronizationManager
+ {
+ ///
+ /// Starts the synchronizing.
+ ///
+ void StartSynchronizing();
+
+ ///
+ /// Stops the synchronizing.
+ ///
+ void StopSynchronizing();
+ }
+}
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer/MultiSelectorBehaviours.cs b/SelectedItemsSynchronizer/MultiSelectorBehaviours.cs
new file mode 100644
index 0000000..027feea
--- /dev/null
+++ b/SelectedItemsSynchronizer/MultiSelectorBehaviours.cs
@@ -0,0 +1,179 @@
+namespace PrimS.SelectedItemsSynchronizer
+{
+ using System;
+ using System.Collections;
+ using System.Windows;
+ using System.Windows.Controls;
+ using System.Windows.Controls.Primitives;
+
+ ///
+ /// A sync behaviour for a multiselector.
+ ///
+ public static class MultiSelectorBehaviours
+ {
+ ///
+ /// The synchronized selected items.
+ ///
+ 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(ISynchronizationManager), 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 ISynchronizationManager GetSynchronizationManager(DependencyObject dependencyObject)
+ {
+ return (ISynchronizationManager)dependencyObject.GetValue(SynchronizationManagerProperty);
+ }
+
+ private static void SetSynchronizationManager(DependencyObject dependencyObject, ISynchronizationManager value)
+ {
+ dependencyObject.SetValue(SynchronizationManagerProperty, value);
+ }
+
+ private static void OnSynchronizedSelectedItemsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
+ {
+ if (e.OldValue != null)
+ {
+ ISynchronizationManager synchronizer = GetSynchronizationManager(dependencyObject);
+ synchronizer.StopSynchronizing();
+
+ SetSynchronizationManager(dependencyObject, null);
+ }
+
+ Calendar calendar = dependencyObject as Calendar;
+ if (calendar != null)
+ {
+ ISynchronizationManager synchronizer = GetSynchronizationManager(dependencyObject);
+ if (synchronizer == null)
+ {
+ synchronizer = new CalendarSynchronizationManager(calendar);
+ SetSynchronizationManager(dependencyObject, synchronizer);
+ }
+
+ synchronizer.StartSynchronizing();
+ }
+ else
+ {
+ 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)
+ {
+ ISynchronizationManager synchronizer = GetSynchronizationManager(dependencyObject);
+ if (synchronizer == null)
+ {
+ synchronizer = new SelectorSynchronizationManager(selector);
+ SetSynchronizationManager(dependencyObject, synchronizer);
+ }
+
+ synchronizer.StartSynchronizing();
+ }
+ }
+ }
+
+ private class CalendarSynchronizationManager : BaseSynchronizationManager, ISynchronizationManager
+ {
+ ///
+ /// Initialises a new instance of the class.
+ ///
+ /// The calendar to sync.
+ internal CalendarSynchronizationManager(Calendar calendar)
+ : base(calendar)
+ {
+ }
+
+ protected override IList GetSelectedItemsCollection(Calendar calendar)
+ {
+ return calendar.SelectedDates;
+ }
+ }
+
+ private abstract class BaseSynchronizationManager : ISynchronizationManager
+ where T : DependencyObject
+ {
+ private readonly T source;
+ private TwoListSynchronizer synchronizer;
+
+ protected BaseSynchronizationManager(T source)
+ {
+ this.source = source;
+ }
+
+ ///
+ /// Starts synchronizing the list.
+ ///
+ public void StartSynchronizing()
+ {
+ IList list = GetSynchronizedSelectedItems(this.source);
+
+ if (list != null)
+ {
+ this.synchronizer = new TwoListSynchronizer(this.GetSelectedItemsCollection(this.source), list);
+ this.synchronizer.StartSynchronizing();
+ }
+ }
+
+ ///
+ /// Stops synchronizing the list.
+ ///
+ public void StopSynchronizing()
+ {
+ this.synchronizer.StopSynchronizing();
+ }
+
+ protected abstract IList GetSelectedItemsCollection(T source);
+ }
+
+ ///
+ /// A synchronization manager.
+ ///
+ private class SelectorSynchronizationManager : BaseSynchronizationManager
+ {
+ ///
+ /// Initialises a new instance of the class.
+ ///
+ /// The selector.
+ internal SelectorSynchronizationManager(Selector selector)
+ : base(selector)
+ {
+ }
+
+ protected override 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.");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer/PrimS.SelectedItemsSynchronizer.csproj b/SelectedItemsSynchronizer/PrimS.SelectedItemsSynchronizer.csproj
new file mode 100644
index 0000000..f81fbc1
--- /dev/null
+++ b/SelectedItemsSynchronizer/PrimS.SelectedItemsSynchronizer.csproj
@@ -0,0 +1,75 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {6FD3314F-9A43-4FCF-A073-6C97CA2D1E81}
+ Library
+ Properties
+ PrimS.SelectedItemsSynchronizer
+ PrimS.SelectedItemsSynchronizer
+ v4.0
+ 512
+
+ 44b8d61f
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ bin\Release\PrimS.SelectedItemsSynchronizer.XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer/Properties/AssemblyInfo.cs b/SelectedItemsSynchronizer/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..85d60e3
--- /dev/null
+++ b/SelectedItemsSynchronizer/Properties/AssemblyInfo.cs
@@ -0,0 +1,15 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("SelectedItemsSynchronizer")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SelectedItemsSynchronizer")]
+[assembly: AssemblyCopyright("Copyright © 2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: ComVisible(false)]
+[assembly: Guid("6050c108-0753-48eb-8c19-b20516e1c3e7")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
\ No newline at end of file
diff --git a/SelectedItemsSynchronizer/TwoListSynchronizer.cs b/SelectedItemsSynchronizer/TwoListSynchronizer.cs
new file mode 100644
index 0000000..bd1ea7c
--- /dev/null
+++ b/SelectedItemsSynchronizer/TwoListSynchronizer.cs
@@ -0,0 +1,304 @@
+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;
+
+ ///
+ /// Initialises a new instance of the class.
+ ///
+ /// The master list.
+ /// The target list.
+ /// The master-target converter.
+ public TwoListSynchronizer(IList masterList, IList targetList, IListItemConverter masterTargetConverter)
+ {
+ this.masterList = masterList;
+ this.targetList = targetList;
+ this.masterTargetConverter = masterTargetConverter;
+ }
+
+ ///
+ /// Initialises 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