├── .gitattributes ├── .gitignore ├── ExtendedObservableCollection ├── ExtendedObservableCollection.sln ├── Gstc.Collections.Observable.Examples │ ├── App.config │ ├── App.xaml │ ├── App.xaml.cs │ ├── Customer.cs │ ├── CustomerViewModel.cs │ ├── CustomerViewModelList.cs │ ├── Gstc.Collection.Observable.Examples.csproj │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── NotifyPropertyChanged.cs │ ├── ObservableDictionaryControl.xaml │ ├── ObservableDictionaryControl.xaml.cs │ ├── ObservableListAdapterControl.xaml │ ├── ObservableListAdapterControl.xaml.cs │ ├── ObservableListControl.xaml │ ├── ObservableListControl.xaml.cs │ ├── ObservableListSyncControl.xaml │ ├── ObservableListSyncControl.xaml.cs │ ├── ObservableListTest.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── Settings.Designer.cs │ │ └── Settings.settings │ ├── TestModel.cs │ └── TestViewModel.cs ├── Gstc.Collections.Observable.Test │ ├── CollectionTestBase.cs │ ├── Extended │ │ ├── ObservableListAdapterTest.cs │ │ ├── ObservableListKeyedTest.cs │ │ └── ObservableListKeyedTestInterface.cs │ ├── Gstc.Collections.Observable.Test.csproj │ ├── InterfaceTestCases.cs │ ├── NotifyCollectionIndexTest.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Standard │ │ ├── ObservableDictionaryTest.cs │ │ ├── ObservableDictionaryTestInterface.cs │ │ ├── ObservableListTest.cs │ │ ├── ObservableListTestInterface.cs │ │ ├── ObservableSortedListTest.cs │ │ └── ObservableSortedListTestInterface.cs │ ├── app.config │ └── packages.config └── Gstc.Collections.Observable │ ├── Base │ ├── BaseObservableCollection.cs │ ├── BaseObservableDictionary.cs │ ├── BaseObservableDictionaryCollection.cs │ ├── BaseObservableList.cs │ ├── BaseObservableListDictionary.cs │ ├── BaseObservableSortedList.cs │ ├── INotifyListChanged.cs │ ├── NotifyCollection.cs │ ├── NotifyDictionary.cs │ ├── NotifyDictionaryChangedEventArgs.cs │ ├── NotifyDictionaryChangedEventArgs_Generic.cs │ ├── NotifyDictionaryCollection.cs │ └── NotifyProperty.cs │ ├── Extended │ ├── INotifyPropertySyncChanged.cs │ ├── ObservableDictionaryCollection.cs │ ├── ObservableListAdapter.cs │ ├── ObservableListAdapterFunc.cs │ ├── ObservableListKeyed.cs │ ├── ObservableListKeyedFunc.cs │ ├── ObservableListSync.cs │ ├── ObservableListSyncFunc.cs │ └── PropertySyncNotifier.cs │ ├── Gstc.Collections.Observable.csproj │ ├── Gstc.Collections.Observable.nuspec │ ├── IObservableCollection.cs │ ├── IObservableDictionary.cs │ ├── IObservableDictionaryCollection.cs │ ├── IObservableList.cs │ ├── Misc │ ├── CollectionViewAdapter.cs │ ├── EnumeratorAdapter.cs │ ├── KeyedList.cs │ ├── ObservableCollectionSync.cs │ ├── RefDictionary.cs │ └── RefObservableCollection.cs │ ├── ObservableDictionary.cs │ ├── ObservableList.cs │ ├── ObservableSortedList.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── packages.config ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | [Nn]uget.exe 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # DNX 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | 50 | *_i.c 51 | *_p.c 52 | *_i.h 53 | *.ilk 54 | *.meta 55 | *.obj 56 | *.pch 57 | *.pdb 58 | *.pgc 59 | *.pgd 60 | *.rsp 61 | *.sbr 62 | *.tlb 63 | *.tli 64 | *.tlh 65 | *.tmp 66 | *.tmp_proj 67 | *.log 68 | *.vspscc 69 | *.vssscc 70 | .builds 71 | *.pidb 72 | *.svclog 73 | *.scc 74 | 75 | # Chutzpah Test files 76 | _Chutzpah* 77 | 78 | # Visual C++ cache files 79 | ipch/ 80 | *.aps 81 | *.ncb 82 | *.opendb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | *.VC.db 87 | *.VC.VC.opendb 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | *.sap 94 | 95 | # TFS 2012 Local Workspace 96 | $tf/ 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | *.DotSettings.user 105 | 106 | # JustCode is a .NET coding add-in 107 | .JustCode 108 | 109 | # TeamCity is a build add-in 110 | _TeamCity* 111 | 112 | # DotCover is a Code Coverage Tool 113 | *.dotCover 114 | 115 | # NCrunch 116 | _NCrunch_* 117 | .*crunch*.local.xml 118 | nCrunchTemp_* 119 | 120 | # MightyMoose 121 | *.mm.* 122 | AutoTest.Net/ 123 | 124 | # Web workbench (sass) 125 | .sass-cache/ 126 | 127 | # Installshield output folder 128 | [Ee]xpress/ 129 | 130 | # DocProject is a documentation generator add-in 131 | DocProject/buildhelp/ 132 | DocProject/Help/*.HxT 133 | DocProject/Help/*.HxC 134 | DocProject/Help/*.hhc 135 | DocProject/Help/*.hhk 136 | DocProject/Help/*.hhp 137 | DocProject/Help/Html2 138 | DocProject/Help/html 139 | 140 | # Click-Once directory 141 | publish/ 142 | 143 | # Publish Web Output 144 | *.[Pp]ublish.xml 145 | *.azurePubxml 146 | # TODO: Comment the next line if you want to checkin your web deploy settings 147 | # but database connection strings (with potential passwords) will be unencrypted 148 | #*.pubxml 149 | *.publishproj 150 | 151 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 152 | # checkin your Azure Web App publish settings, but sensitive information contained 153 | # in these scripts will be unencrypted 154 | PublishScripts/ 155 | 156 | # NuGet Packages 157 | *.nupkg 158 | # The packages folder can be ignored because of Package Restore 159 | **/packages/* 160 | # except build/, which is used as an MSBuild target. 161 | !**/packages/build/ 162 | # Uncomment if necessary however generally it will be regenerated when needed 163 | #!**/packages/repositories.config 164 | # NuGet v3's project.json files produces more ignoreable files 165 | *.nuget.props 166 | *.nuget.targets 167 | 168 | # Microsoft Azure Build Output 169 | csx/ 170 | *.build.csdef 171 | 172 | # Microsoft Azure Emulator 173 | ecf/ 174 | rcf/ 175 | 176 | # Windows Store app package directories and files 177 | AppPackages/ 178 | BundleArtifacts/ 179 | Package.StoreAssociation.xml 180 | _pkginfo.txt 181 | 182 | # Visual Studio cache files 183 | # files ending in .cache can be ignored 184 | *.[Cc]ache 185 | # but keep track of directories ending in .cache 186 | !*.[Cc]ache/ 187 | 188 | # Others 189 | ClientBin/ 190 | ~$* 191 | *~ 192 | *.dbmdl 193 | *.dbproj.schemaview 194 | *.jfm 195 | *.pfx 196 | *.publishsettings 197 | node_modules/ 198 | orleans.codegen.cs 199 | 200 | # Since there are multiple workflows, uncomment next line to ignore bower_components 201 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 202 | #bower_components/ 203 | 204 | # RIA/Silverlight projects 205 | Generated_Code/ 206 | 207 | # Backup & report files from converting an old project file 208 | # to a newer Visual Studio version. Backup files are not needed, 209 | # because we have git ;-) 210 | _UpgradeReport_Files/ 211 | Backup*/ 212 | UpgradeLog*.XML 213 | UpgradeLog*.htm 214 | 215 | # SQL Server files 216 | *.mdf 217 | *.ldf 218 | 219 | # Business Intelligence projects 220 | *.rdl.data 221 | *.bim.layout 222 | *.bim_*.settings 223 | 224 | # Microsoft Fakes 225 | FakesAssemblies/ 226 | 227 | # GhostDoc plugin setting file 228 | *.GhostDoc.xml 229 | 230 | # Node.js Tools for Visual Studio 231 | .ntvs_analysis.dat 232 | 233 | # Visual Studio 6 build log 234 | *.plg 235 | 236 | # Visual Studio 6 workspace options file 237 | *.opt 238 | 239 | # Visual Studio LightSwitch build output 240 | **/*.HTMLClient/GeneratedArtifacts 241 | **/*.DesktopClient/GeneratedArtifacts 242 | **/*.DesktopClient/ModelManifest.xml 243 | **/*.Server/GeneratedArtifacts 244 | **/*.Server/ModelManifest.xml 245 | _Pvt_Extensions 246 | 247 | # Paket dependency manager 248 | .paket/paket.exe 249 | paket-files/ 250 | 251 | # FAKE - F# Make 252 | .fake/ 253 | 254 | # JetBrains Rider 255 | .idea/ 256 | *.sln.iml 257 | 258 | # CodeRush 259 | .cr/ 260 | 261 | # Python Tools for Visual Studio (PTVS) 262 | __pycache__/ 263 | *.pyc -------------------------------------------------------------------------------- /ExtendedObservableCollection/ExtendedObservableCollection.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2043 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gstc.Collections.Observable", "Gstc.Collections.Observable\Gstc.Collections.Observable.csproj", "{9B12917B-3860-474A-8A9F-30F89C2CEAE8}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gstc.Collections.Observable.Test", "Gstc.Collections.Observable.Test\Gstc.Collections.Observable.Test.csproj", "{E0F76C87-2255-4102-8350-2F50F6288184}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gstc.Collection.Observable.Examples", "Gstc.Collections.Observable.Examples\Gstc.Collection.Observable.Examples.csproj", "{5B2C012D-751C-4B9C-92BD-CB3DBA421845}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {9B12917B-3860-474A-8A9F-30F89C2CEAE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {9B12917B-3860-474A-8A9F-30F89C2CEAE8}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {9B12917B-3860-474A-8A9F-30F89C2CEAE8}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {9B12917B-3860-474A-8A9F-30F89C2CEAE8}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {E0F76C87-2255-4102-8350-2F50F6288184}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {E0F76C87-2255-4102-8350-2F50F6288184}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {E0F76C87-2255-4102-8350-2F50F6288184}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {E0F76C87-2255-4102-8350-2F50F6288184}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {5B2C012D-751C-4B9C-92BD-CB3DBA421845}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {5B2C012D-751C-4B9C-92BD-CB3DBA421845}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {5B2C012D-751C-4B9C-92BD-CB3DBA421845}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {5B2C012D-751C-4B9C-92BD-CB3DBA421845}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {47899EAE-767F-481E-B6C7-EAA57A8ED983} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace Gstc.Collection.Observable.Examples { 4 | /// 5 | /// Interaction logic for App.xaml 6 | /// 7 | public partial class App : Application { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/Customer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Gstc.Collection.Observable.Examples { 5 | public class Customer { 6 | public string FirstName { get; set; } 7 | public string LastName { get; set; } 8 | public DateTime BirthDate { get; set; } 9 | public double PurchaseAmount { get; set; } 10 | public string Id { get; set; } 11 | 12 | private static Random RandomGenerator { get; set; } = new Random(); 13 | 14 | private static int _idCounter = 0; 15 | 16 | private static string GenerateId() => "id_" + _idCounter++; 17 | 18 | public static Customer GenerateCustomer() { 19 | return new Customer() { 20 | FirstName = _firstNameList[RandomGenerator.Next(10)], 21 | LastName = _lastNameList[RandomGenerator.Next(10)], 22 | BirthDate = DateTime.Now.AddDays(-1 * RandomGenerator.Next(30000) - 3000), 23 | PurchaseAmount = 1 + RandomGenerator.Next(10000) * 0.01, 24 | Id = GenerateId() 25 | }; 26 | } 27 | 28 | public static List GenerateCustomerList() { 29 | return new List() { 30 | Customer.GenerateCustomer(), 31 | Customer.GenerateCustomer(), 32 | Customer.GenerateCustomer(), 33 | Customer.GenerateCustomer(), 34 | Customer.GenerateCustomer(), 35 | }; 36 | } 37 | 38 | private static List _firstNameList = new List() { 39 | "Emma", 40 | "Sophia", 41 | "Olivia", 42 | "Isabella", 43 | "Ava", 44 | "Mason", 45 | "Noah", 46 | "Lucas", 47 | "Jacob", 48 | "Jack" 49 | }; 50 | 51 | private static List _lastNameList = new List() { 52 | "Smith", 53 | "Trujillo", 54 | "Jackson", 55 | "Lee", 56 | "Mercado", 57 | "Sonnenberg", 58 | "Valdez", 59 | "Johnson", 60 | "Parker", 61 | "Warren" 62 | }; 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/CustomerViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Gstc.Collection.Observable.Examples { 4 | public class CustomerViewModel { 5 | 6 | public CustomerViewModel(Customer customer) { Customer = customer; } 7 | 8 | public Customer Customer { get; } 9 | 10 | public string Name => Customer.FirstName + " " + Customer.LastName; 11 | public int Age => (int)((DateTime.Now - Customer.BirthDate).TotalDays / 365); 12 | public string Amount => Customer.PurchaseAmount + " Dollars"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/CustomerViewModelList.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable.Extended; 2 | 3 | namespace Gstc.Collection.Observable.Examples { 4 | //Implementation of abstract class Observable list Adapter. 5 | public class CustomerViewModelList : ObservableListAdapter { 6 | public override CustomerViewModel Convert(Customer item) => new CustomerViewModel(item); 7 | public override Customer Convert(CustomerViewModel item) => item.Customer; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/Gstc.Collection.Observable.Examples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5B2C012D-751C-4B9C-92BD-CB3DBA421845} 8 | WinExe 9 | Gstc.Collection.Observable.Examples 10 | Gstc.Collection.Observable.Examples 11 | v4.8 12 | 512 13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 4 15 | true 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 4.0 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | MSBuild:Compile 56 | Designer 57 | 58 | 59 | 60 | 61 | 62 | MSBuild:Compile 63 | Designer 64 | 65 | 66 | App.xaml 67 | Code 68 | 69 | 70 | 71 | 72 | 73 | MainWindow.xaml 74 | Code 75 | 76 | 77 | Designer 78 | MSBuild:Compile 79 | 80 | 81 | Designer 82 | MSBuild:Compile 83 | 84 | 85 | Designer 86 | MSBuild:Compile 87 | 88 | 89 | Designer 90 | MSBuild:Compile 91 | 92 | 93 | 94 | 95 | ObservableDictionaryControl.xaml 96 | 97 | 98 | ObservableListAdapterControl.xaml 99 | 100 | 101 | ObservableListControl.xaml 102 | 103 | 104 | ObservableListSyncControl.xaml 105 | 106 | 107 | 108 | Code 109 | 110 | 111 | True 112 | True 113 | Resources.resx 114 | 115 | 116 | True 117 | Settings.settings 118 | True 119 | 120 | 121 | ResXFileCodeGenerator 122 | Resources.Designer.cs 123 | 124 | 125 | SettingsSingleFileGenerator 126 | Settings.Designer.cs 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | {8e22a176-7ae7-4f41-8fae-edc8a0add7a8} 135 | Gstc.Collections.Observable 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace Gstc.Collection.Observable.Examples { 4 | 5 | public partial class MainWindow : Window { 6 | 7 | public MainWindow() { 8 | InitializeComponent(); 9 | 10 | 11 | } 12 | 13 | 14 | 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/NotifyPropertyChanged.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable.Extended; 2 | using System.ComponentModel; 3 | 4 | namespace Gstc.Collection.Observable.Examples { 5 | public abstract class NotifyPropertySyncChanged : INotifyPropertySyncChanged, INotifyPropertyChanged { 6 | 7 | // Notify Property Changed Fields 8 | public event PropertyChangedEventHandler PropertyChanged; 9 | 10 | //Sync Fields 11 | public void OnPropertyChanged(object sender, PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args); 12 | 13 | //Convience Fields 14 | public void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/ObservableDictionaryControl.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/ObservableDictionaryControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace Gstc.Collection.Observable.Examples { 4 | /// 5 | /// Interaction logic for ObservableDictionaryControl.xaml 6 | /// 7 | public partial class ObservableDictionaryControl : UserControl { 8 | public ObservableDictionaryControl() { 9 | InitializeComponent(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/ObservableListAdapterControl.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/ObservableListAdapterControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable; 2 | using System.Windows.Controls; 3 | 4 | namespace Gstc.Collection.Observable.Examples { 5 | /// 6 | /// Interaction logic for ObservableListAdapterControl.xaml 7 | /// 8 | public partial class ObservableListAdapterControl : UserControl { 9 | 10 | //List of Customer models 11 | public ObservableList CustomerList { 12 | get => (ObservableList)CustomerViewModelList.SourceCollection; 13 | set => CustomerViewModelList.SourceCollection = value; 14 | } 15 | 16 | //List of Customer View Models: Method one - using implementation of abstract class. 17 | public CustomerViewModelList CustomerViewModelList { get; set; } = new CustomerViewModelList(); 18 | 19 | //List of Customer View Models: Method two - using anonymous functions to map Model to ViewModel. 20 | //public ObservableListAdapter CustomerViewModelList { get; set;} 21 | // = new ObservableListAdapterFunc( (customer) => new CustomerViewModel(customer), (customerVm) => customerVm.Customer ); 22 | 23 | public ObservableListAdapterControl() { 24 | InitializeComponent(); 25 | DataContext = this; 26 | CustomerList = CreateList(); 27 | } 28 | 29 | private void Button_Click_Add(object sender, System.Windows.RoutedEventArgs e) { 30 | CustomerList.Add(Customer.GenerateCustomer()); 31 | } 32 | 33 | private void Button_Click_Remove(object sender, System.Windows.RoutedEventArgs e) { 34 | if (CustomerList.Count > 0) CustomerList.RemoveAt(0); 35 | } 36 | 37 | private void Button_Click_New(object sender, System.Windows.RoutedEventArgs e) { 38 | CustomerList = CreateList(); 39 | } 40 | 41 | private static ObservableList CreateList() => new ObservableList() { 42 | Customer.GenerateCustomer(), 43 | Customer.GenerateCustomer(), 44 | Customer.GenerateCustomer(), 45 | Customer.GenerateCustomer() 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/ObservableListControl.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/ObservableListControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable; 2 | using System.Windows.Controls; 3 | 4 | namespace Gstc.Collection.Observable.Examples { 5 | /// 6 | /// Interaction logic for ObservableListControl.xaml 7 | /// 8 | public partial class ObservableListControl : UserControl { 9 | 10 | public ObservableList CustomerObservableList { get; set; } = new ObservableList(); 11 | 12 | public ObservableListControl() { 13 | InitializeComponent(); 14 | DataContext = this; 15 | 16 | CustomerObservableList.List = Customer.GenerateCustomerList(); 17 | CustomerObservableList.CollectionChanged += (sender, args) => { 18 | string message = "\nCollection Changed:"; 19 | 20 | if (args.NewItems != null) 21 | foreach (Customer customer in args.NewItems) 22 | message += ("\nCustomer Added: " + customer.FirstName + " " + customer.LastName); 23 | 24 | if (args.OldItems != null) 25 | foreach (Customer customer in args.OldItems) 26 | message += ("\nCustomer Removed: " + customer.FirstName + " " + customer.LastName); 27 | 28 | EventTextBox.Text = message + "\n\n"; 29 | }; 30 | } 31 | 32 | private void Button_Click_AddRange(object sender, System.Windows.RoutedEventArgs e) { 33 | CustomerObservableList.AddRange(Customer.GenerateCustomerList()); 34 | } 35 | 36 | private void Button_Click_Add(object sender, System.Windows.RoutedEventArgs e) { 37 | CustomerObservableList.Add(Customer.GenerateCustomer()); 38 | } 39 | 40 | private void Button_Click_Remove(object sender, System.Windows.RoutedEventArgs e) { 41 | if (CustomerObservableList.Count > 0) CustomerObservableList.RemoveAt(0); 42 | } 43 | 44 | private void Button_Click_New(object sender, System.Windows.RoutedEventArgs e) { 45 | CustomerObservableList.List = Customer.GenerateCustomerList(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/ObservableListSyncControl.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/ObservableListSyncControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable; 2 | using Gstc.Collections.Observable.Extended; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | 6 | namespace Gstc.Collection.Observable.Examples { 7 | /// 8 | /// Interaction logic for ObservableListSyncControl.xaml 9 | /// 10 | public partial class ObservableListSyncControl : UserControl { 11 | 12 | /// 13 | /// The syncronized observable list for viewmodel. 14 | /// 15 | private ObservableListSync ObvListSync = 16 | new ObservableListSyncFunc( 17 | (sourceItem) => new TestViewModel(sourceItem), 18 | (destItem) => destItem.TestModel 19 | ); 20 | 21 | /// 22 | /// The source list to insert into the syrnchonized observable list. 23 | /// 24 | public ObservableList SourceObvList { 25 | get => ObvListSync.SourceObservableList; 26 | set { 27 | ObvListSync.SourceObservableList = value; 28 | SourceGrid.ItemsSource = value; 29 | DestGrid.ItemsSource = ObvListSync; 30 | } 31 | } 32 | 33 | public ObservableListSyncControl() { 34 | InitializeComponent(); 35 | //Initializes example data 36 | ObservableList obvList = new ObservableList() { 37 | new TestModel {Num1=1, Num2 = 2}, 38 | new TestModel {Num1=10, Num2 = 20}, 39 | new TestModel {Num1=100, Num2 = 200}, 40 | }; 41 | 42 | SourceObvList = obvList; 43 | } 44 | 45 | private void Button_Click(object sender, RoutedEventArgs e) { 46 | SourceObvList[0].Num1 += 100; 47 | } 48 | 49 | private void Button_Click_1(object sender, RoutedEventArgs e) { 50 | ObvListSync[0].Num2 += 40; 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/ObservableListTest.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | 6 | namespace Gstc.Collection.Observable.Examples { 7 | public class ObservableListDemo { 8 | public List CustomerList { get; set; } 9 | public ObservableList CustomerObservableListWrapper { get; set; } 10 | public ObservableList CustomerObservableList { get; set; } 11 | 12 | public ObservableListDemo() { 13 | CustomerList = new List(); 14 | CustomerObservableList = new ObservableList(); 15 | CustomerObservableListWrapper = new ObservableList(); 16 | 17 | CustomerObservableListWrapper.CollectionChanged += (sender, args) => { 18 | Console.WriteLine("Collection Changed"); 19 | foreach (Customer customer in args.NewItems) 20 | Console.WriteLine("Customer Added: " + customer.FirstName + " " + customer.LastName); 21 | }; 22 | 23 | CustomerObservableList.CollectionChanged += (sender, args) => { 24 | Console.WriteLine("Collection Changed"); 25 | foreach (Customer customer in args.NewItems) 26 | Console.WriteLine("Customer Added: " + customer.FirstName + " " + customer.LastName); 27 | }; 28 | 29 | //Populates initial standard list. 30 | CustomerList.Add(Customer.GenerateCustomer()); 31 | CustomerList.Add(Customer.GenerateCustomer()); 32 | CustomerList.Add(Customer.GenerateCustomer()); 33 | CustomerList.Add(Customer.GenerateCustomer()); 34 | 35 | //Inserts standard list into the wrapper as its backing list. 36 | CustomerObservableListWrapper.List = CustomerList; 37 | 38 | //Adds customers directly to second observable list. 39 | CustomerObservableList.Add(Customer.GenerateCustomer()); 40 | CustomerObservableList.Add(Customer.GenerateCustomer()); 41 | CustomerObservableList.Add(Customer.GenerateCustomer()); 42 | 43 | //Adds list of customers from initial list into second observable list. 44 | CustomerObservableList.AddRange(CustomerList); 45 | 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using System.Windows; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Gstc.Collection.Observable.Examples")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Gstc.Collection.Observable.Examples")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | //In order to begin building localizable applications, set 23 | //CultureYouAreCodingWith in your .csproj file 24 | //inside a . For example, if you are using US english 25 | //in your source files, set the to en-US. Then uncomment 26 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 27 | //the line below to match the UICulture setting in the project file. 28 | 29 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 30 | 31 | 32 | [assembly: ThemeInfo( 33 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 34 | //(used if a resource is not found in the page, 35 | // or application resource dictionaries) 36 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 37 | //(used if a resource is not found in the page, 38 | // app, or any theme specific resource dictionaries) 39 | )] 40 | 41 | 42 | // Version information for an assembly consists of the following four values: 43 | // 44 | // Major Version 45 | // Minor Version 46 | // Build Number 47 | // Revision 48 | // 49 | // You can specify all the values or you can default the Build and Revision Numbers 50 | // by using the '*' as shown below: 51 | // [assembly: AssemblyVersion("1.0.*")] 52 | [assembly: AssemblyVersion("1.0.0.0")] 53 | [assembly: AssemblyFileVersion("1.0.0.0")] 54 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Gstc.Collection.Observable.Examples.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Gstc.Collection.Observable.Examples.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Gstc.Collection.Observable.Examples.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.0.3.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/TestModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Gstc.Collection.Observable.Examples { 4 | public class TestModel : INotifyPropertyChanged { 5 | 6 | private int num1 = 5; 7 | private int num2 = 10; 8 | 9 | public event PropertyChangedEventHandler PropertyChanged; 10 | public void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); 11 | 12 | public int Num1 { get => num1; set { num1 = value; OnPropertyChanged(null); } } 13 | public int Num2 { get => num2; set { num2 = value; OnPropertyChanged(null); } } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Examples/TestViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Gstc.Collection.Observable.Examples { 2 | public class TestViewModel : NotifyPropertySyncChanged { 3 | public TestModel TestModel; 4 | 5 | public TestViewModel() => TestModel = new TestModel(); 6 | 7 | public TestViewModel(TestModel testModel) => TestModel = testModel; 8 | 9 | public string Num1 { 10 | get => TestModel.Num1.ToString(); 11 | set { 12 | int num = 0; 13 | var isParsed = int.TryParse(value, out num); 14 | if (!isParsed) return; 15 | TestModel.Num1 = num; 16 | OnPropertyChanged(null); 17 | } 18 | } 19 | 20 | public int Num2 { 21 | get => TestModel.Num2 + 5; 22 | set { 23 | TestModel.Num2 = value - 5; 24 | OnPropertyChanged(null); 25 | } 26 | } 27 | } 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/CollectionTestBase.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Gstc.Collections.Observable.Base; 3 | using Moq; 4 | using NUnit.Framework; 5 | using System; 6 | using System.Collections.Specialized; 7 | using System.ComponentModel; 8 | 9 | namespace Gstc.Collections.Observable.Test { 10 | public class CollectionTestBase { 11 | 12 | #region Default Test Tools 13 | protected static Fixture Fixture { get; } = new Fixture(); 14 | protected MockEventClass MockEvent { get; set; } 15 | protected AssertEventClass AssertEvent { get; set; } 16 | #endregion 17 | 18 | #region Default test items 19 | 20 | protected TItem DefaultTestItem { get; set; } 21 | protected TItem UpdateTestItem { get; set; } 22 | 23 | protected TItem Item1 { get; set; } 24 | protected TItem Item2 { get; set; } 25 | protected TItem Item3 { get; set; } 26 | 27 | 28 | protected TKey Key1 { get; set; } 29 | protected TKey Key2 { get; set; } 30 | protected TKey Key3 { get; set; } 31 | 32 | protected TKey DefaultKey { get; set; } 33 | protected TKey UpdateKey { get; set; } 34 | protected TItem DefaultValue { get; set; } 35 | protected TItem UpdateValue { get; set; } 36 | 37 | 38 | 39 | #endregion 40 | 41 | public void TestInit() { 42 | MockEvent = new MockEventClass(); 43 | AssertEvent = MockEvent.Object; 44 | 45 | //Generalize mock data 46 | DefaultTestItem = Fixture.Create(); 47 | UpdateTestItem = Fixture.Create(); 48 | 49 | Item1 = Fixture.Create(); 50 | Item2 = Fixture.Create(); 51 | Item3 = Fixture.Create(); 52 | 53 | Key1 = Fixture.Create(); 54 | Key2 = Fixture.Create(); 55 | Key3 = Fixture.Create(); 56 | 57 | DefaultKey = Fixture.Create(); 58 | UpdateKey = Fixture.Create(); 59 | DefaultValue = Fixture.Create(); 60 | UpdateValue = Fixture.Create(); 61 | } 62 | 63 | #region Collection Event Args Tests 64 | protected void AssertCollectionEventReset(object sender, NotifyCollectionChangedEventArgs args) { 65 | Assert.That(args.Action, Is.EqualTo(NotifyCollectionChangedAction.Reset)); 66 | Assert.That(args.OldItems, Is.Null); 67 | Assert.That(args.NewItems, Is.Null); 68 | Assert.That(args.OldStartingIndex == -1); 69 | Assert.That(args.NewStartingIndex == -1); 70 | } 71 | 72 | protected NotifyCollectionChangedEventHandler GenerateAssertCollectionEventAddOne(int index, TItem item) { 73 | return (sender, args) => { 74 | Assert.That(args.Action, Is.EqualTo(NotifyCollectionChangedAction.Add)); 75 | Assert.That(args.OldStartingIndex == -1); 76 | Assert.That(args.NewStartingIndex == index); 77 | Assert.That(args.OldItems, Is.Null); 78 | Assert.That(args.NewItems[0], Is.EqualTo(item)); 79 | }; 80 | } 81 | 82 | protected NotifyCollectionChangedEventHandler GenerateAssertCollectionEventAddThree(int index, TItem item1, TItem item2, TItem item3) { 83 | return (sender, args) => { 84 | Assert.That(args.Action, Is.EqualTo(NotifyCollectionChangedAction.Add)); 85 | Assert.That(args.OldStartingIndex, Is.EqualTo(-1)); 86 | Assert.That(args.NewStartingIndex, Is.EqualTo(index)); 87 | Assert.That(args.OldItems, Is.Null); 88 | Assert.That(args.NewItems[0], Is.EqualTo(item1)); 89 | Assert.That(args.NewItems[1], Is.EqualTo(item2)); 90 | Assert.That(args.NewItems[2], Is.EqualTo(item3)); 91 | }; 92 | } 93 | 94 | protected NotifyCollectionChangedEventHandler GenerateAssertCollectionEventRemoveOne(int index, TItem item) { 95 | return (sender, args) => { 96 | Assert.That(args.Action, Is.EqualTo(NotifyCollectionChangedAction.Remove)); 97 | Assert.That(args.OldStartingIndex == index); 98 | Assert.That(args.NewStartingIndex == -1); 99 | Assert.That(args.OldItems[0], Is.EqualTo(item)); 100 | Assert.That(args.NewItems, Is.Null); 101 | }; 102 | } 103 | #endregion 104 | 105 | #region Dictionary Event Arg Tests 106 | protected void AssertDictionaryEventReset(object sender, NotifyDictionaryChangedEventArgs args) { 107 | Assert.That(args.Action, Is.EqualTo(NotifyDictionaryChangedAction.Reset)); 108 | Assert.That(args.OldItems, Is.Null); 109 | Assert.That(args.NewItems, Is.Null); 110 | Assert.That(args.OldKeys, Is.Null); 111 | Assert.That(args.NewKeys, Is.Null); 112 | } 113 | 114 | protected NotifyDictionaryChangedEventHandler GenerateAssertDictionaryEventAddOne(TKey key, TItem item) { 115 | return (sender, args) => { 116 | Assert.That(args.Action, Is.EqualTo(NotifyDictionaryChangedAction.Add)); 117 | Assert.That(args.OldItems, Is.Null); 118 | Assert.That(args.OldKeys, Is.Null); 119 | Assert.That(args.NewKeys[0], Is.EqualTo(key)); 120 | Assert.That(args.NewItems[0], Is.EqualTo(item)); 121 | }; 122 | } 123 | 124 | protected NotifyDictionaryChangedEventHandler GenerateAssertDictionaryEventRemoveOne(TKey key, TItem item) { 125 | return (sender, args) => { 126 | Assert.That(args.Action, Is.EqualTo(NotifyDictionaryChangedAction.Remove)); 127 | Assert.That(args.NewKeys, Is.Null); 128 | Assert.That(args.NewItems, Is.Null); 129 | Assert.That(args.OldKeys[0], Is.EqualTo(key)); 130 | Assert.That(args.OldItems[0], Is.EqualTo(item)); 131 | }; 132 | } 133 | #endregion 134 | 135 | protected void Log(object message) => Console.WriteLine(message); 136 | 137 | #region Event Call Test 138 | 139 | 140 | /// 141 | /// Class for testing mock events for observable collections. Uses Moq framework to verify event are called. 142 | /// 143 | public class MockEventClass : Mock { 144 | 145 | private int _timesPropertyCalled = 0; 146 | private int _timesCollectionCalled = 0; 147 | private int _timesDictionaryCalled = 0; 148 | 149 | public void AddNotifiersCollectionAndProperty(IObservableCollection obvList) { 150 | //Sets up event testers 151 | obvList.PropertyChanged += OnPropertyChanged; 152 | obvList.CollectionChanged += OnCollectionChanged; 153 | } 154 | 155 | public void AddNotifiersDictionary(INotifyDictionaryChanged obvDict) { 156 | //Sets up event testers 157 | obvDict.DictionaryChanged += OnDictionaryChanged; 158 | 159 | } 160 | 161 | public void AssertMockNotifiersCollection(int timesPropertyCalled, int timesCollectionCalled) { 162 | _timesPropertyCalled += timesPropertyCalled; 163 | _timesCollectionCalled += timesCollectionCalled; 164 | Verify(m => m.Call("PropertyChanged"), Times.Exactly(_timesPropertyCalled)); 165 | Verify(m => m.Call("CollectionChanged"), Times.Exactly(_timesCollectionCalled)); 166 | } 167 | 168 | public void AssertMockNotifiersDictionary(int timesDictionaryCalled) { 169 | _timesDictionaryCalled += timesDictionaryCalled; 170 | Verify(m => m.Call("DictionaryChanged"), Times.Exactly(_timesDictionaryCalled)); 171 | } 172 | 173 | public void RemoveCollectionAndPropertyNotifiers(IObservableCollection obvList) { 174 | obvList.PropertyChanged -= OnPropertyChanged; 175 | obvList.CollectionChanged -= OnCollectionChanged; 176 | } 177 | 178 | public void RemoveDictionaryNotifiers(IObservableDictionary obvDict) { 179 | obvDict.DictionaryChanged -= OnDictionaryChanged; 180 | } 181 | 182 | private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 183 | => Object.Call("CollectionChanged"); 184 | 185 | private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) 186 | => Object.Call("PropertyChanged"); 187 | 188 | private void OnDictionaryChanged(object sender, NotifyDictionaryChangedEventArgs e) 189 | => Object.Call("DictionaryChanged"); 190 | } 191 | 192 | /// 193 | /// Simple class for testing events using Moq. Moq proxies the Call method and counts number of times called. 194 | /// 195 | public class AssertEventClass { 196 | public virtual void Call(string obj) { } 197 | } 198 | 199 | #endregion 200 | 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/Extended/ObservableListAdapterTest.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Gstc.Collections.Observable.Base; 3 | using Gstc.Collections.Observable.Extended; 4 | using Moq; 5 | using NUnit.Framework; 6 | using System.Linq; 7 | 8 | namespace Gstc.Collections.Observable.Test.Extended { 9 | 10 | [TestFixture] 11 | public class ObservableListAdapterTest : CollectionTestBase { 12 | private BaseObservableList TestBaseList { get; set; } 13 | private ObservableListAdapterConcrete ListAdapter { get; set; } 14 | 15 | [SetUp] 16 | public new void TestInit() { 17 | base.TestInit(); 18 | TestBaseList = new ObservableList(); 19 | } 20 | 21 | [Test, Description("")] 22 | public void TestMethod_Set2() { 23 | 24 | TestBaseList.Add(Item1); 25 | TestBaseList.Add(Item2); 26 | TestBaseList.Add(Item3); 27 | 28 | ListAdapter.SourceCollection = TestBaseList; 29 | 30 | Assert.That(ListAdapter.Count, Is.EqualTo(3)); 31 | Assert.That(Item1, Is.EqualTo(ListAdapter.First().BaseString)); 32 | Assert.That(Item2, Is.EqualTo(ListAdapter[1].BaseString)); 33 | Assert.That(Item3, Is.EqualTo(ListAdapter[2].BaseString)); 34 | Assert.That(Item1 + ListAdapter.First().AddedString, Is.EqualTo(ListAdapter.First().StringView)); 35 | } 36 | 37 | [Test, Description("")] 38 | public void TestMethod_SetConstructorAndAdd() { 39 | ListAdapter = new ObservableListAdapterConcrete(TestBaseList); 40 | 41 | TestBaseList.PropertyChanged += (sender, args) => AssertEvent.Call("PropertyChanged"); 42 | TestBaseList.CollectionChanged += (sender, args) => AssertEvent.Call("CollectionChanged"); 43 | 44 | TestBaseList.Add(Item1); 45 | TestBaseList.Add(Item2); 46 | TestBaseList.Add(Item3); 47 | 48 | Assert.That(ListAdapter.Count, Is.EqualTo(3)); 49 | Assert.That(Item1, Is.EqualTo(ListAdapter.First().BaseString)); 50 | Assert.That(Item2, Is.EqualTo(ListAdapter[1].BaseString)); 51 | Assert.That(Item3, Is.EqualTo(ListAdapter[2].BaseString)); 52 | Assert.That(Item1 + ListAdapter.First().AddedString, Is.EqualTo(ListAdapter.First().StringView)); 53 | 54 | MockEvent.Verify(m => m.Call("PropertyChanged"), Times.Exactly(6)); 55 | MockEvent.Verify(m => m.Call("CollectionChanged"), Times.Exactly(3)); 56 | } 57 | 58 | [Test, Description("")] 59 | public void TestMethod_Set4() { 60 | 61 | TestBaseList.Add(Item1); 62 | TestBaseList.Add(Item2); 63 | TestBaseList.Add(Item3); 64 | 65 | ListAdapter = new ObservableListAdapterConcrete(TestBaseList); 66 | 67 | Assert.That(ListAdapter.Count, Is.EqualTo(3)); 68 | Assert.That(Item1, Is.EqualTo(ListAdapter.First().BaseString)); 69 | Assert.That(Item2, Is.EqualTo(ListAdapter[1].BaseString)); 70 | Assert.That(Item3, Is.EqualTo(ListAdapter[2].BaseString)); 71 | Assert.That(Item1 + ListAdapter.First().AddedString, Is.EqualTo(ListAdapter.First().StringView)); 72 | } 73 | 74 | [Test, Description("")] 75 | public void TestMethod_SetAndAdd() { 76 | ListAdapter.SourceCollection = TestBaseList; 77 | 78 | TestBaseList.Add(Item1); 79 | TestBaseList.Add(Item2); 80 | TestBaseList.Add(Item3); 81 | 82 | Assert.That(ListAdapter.Count, Is.EqualTo(3)); 83 | Assert.That(Item1, Is.EqualTo(ListAdapter.First().BaseString)); 84 | Assert.That(Item2, Is.EqualTo(ListAdapter[1].BaseString)); 85 | Assert.That(Item3, Is.EqualTo(ListAdapter[2].BaseString)); 86 | Assert.That(Item1 + ListAdapter.First().AddedString, Is.EqualTo(ListAdapter.First().StringView)); 87 | } 88 | 89 | [Test, Description("")] 90 | public void TestMethod_ReplaceList() { 91 | var testListA = new ObservableList(); 92 | 93 | testListA.Add(Item1); 94 | testListA.Add(Item2); 95 | testListA.Add(Item3); 96 | 97 | var testListB = new ObservableList(); 98 | var item1B = Fixture.Create(); 99 | var item2B = Fixture.Create(); 100 | testListB.Add(item1B); 101 | testListB.Add(item2B); 102 | 103 | ListAdapter.SourceCollection = testListA; 104 | 105 | Assert.That(ListAdapter.Count, Is.EqualTo(3)); 106 | Assert.That(Item1 == ListAdapter[0].BaseString); 107 | Assert.That(Item2 == ListAdapter[1].BaseString); 108 | Assert.That(Item3 == ListAdapter[2].BaseString); 109 | 110 | 111 | ListAdapter.SourceCollection = testListB; 112 | 113 | Assert.That(item1B == ListAdapter[0].BaseString); 114 | Assert.That(item2B == ListAdapter[1].BaseString); 115 | 116 | Assert.That(ListAdapter.Count, Is.EqualTo(2)); 117 | } 118 | 119 | 120 | [Test, Description("")] 121 | public void TestMethod_Remove() { 122 | 123 | TestBaseList.Add(Item1); 124 | TestBaseList.Add(Item2); 125 | TestBaseList.Add(Item3); 126 | 127 | ListAdapter = new ObservableListAdapterConcrete(TestBaseList); 128 | 129 | Assert.That(ListAdapter.Count == 3); 130 | 131 | TestBaseList.RemoveAt(1); 132 | 133 | Assert.That(ListAdapter.Count == 2); 134 | Assert.That(ListAdapter[0].BaseString == Item1); 135 | Assert.That(ListAdapter[1].BaseString == Item3); 136 | } 137 | 138 | [Test, Description("")] 139 | public void TestMethod_Move() { 140 | 141 | TestBaseList.Add(Item1); 142 | TestBaseList.Add(Item2); 143 | TestBaseList.Add(Item3); 144 | 145 | ListAdapter = new ObservableListAdapterConcrete(TestBaseList); 146 | 147 | Assert.That(ListAdapter.Count == 3); 148 | 149 | TestBaseList.Move(1, 0); 150 | 151 | Assert.That(ListAdapter.Count == 3); 152 | Assert.That(ListAdapter[0].BaseString == Item2); 153 | Assert.That(ListAdapter[1].BaseString == Item1); 154 | Assert.That(ListAdapter[2].BaseString == Item3); 155 | } 156 | 157 | 158 | [Test, Description("")] 159 | public void TestMethod_Replace() { 160 | 161 | TestBaseList.Add(Item1); 162 | TestBaseList.Add(Item2); 163 | TestBaseList.Add(Item3); 164 | 165 | ListAdapter = new ObservableListAdapterConcrete(TestBaseList); 166 | 167 | Assert.That(ListAdapter.Count == 3); 168 | 169 | var item4 = Fixture.Create(); 170 | TestBaseList[1] = item4; 171 | 172 | Assert.That(ListAdapter.Count == 3); 173 | Assert.That(ListAdapter[0].BaseString == Item1); 174 | Assert.That(ListAdapter[1].BaseString == item4); 175 | Assert.That(ListAdapter[2].BaseString == Item3); 176 | } 177 | 178 | [Test, Description("")] 179 | public void TestMethod_Clear() { 180 | 181 | TestBaseList.Add(Item1); 182 | TestBaseList.Add(Item2); 183 | TestBaseList.Add(Item3); 184 | 185 | ListAdapter = new ObservableListAdapterConcrete(TestBaseList); 186 | 187 | Assert.That(ListAdapter.Count == 3); 188 | 189 | TestBaseList.Clear(); 190 | 191 | Assert.That(ListAdapter.Count == 0); 192 | } 193 | 194 | 195 | #region Test Class Definitions 196 | /// 197 | /// Items used in test. 198 | /// 199 | public class TestItemClass { 200 | 201 | //public static string AddedString = ":ViewModel"; 202 | public static Fixture Fixture = new Fixture(); 203 | 204 | public TestItemClass(string myString) { 205 | BaseString = myString; 206 | AddedString = Fixture.Create(); 207 | StringView = myString + AddedString; 208 | } 209 | public string StringView { get; set; } 210 | public string BaseString { get; set; } 211 | public string AddedString { get; set; } 212 | 213 | } 214 | 215 | public class ObservableListAdapterConcrete : ObservableListAdapter { 216 | 217 | public ObservableListAdapterConcrete() : base() { } 218 | 219 | public ObservableListAdapterConcrete(IObservableCollection sourceCollection) : base(sourceCollection) { } 220 | 221 | public override string Convert(TestItemClass itemClass) => itemClass.StringView; 222 | 223 | public override TestItemClass Convert(string item) => new TestItemClass(item); 224 | } 225 | #endregion 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/Extended/ObservableListKeyedTestInterface.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable.Extended; 2 | using NUnit.Framework; 3 | 4 | namespace Gstc.Collections.Observable.Test.Extended { 5 | [TestFixture] 6 | public class ObservableListKeyedTestInterface { 7 | 8 | public ObservableListKeyed Observable; 9 | 10 | private readonly InterfaceTestCases _testCases = new InterfaceTestCases(); 11 | 12 | [SetUp] 13 | public void TestInit() { 14 | _testCases.TestInit(); 15 | Observable = new ObservableListKeyedFunc(item => item.Id); 16 | } 17 | 18 | [Test] 19 | public void Collection() { 20 | //Test needs 3 test items. 21 | Observable.Add(new TestItem()); 22 | Observable.Add(new TestItem()); 23 | Observable.Add(new TestItem()); 24 | _testCases.CollectionTest(Observable); 25 | } 26 | 27 | [Test] 28 | public void CollectionGeneric() => _testCases.CollectionGenericTest(Observable); 29 | 30 | [Test] 31 | public void CollectionKvp() => _testCases.CollectionKeyValuePairTest(Observable); 32 | 33 | [Test] 34 | public void Dictionary() => _testCases.DictionaryTest(Observable); 35 | 36 | [Test] 37 | public void DictionaryGeneric() => _testCases.DictionaryGenericTest(Observable); 38 | 39 | [Test] 40 | public void ListInterface() => _testCases.ListGenericTest(Observable); 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/NotifyCollectionIndexTest.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Moq; 3 | using NUnit.Framework; 4 | 5 | namespace Gstc.Collections.Observable.Test { 6 | [TestFixture] 7 | public class NotifyCollectionIndexTest : CollectionTestBase { 8 | 9 | private ObservableSortedList ObvDictionary { get; set; } 10 | 11 | [SetUp] 12 | public new void TestInit() { 13 | base.TestInit(); 14 | ObvDictionary = new ObservableSortedList(); 15 | } 16 | 17 | [Test, Description("Remove Key/item using key, returns correct index.")] 18 | public void TestMethod_RemoveWithIndex() { 19 | 20 | var key1 = "a"; //0 21 | var key2 = "c"; //2 22 | var key3 = "b"; //1 23 | 24 | var value1 = Fixture.Create(); 25 | var value2 = Fixture.Create(); 26 | var value3 = Fixture.Create(); 27 | 28 | ObvDictionary.Add(key1, value1); 29 | ObvDictionary.Add(key2, value2); 30 | ObvDictionary.Add(key3, value3); 31 | 32 | Log(ObvDictionary.SortedList.IndexOfKey(key1)); 33 | Log(ObvDictionary.SortedList.IndexOfKey(key2)); 34 | Log(ObvDictionary.SortedList.IndexOfKey(key3)); 35 | 36 | 37 | ObvDictionary.CollectionChanged += (sender, args) => { 38 | Assert.That(args.OldItems[0], Is.EqualTo(value2)); 39 | Assert.That(args.OldStartingIndex, Is.EqualTo(2)); 40 | Assert.That(args.NewItems, Is.Null); 41 | Assert.That(args.NewStartingIndex, Is.EqualTo(-1)); 42 | AssertEvent.Call("collection"); 43 | }; 44 | 45 | ObvDictionary.Remove(key2); 46 | 47 | MockEvent.Verify(m => m.Call("collection"), Times.Exactly(1)); 48 | 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("Gstc.Utility.Collections.Test")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Gstc.Utility.Collections.Test")] 12 | [assembly: AssemblyCopyright("Copyright © 2018")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("e0f76c87-2255-4102-8350-2f50f6288184")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/Standard/ObservableDictionaryTest.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Gstc.Collections.Observable.Base; 3 | using Moq; 4 | using NUnit.Framework; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace Gstc.Collections.Observable.Test.Standard { 9 | [TestFixture] 10 | public class ObservableDictionaryTest : CollectionTestBase { 11 | 12 | private static object[] DefaultStaticKey { get; } = { 13 | Fixture.Create(), 14 | Fixture.Create() 15 | }; 16 | 17 | private static object[] DefaultStaticValue { get; } = { 18 | null, 19 | Fixture.Create(), 20 | Fixture.Create(), 21 | Fixture.Create(), 22 | Fixture.Create() 23 | }; 24 | 25 | private ObservableDictionary ObvDictionary { get; set; } 26 | 27 | [SetUp] 28 | public new void TestInit() { 29 | base.TestInit(); 30 | ObvDictionary = new ObservableDictionary(); 31 | } 32 | 33 | [Test, TestCaseSource(nameof(DefaultStaticValue)), Description("Add key/item using Add")] 34 | public void TestMethod_Add(object item) { 35 | //object item = StaticValueRef[index]; 36 | ObvDictionary.DictionaryChanged += GenerateAssertDictionaryEventAddOne(DefaultKey, item); 37 | AddMockEventNotifiers(); 38 | ObvDictionary.Add(DefaultKey, item); 39 | AssertMockEventNotifiers(2, 1); 40 | } 41 | 42 | [Test, TestCaseSource(nameof(DefaultStaticValue)), Description("Replace value using index")] 43 | public void TestMethod_AddIndexer(object item) { 44 | 45 | ObvDictionary.DictionaryChanged += GenerateAssertDictionaryEventAddOne(DefaultKey, item); 46 | AddMockEventNotifiers(); 47 | 48 | ObvDictionary[DefaultKey] = item; 49 | 50 | AssertMockEventNotifiers(2, 1); 51 | } 52 | 53 | [Test, Description("")] 54 | public void TestMethod_Clear() { 55 | 56 | ObvDictionary.Add(DefaultKey, DefaultValue); 57 | 58 | ObvDictionary.DictionaryChanged += AssertDictionaryEventReset; 59 | AddMockEventNotifiers(); 60 | 61 | ObvDictionary.Clear(); 62 | 63 | AssertMockEventNotifiers(2, 1); 64 | Assert.That(ObvDictionary.Count, Is.EqualTo(0)); 65 | } 66 | 67 | 68 | [Test, Description("")] 69 | public void TestMethod_ReplaceDictionary() { 70 | 71 | ObvDictionary.Add(DefaultKey, DefaultValue); 72 | 73 | ObvDictionary.DictionaryChanged += AssertDictionaryEventReset; 74 | AddMockEventNotifiers(); 75 | 76 | ObvDictionary.Dictionary = new Dictionary(); 77 | 78 | AssertMockEventNotifiers(2, 1); 79 | Assert.That(ObvDictionary.Count, Is.EqualTo(0)); 80 | } 81 | 82 | [Test, Description("Remove Key/item using key")] 83 | public void TestMethod_Remove() { 84 | 85 | ObvDictionary.Add(DefaultKey, DefaultValue); 86 | 87 | ObvDictionary.DictionaryChanged += GenerateAssertDictionaryEventRemoveOne(DefaultKey, DefaultValue); 88 | AddMockEventNotifiers(); 89 | 90 | ObvDictionary.Remove(DefaultKey); 91 | 92 | AssertMockEventNotifiers(2, 1); 93 | } 94 | 95 | [Test, Description("Replace value using index")] 96 | public void TestMethod_ReplaceValue() { 97 | 98 | ObvDictionary.Add(DefaultKey, DefaultValue); 99 | 100 | ObvDictionary.DictionaryChanged += (sender, args) => { 101 | Assert.That(args.Action == NotifyDictionaryChangedAction.Replace); 102 | Assert.That(args.OldKeys[0], Is.EqualTo(DefaultKey)); 103 | Assert.That(args.OldItems[0], Is.EqualTo(DefaultValue)); 104 | Assert.That(args.NewKeys[0], Is.EqualTo(DefaultKey)); 105 | Assert.That(args.NewItems[0], Is.EqualTo(UpdateValue)); 106 | AssertEvent.Call("DictionaryChanged"); 107 | }; 108 | 109 | ObvDictionary[DefaultKey] = UpdateValue; 110 | 111 | MockEvent.Verify(m => m.Call("DictionaryChanged"), Times.Exactly(1)); 112 | } 113 | 114 | [Test, Description("")] 115 | public void TestMethod_NullKey() { 116 | 117 | //Fails if null keys trigger an event. 118 | ObvDictionary.DictionaryChanged += (sender, args) => Assert.Fail(); 119 | Assert.Throws(() => ObvDictionary.Add(null, DefaultValue)); 120 | Assert.Throws(() => ObvDictionary[null] = DefaultValue); 121 | } 122 | 123 | [Test, Description("")] 124 | public void TestMethod_DuplicateKey() { 125 | 126 | ObvDictionary.Add(DefaultKey, DefaultValue); 127 | 128 | //Fails if adding a duplicate triggers triggers an event. 129 | ObvDictionary.DictionaryChanged += (sender, args) => Assert.Fail(); 130 | Assert.Throws(() => ObvDictionary.Add(DefaultKey, DefaultValue)); 131 | } 132 | 133 | #region Test Specific Utility 134 | private void AddMockEventNotifiers() { 135 | //Sets up event testers 136 | ObvDictionary.PropertyChanged += (sender, args) => AssertEvent.Call("PropertyChanged"); 137 | ObvDictionary.DictionaryChanged += (sender, args) => AssertEvent.Call("DictionaryChanged"); 138 | } 139 | 140 | private void AssertMockEventNotifiers(int timesPropertyCalled, int timesDictionaryCalled) { 141 | //Sets up event testers 142 | MockEvent.Verify(m => m.Call("PropertyChanged"), Times.Exactly(timesPropertyCalled)); 143 | MockEvent.Verify(m => m.Call("DictionaryChanged"), Times.Exactly(timesDictionaryCalled)); 144 | } 145 | 146 | #endregion 147 | 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/Standard/ObservableDictionaryTestInterface.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using NUnit.Framework; 3 | 4 | namespace Gstc.Collections.Observable.Test.Standard { 5 | 6 | [TestFixture] 7 | public class ObservableDictionaryTestInterface { 8 | 9 | public static Fixture Fixture = new Fixture(); 10 | 11 | public ObservableDictionary Observable; 12 | 13 | private readonly InterfaceTestCases _testCases = new InterfaceTestCases(); 14 | 15 | [SetUp] 16 | public void TestInit() { 17 | _testCases.TestInit(); 18 | Observable = new ObservableDictionary(); 19 | } 20 | 21 | [Test] 22 | public void Collection() { 23 | Observable.Add(Fixture.Create(), Fixture.Create()); 24 | Observable.Add(Fixture.Create(), Fixture.Create()); 25 | Observable.Add(Fixture.Create(), Fixture.Create()); 26 | _testCases.CollectionTest(Observable); 27 | } 28 | 29 | // Does not implement 30 | //[Test] 31 | //public void CollectionGeneric() => _testCases.CollectionGenericTest(ObservableDictionary); 32 | 33 | [Test] 34 | public void CollectionKvp() => _testCases.CollectionKeyValuePairTest(Observable); 35 | 36 | [Test] 37 | public void Dictionary() => _testCases.DictionaryTest(Observable); 38 | 39 | [Test] 40 | public void DictionaryGeneric() => _testCases.DictionaryGenericTest(Observable); 41 | 42 | //[Test] 43 | //public void List() => _testCases.ListTest(Observable); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/Standard/ObservableListTest.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using NUnit.Framework; 3 | using System.Collections.Generic; 4 | 5 | namespace Gstc.Collections.Observable.Test.Standard { 6 | [TestFixture] 7 | public class ObservableListTest : CollectionTestBase { 8 | 9 | private ObservableList ObvList { get; set; } 10 | 11 | [SetUp] 12 | public new void TestInit() { 13 | base.TestInit(); 14 | ObvList = new ObservableList(); 15 | } 16 | 17 | [Test, Description("")] 18 | public void TestMethod_InitList() { 19 | List list = new List { Item1, Item2, Item3 }; 20 | 21 | ObvList = new ObservableList(list); 22 | 23 | Assert.That(ObvList.Count == 3); 24 | Assert.That(ObvList[0] == Item1); 25 | Assert.That(ObvList[1] == Item2); 26 | Assert.That(ObvList[2] == Item3); 27 | } 28 | 29 | [Test, Description("")] 30 | public void TestMethod_ListAdd() { 31 | 32 | List list = new List { Item1, Item2, Item3 }; 33 | 34 | ObvList.CollectionChanged += AssertCollectionEventReset; 35 | AddMockNotifiers(); 36 | 37 | ObvList.List = list; 38 | 39 | AssertMockNotifiers(2, 1); 40 | Assert.That(ObvList.Count == 3); 41 | Assert.That(ObvList[0] == Item1); 42 | Assert.That(ObvList[1] == Item2); 43 | Assert.That(ObvList[2] == Item3); 44 | 45 | } 46 | 47 | [Test, Description("")] 48 | public void TestMethod_Add() { 49 | 50 | ObvList.CollectionChanged += GenerateAssertCollectionEventAddOne(0, DefaultTestItem); 51 | AddMockNotifiers(); 52 | 53 | ObvList.Add(DefaultTestItem); 54 | 55 | AssertMockNotifiers(2, 1); 56 | Assert.That(ObvList.Count == 1); 57 | Assert.That(ObvList[0] == DefaultTestItem); 58 | 59 | } 60 | 61 | [Test, Description("")] 62 | public void TestMethod_AddRange() { 63 | 64 | ObvList.Add(DefaultTestItem); 65 | 66 | ObvList.CollectionChanged += GenerateAssertCollectionEventAddThree(1, Item1, Item2, Item3); 67 | AddMockNotifiers(); 68 | 69 | ObvList.AddRange(new List() { Item1, Item2, Item3 }); 70 | 71 | AssertMockNotifiers(2, 1); 72 | Assert.That(ObvList.Count, Is.EqualTo(4)); 73 | Assert.That(ObvList[0], Is.EqualTo(DefaultTestItem)); 74 | Assert.That(ObvList[1], Is.EqualTo(Item1)); 75 | Assert.That(ObvList[2], Is.EqualTo(Item2)); 76 | Assert.That(ObvList[3], Is.EqualTo(Item3)); 77 | } 78 | 79 | [Test, Description("")] 80 | public void TestMethod_ReplaceList() { 81 | 82 | ObvList.Add(DefaultTestItem); 83 | ObvList.Add(UpdateTestItem); 84 | 85 | List list = new List { Item1, Item2, Item3 }; 86 | 87 | ObvList.CollectionChanged += AssertCollectionEventReset; 88 | AddMockNotifiers(); 89 | 90 | ObvList.List = list; 91 | 92 | AssertMockNotifiers(2, 1); 93 | Assert.That(ObvList.Count == 3); 94 | Assert.That(ObvList[0] == Item1); 95 | Assert.That(ObvList[1] == Item2); 96 | Assert.That(ObvList[2] == Item3); 97 | } 98 | 99 | [Test, Description("")] 100 | public void TestMethod_Remove() { 101 | 102 | ObvList.List = new List { Item1, Item2, Item3 }; 103 | 104 | ObvList.CollectionChanged += GenerateAssertCollectionEventRemoveOne(1, Item2); 105 | AddMockNotifiers(); 106 | 107 | ObvList.Remove(Item2); 108 | 109 | AssertMockNotifiers(2, 1); 110 | Assert.That(ObvList.Count == 2); 111 | Assert.That(ObvList[0] == Item1); 112 | Assert.That(ObvList[1] == Item3); 113 | } 114 | 115 | [Test, Description("")] 116 | public void TestMethod_RemoveAt() { 117 | 118 | ObvList.List = new List { Item1, Item2, Item3 }; 119 | 120 | ObvList.CollectionChanged += GenerateAssertCollectionEventRemoveOne(1, Item2); 121 | AddMockNotifiers(); 122 | 123 | ObvList.RemoveAt(1); 124 | 125 | AssertMockNotifiers(2, 1); 126 | Assert.That(ObvList.Count == 2); 127 | Assert.That(ObvList[0] == Item1); 128 | Assert.That(ObvList[1] == Item3); 129 | } 130 | 131 | /// 132 | /// Utility stuff 133 | /// 134 | private void AddMockNotifiers() { 135 | //Sets up event testers 136 | ObvList.PropertyChanged += (sender, args) => AssertEvent.Call("PropertyChanged"); 137 | ObvList.CollectionChanged += (sender, args) => AssertEvent.Call("CollectionChanged"); 138 | } 139 | 140 | private void AssertMockNotifiers(int timesPropertyCalled, int timesCollectionCalled) { 141 | MockEvent.Verify(m => m.Call("PropertyChanged"), Times.Exactly(timesPropertyCalled)); 142 | MockEvent.Verify(m => m.Call("CollectionChanged"), Times.Exactly(timesCollectionCalled)); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/Standard/ObservableListTestInterface.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Gstc.Collections.Observable.Test.Standard { 4 | 5 | [TestFixture] 6 | public class ObservableListTestInterface { 7 | 8 | public ObservableList Observable; 9 | 10 | private InterfaceTestCases _testCases = new InterfaceTestCases(); 11 | 12 | [SetUp] 13 | public void TestInit() { 14 | _testCases.TestInit(); 15 | Observable = new ObservableList(); 16 | } 17 | 18 | [Test] 19 | public void CollectionInterface() { 20 | //Test needs 3 test items. 21 | Observable.Add(new TestItem()); 22 | Observable.Add(new TestItem()); 23 | Observable.Add(new TestItem()); 24 | _testCases.CollectionTest(Observable); 25 | } 26 | 27 | [Test] 28 | public void CollectionGenericInterface() => _testCases.CollectionGenericTest(Observable); 29 | 30 | //[Test] 31 | //public void CollectionKvp() => _testCases.CollectionKeyValuePairTest(Observable); 32 | 33 | //[Test] 34 | //public void Dictionary() => _testCases.DictionaryTest(Observable); 35 | 36 | //[Test] 37 | //public void DictionaryGeneric() => _testCases.DictionaryGenericTest(Observable); 38 | 39 | [Test] 40 | public void ListGenericInterface() => _testCases.ListGenericTest(Observable); 41 | 42 | [Test] 43 | public void ListInterface() => _testCases.ListTest(Observable); 44 | 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/Standard/ObservableSortedListTest.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Moq; 3 | using NUnit.Framework; 4 | using System; 5 | 6 | namespace Gstc.Collections.Observable.Test.Standard { 7 | [TestFixture] 8 | public class ObservableSortedListTest : CollectionTestBase { 9 | 10 | private static object[] DefaultStaticKey { get; } = { 11 | Fixture.Create(), 12 | Fixture.Create() 13 | }; 14 | private static object[] DefaultStaticValue { get; } = { 15 | null, 16 | Fixture.Create(), 17 | Fixture.Create(), 18 | Fixture.Create(), 19 | Fixture.Create() 20 | }; 21 | 22 | private ObservableSortedList ObvSortedList { get; set; } 23 | 24 | 25 | [SetUp] 26 | public new void TestInit() { 27 | base.TestInit(); 28 | ObvSortedList = new ObservableSortedList(); 29 | } 30 | 31 | [Test, Description("")] 32 | public void TestMethod_Clear() { 33 | 34 | 35 | ObvSortedList.Add(DefaultKey, DefaultValue); 36 | 37 | ObvSortedList.CollectionChanged += AssertCollectionEventReset; 38 | ObvSortedList.DictionaryChanged += AssertDictionaryEventReset; 39 | AddMockEventNotifiers(); 40 | 41 | ObvSortedList.Clear(); 42 | 43 | AssertMockEventNotifiers(2, 1, 1); 44 | Assert.That(ObvSortedList.Count, Is.EqualTo(0)); 45 | 46 | } 47 | 48 | 49 | [Test, Description("")] 50 | public void TestMethod_ReplaceDictionary() { 51 | 52 | ObvSortedList.Add(DefaultKey, DefaultValue); 53 | 54 | ObvSortedList.CollectionChanged += AssertCollectionEventReset; 55 | ObvSortedList.DictionaryChanged += AssertDictionaryEventReset; 56 | AddMockEventNotifiers(); 57 | 58 | ObvSortedList.SortedList = new System.Collections.Generic.SortedList(); 59 | 60 | AssertMockEventNotifiers(2, 1, 1); 61 | Assert.That(ObvSortedList.Count, Is.EqualTo(0)); 62 | 63 | } 64 | 65 | [Test, TestCaseSource(nameof(DefaultStaticValue)), Description("Add key/item using Add")] 66 | public void TestMethod_Add(object item) { 67 | 68 | ObvSortedList.CollectionChanged += GenerateAssertCollectionEventAddOne(0, item); 69 | ObvSortedList.DictionaryChanged += GenerateAssertDictionaryEventAddOne(DefaultKey, item); 70 | AddMockEventNotifiers(); 71 | 72 | ObvSortedList.Add(DefaultKey, item); 73 | 74 | AssertMockEventNotifiers(2, 1, 1); 75 | } 76 | 77 | [Test, TestCaseSource(nameof(DefaultStaticValue)), Description("Add key/item using Add")] 78 | public void TestMethod_AddIndexer(object item) { 79 | 80 | 81 | ObvSortedList.CollectionChanged += GenerateAssertCollectionEventAddOne(0, item); 82 | ObvSortedList.DictionaryChanged += GenerateAssertDictionaryEventAddOne(DefaultKey, item); 83 | AddMockEventNotifiers(); 84 | 85 | ObvSortedList[DefaultKey] = item; 86 | AssertMockEventNotifiers(2, 1, 1); 87 | } 88 | 89 | [Test, Description("Remove Key/item using key")] 90 | public void TestMethod_Remove() { 91 | 92 | ObvSortedList.Add(DefaultKey, DefaultValue); 93 | 94 | ObvSortedList.CollectionChanged += GenerateAssertCollectionEventRemoveOne(0, DefaultValue); 95 | ObvSortedList.DictionaryChanged += GenerateAssertDictionaryEventRemoveOne(DefaultKey, DefaultValue); 96 | AddMockEventNotifiers(); 97 | 98 | ObvSortedList.Remove(DefaultKey); 99 | 100 | AssertMockEventNotifiers(2, 1, 1); 101 | } 102 | 103 | 104 | [Test, TestCaseSource(nameof(DefaultStaticValue)), Description("Replace value using index")] 105 | public void TestMethod_ReplaceValue(object value) { 106 | 107 | ObvSortedList.Add(DefaultKey, DefaultValue); 108 | 109 | ObvSortedList.CollectionChanged += (sender, args) => { 110 | Assert.AreEqual(args.NewItems[0], value); 111 | Assert.AreEqual(args.OldItems[0], DefaultValue); 112 | }; 113 | AddMockEventNotifiers(); 114 | 115 | ObvSortedList[DefaultKey] = value; 116 | 117 | AssertMockEventNotifiers(1, 1, 1); 118 | } 119 | 120 | [Test, Description("")] 121 | public void TestMethod_NullKey() { 122 | 123 | //Fails if null keys trigger an event. 124 | ObvSortedList.PropertyChanged += (sender, args) => Assert.Fail(); 125 | ObvSortedList.CollectionChanged += (sender, args) => Assert.Fail(); 126 | 127 | Assert.Throws(() => ObvSortedList.Add(null, DefaultValue)); 128 | Assert.Throws(() => ObvSortedList[null] = DefaultValue); 129 | } 130 | 131 | [Test, Description("")] 132 | public void TestMethod_DuplicateKey() { 133 | 134 | ObvSortedList.Add(DefaultKey, DefaultValue); 135 | 136 | ObvSortedList.PropertyChanged += (sender, args) => Assert.Fail(); 137 | ObvSortedList.CollectionChanged += (sender, args) => Assert.Fail(); 138 | 139 | Assert.Throws(() => ObvSortedList.Add(DefaultKey, DefaultValue)); 140 | } 141 | 142 | #region Test Specific Utility 143 | private void AddMockEventNotifiers() { 144 | //Sets up event testers 145 | ObvSortedList.PropertyChanged += (sender, args) => AssertEvent.Call("PropertyChanged"); 146 | ObvSortedList.CollectionChanged += (sender, args) => AssertEvent.Call("CollectionChanged"); 147 | ObvSortedList.DictionaryChanged += (sender, args) => AssertEvent.Call("DictionaryChanged"); 148 | } 149 | 150 | private void AssertMockEventNotifiers(int timesPropertyCalled, int timesCollectionCalled, int timesDictionaryCalled) { 151 | //Sets up event testers 152 | MockEvent.Verify(m => m.Call("PropertyChanged"), Times.Exactly(timesPropertyCalled)); 153 | MockEvent.Verify(m => m.Call("CollectionChanged"), Times.Exactly(timesCollectionCalled)); 154 | MockEvent.Verify(m => m.Call("DictionaryChanged"), Times.Exactly(timesDictionaryCalled)); 155 | } 156 | 157 | #endregion 158 | 159 | 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/Standard/ObservableSortedListTestInterface.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using NUnit.Framework; 3 | 4 | namespace Gstc.Collections.Observable.Test.Standard { 5 | 6 | [TestFixture] 7 | public class ObservableSortedListTestInterface { 8 | 9 | public static Fixture Fixture = new Fixture(); 10 | 11 | public ObservableSortedList Observable; 12 | 13 | private readonly InterfaceTestCases _testCases = new InterfaceTestCases(); 14 | 15 | [SetUp] 16 | public void TestInit() { 17 | _testCases.TestInit(); 18 | Observable = new ObservableSortedList(); 19 | } 20 | 21 | [Test] 22 | public void Collection() { 23 | Observable.Add(Fixture.Create(), Fixture.Create()); 24 | Observable.Add(Fixture.Create(), Fixture.Create()); 25 | Observable.Add(Fixture.Create(), Fixture.Create()); 26 | _testCases.CollectionTest(Observable); 27 | } 28 | 29 | //Does not implement 30 | //[Test] 31 | //public void CollectionGeneric() => _testCases.CollectionGenericTest(ObservableSortedList); 32 | 33 | [Test] 34 | public void CollectionKvp() => _testCases.CollectionKeyValuePairTest(Observable); 35 | 36 | [Test] 37 | public void Dictionary() => _testCases.DictionaryTest(Observable); 38 | 39 | [Test] 40 | public void DictionaryGeneric() => _testCases.DictionaryGenericTest(Observable); 41 | 42 | //[Test] 43 | //public void ListInterface() => _testCases.ListTest(Observable); 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable.Test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/BaseObservableCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Gstc.Collections.Observable.Base { 6 | public abstract class BaseObservableCollection : 7 | NotifyCollection, 8 | IObservableCollection { 9 | 10 | protected abstract ICollection InternalCollection { get; } 11 | //public abstract TItem this[int index] { get; set; } 12 | 13 | //Abstract methods 14 | public abstract void Add(TItem item); 15 | public abstract void Clear(); 16 | public abstract bool Remove(TItem item); 17 | 18 | // ICollection 19 | public int Count => InternalCollection.Count; 20 | public bool IsReadOnly => ((IList)InternalCollection).IsReadOnly; 21 | public bool Contains(TItem item) => InternalCollection.Contains(item); 22 | public void CopyTo(TItem[] array, int arrayIndex) => InternalCollection.CopyTo(array, arrayIndex); 23 | 24 | // Enumerator 25 | public IEnumerator GetEnumerator() => InternalCollection.GetEnumerator(); 26 | IEnumerator IEnumerable.GetEnumerator() => InternalCollection.GetEnumerator(); 27 | 28 | //ICollection 29 | int ICollection.Count => InternalCollection.Count; 30 | void ICollection.CopyTo(Array array, int arrayIndex) => ((ICollection)InternalCollection).CopyTo(array, arrayIndex); 31 | bool ICollection.IsSynchronized => ((ICollection)InternalCollection).IsSynchronized; 32 | object ICollection.SyncRoot => ((ICollection)InternalCollection).SyncRoot; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/BaseObservableDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Gstc.Collections.Observable.Base { 6 | public abstract class BaseObservableDictionary : 7 | NotifyDictionary, 8 | IObservableDictionary { 9 | 10 | //Absstract Methods 11 | protected abstract IDictionary InternalDictionary { get; } 12 | public abstract TValue this[TKey key] { get; set; } 13 | public abstract void Clear(); 14 | public abstract void Add(TKey key, TValue value); 15 | public abstract bool Remove(TKey key); 16 | 17 | //Dictionary 18 | public int Count => InternalDictionary.Count; 19 | public bool ContainsKey(TKey key) => InternalDictionary.ContainsKey(key); 20 | public bool TryGetValue(TKey key, out TValue value) => InternalDictionary.TryGetValue(key, out value); 21 | 22 | //Dictionary 23 | object IDictionary.this[object key] { 24 | get => ((IDictionary)InternalDictionary)[key]; 25 | set => this[(TKey)key] = (TValue)value; 26 | } 27 | void IDictionary.Add(object key, object value) => Add((TKey)key, (TValue)value); 28 | void IDictionary.Remove(object key) => Remove((TKey)key); 29 | bool IDictionary.Contains(object key) => InternalDictionary.ContainsKey((TKey)key); 30 | bool IDictionary.IsFixedSize => ((IDictionary)InternalDictionary).IsFixedSize; 31 | bool IDictionary.IsReadOnly => ((IDictionary)InternalDictionary).IsReadOnly; 32 | ICollection IDictionary.Keys => ((IDictionary)InternalDictionary).Keys; 33 | ICollection IDictionary.Values => ((IDictionary)InternalDictionary).Values; 34 | IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)InternalDictionary).GetEnumerator(); 35 | 36 | // Key Value Operations 37 | public ICollection Keys => InternalDictionary.Keys; 38 | public ICollection Values => InternalDictionary.Values; 39 | void ICollection>.Add(KeyValuePair item) => Add(item.Key, item.Value); 40 | bool ICollection>.Remove(KeyValuePair item) => Remove(item.Key); 41 | bool ICollection>.Contains(KeyValuePair item) => InternalDictionary.Contains(item); 42 | void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => InternalDictionary.CopyTo(array, arrayIndex); 43 | bool ICollection>.IsReadOnly => InternalDictionary.IsReadOnly; 44 | 45 | //Enumerators 46 | public IEnumerator GetEnumerator() => InternalDictionary.GetEnumerator(); 47 | IEnumerator> IEnumerable>.GetEnumerator() => InternalDictionary.GetEnumerator(); 48 | 49 | //ICollection 50 | void ICollection.CopyTo(Array array, int arrayIndex) => ((ICollection)InternalDictionary).CopyTo(array, arrayIndex); 51 | bool ICollection.IsSynchronized => ((ICollection)InternalDictionary).IsSynchronized; 52 | object ICollection.SyncRoot => ((ICollection)InternalDictionary).SyncRoot; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/BaseObservableDictionaryCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Gstc.Collections.Observable.Base { 6 | public abstract class BaseObservableDictionaryCollection : 7 | NotifyDictionaryCollection, 8 | IObservableDictionaryCollection { 9 | 10 | //internal collections 11 | protected abstract ICollection InternalCollection { get; } 12 | protected abstract IDictionary InternalDictionary { get; } 13 | 14 | //Abstract methods 15 | public abstract TValue this[TKey key] { get; set; } 16 | public abstract void Add(TKey key, TValue value); 17 | public abstract bool Remove(TKey key); 18 | public abstract void Clear(); 19 | 20 | public int Count => InternalDictionary.Count; 21 | 22 | 23 | //IDictionary 24 | public bool ContainsKey(TKey key) => InternalDictionary.ContainsKey(key); 25 | public bool TryGetValue(TKey key, out TValue value) => InternalDictionary.TryGetValue(key, out value); 26 | 27 | //Dictionary 28 | object IDictionary.this[object key] { 29 | get => ((IDictionary)InternalDictionary)[key]; 30 | set => this[(TKey)key] = (TValue)value; 31 | } 32 | void IDictionary.Add(object key, object value) => Add((TKey)key, (TValue)value); 33 | void IDictionary.Remove(object key) => Remove((TKey)key); 34 | bool IDictionary.Contains(object key) => InternalDictionary.ContainsKey((TKey)key); 35 | bool IDictionary.IsFixedSize => ((IDictionary)InternalDictionary).IsFixedSize; 36 | bool IDictionary.IsReadOnly => ((IDictionary)InternalDictionary).IsReadOnly; 37 | ICollection IDictionary.Keys => ((IDictionary)InternalDictionary).Keys; 38 | ICollection IDictionary.Values => ((IDictionary)InternalDictionary).Values; 39 | IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)InternalDictionary).GetEnumerator(); 40 | 41 | 42 | //ICollection 43 | /* 44 | void ICollection.CopyTo(TValue[] array, int arrayIndex) => InternalCollection.CopyTo(array, arrayIndex); 45 | bool ICollection.Contains(TValue item) => InternalCollection.Contains(item); 46 | bool ICollection.IsReadOnly => InternalDictionary.IsReadOnly; 47 | void ICollection.Add(TValue item) => throw new NotImplementedException("Adding an item without a key is not allowed."); 48 | 49 | bool ICollection.Remove(TValue item) { 50 | if (!InternalCollection.Contains(item)) return false; 51 | foreach (var dictionaryItem in InternalDictionary) { 52 | if (item?.GetHashCode() != dictionaryItem.Value.GetHashCode()) continue; 53 | Remove(dictionaryItem.Key); 54 | return true; 55 | } 56 | return false; 57 | } 58 | */ 59 | //Key Value operations 60 | public ICollection Keys => InternalDictionary.Keys; 61 | public ICollection Values => InternalDictionary.Values; 62 | void ICollection>.Add(KeyValuePair item) => Add(item.Key, item.Value); 63 | bool ICollection>.Remove(KeyValuePair item) => Remove(item.Key); 64 | bool ICollection>.Contains(KeyValuePair item) => InternalDictionary.Contains(item); 65 | void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => InternalDictionary.CopyTo(array, arrayIndex); bool ICollection>.IsReadOnly => InternalDictionary.IsReadOnly; 66 | 67 | //Enumerator 68 | public IEnumerator GetEnumerator() => InternalCollection.GetEnumerator(); 69 | IEnumerator IEnumerable.GetEnumerator() => InternalDictionary.GetEnumerator(); 70 | IEnumerator> IEnumerable>.GetEnumerator() => InternalDictionary.GetEnumerator(); 71 | 72 | //ICollection 73 | void ICollection.CopyTo(Array array, int arrayIndex) => ((ICollection)InternalDictionary).CopyTo(array, arrayIndex); 74 | bool ICollection.IsSynchronized => ((ICollection)InternalDictionary).IsSynchronized; 75 | object ICollection.SyncRoot => ((ICollection)InternalDictionary).SyncRoot; 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/BaseObservableList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Gstc.Collections.Observable.Base { 6 | 7 | public abstract class BaseObservableList : 8 | NotifyCollection, 9 | IObservableList { 10 | 11 | #region abstract methods 12 | protected abstract IList InternalList { get; } 13 | public abstract TItem this[int index] { get; set; } 14 | public abstract void Insert(int index, TItem item); 15 | public abstract void RemoveAt(int index); 16 | public abstract void Add(TItem item); 17 | public abstract void Clear(); 18 | public abstract bool Remove(TItem item); 19 | public abstract void Move(int oldIndex, int newIndex); 20 | 21 | #endregion 22 | 23 | 24 | #region IList 25 | 26 | int IList.Add(object value) { Add((TItem)value); return Count - 1; } 27 | 28 | bool IList.Contains(object value) => Contains((TItem)value); 29 | int IList.IndexOf(object value) => IndexOf((TItem)value); 30 | void IList.Insert(int index, object value) => Insert(index, (TItem)value); 31 | void IList.Remove(object value) => Remove((TItem)value); 32 | 33 | bool IList.IsReadOnly => InternalList.IsReadOnly; 34 | bool IList.IsFixedSize => false; 35 | object IList.this[int index] { 36 | get => this[index]; 37 | set => this[index] = (TItem)value; 38 | } 39 | #endregion 40 | 41 | TItem IList.this[int index] { 42 | get => this[index]; 43 | set => this[index] = value; 44 | } 45 | 46 | public int Count => InternalList.Count; 47 | 48 | public bool Contains(TItem item) => InternalList.Contains(item); 49 | public void CopyTo(TItem[] array, int arrayIndex) => InternalList.CopyTo(array, arrayIndex); 50 | public int IndexOf(TItem item) => InternalList.IndexOf(item); 51 | public IEnumerator GetEnumerator() => InternalList.GetEnumerator(); 52 | bool ICollection.IsReadOnly => InternalList.IsReadOnly; 53 | IEnumerator IEnumerable.GetEnumerator() => InternalList.GetEnumerator(); 54 | 55 | //ICollection 56 | void ICollection.CopyTo(Array array, int arrayIndex) => ((ICollection)InternalList).CopyTo(array, arrayIndex); 57 | 58 | 59 | 60 | bool ICollection.IsSynchronized => ((ICollection)InternalList).IsSynchronized; 61 | object ICollection.SyncRoot => ((ICollection)InternalList).SyncRoot; 62 | 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/BaseObservableListDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Gstc.Collections.Observable.Base { 6 | 7 | //TODO: Rename as ObservableIDictionaryBase and break out into several different things 8 | public abstract class BaseObservableListDictionary : 9 | NotifyDictionaryCollection, 10 | IObservableDictionaryCollection, 11 | IObservableList { 12 | 13 | protected abstract IList InternalList { get; } 14 | protected abstract IDictionary InternalDictionary { get; } 15 | 16 | public abstract TValue this[int index] { get; set; } 17 | public abstract void Insert(int index, TValue item); 18 | public abstract void RemoveAt(int index); 19 | public abstract void Add(TValue item); 20 | public abstract void Clear(); 21 | public abstract bool Remove(TValue item); 22 | public abstract void Move(int oldIndex, int newIndex); 23 | 24 | //Dictionary<> 25 | public abstract TValue this[TKey key] { get; set; } 26 | public abstract void Add(TKey key, TValue value); 27 | public abstract bool Remove(TKey key); 28 | 29 | //List 30 | public int Count => InternalList.Count; 31 | public int IndexOf(TValue item) => InternalList.IndexOf(item); 32 | 33 | //Dictionary 34 | object IDictionary.this[object key] { 35 | get => ((IDictionary)InternalDictionary)[key]; 36 | set => this[(TKey)key] = (TValue)value; 37 | } 38 | void IDictionary.Add(object key, object value) => Add((TKey)key, (TValue)value); 39 | void IDictionary.Remove(object key) => Remove((TKey)key); 40 | bool IDictionary.Contains(object key) => InternalDictionary.ContainsKey((TKey)key); 41 | bool IDictionary.IsFixedSize => ((IDictionary)InternalDictionary).IsFixedSize; 42 | bool IDictionary.IsReadOnly => ((IDictionary)InternalDictionary).IsReadOnly; 43 | ICollection IDictionary.Keys => ((IDictionary)InternalDictionary).Keys; 44 | ICollection IDictionary.Values => ((IDictionary)InternalDictionary).Values; 45 | IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)InternalDictionary).GetEnumerator(); 46 | 47 | //Dictionary<> 48 | public bool ContainsKey(TKey key) => InternalDictionary.ContainsKey(key); 49 | public bool TryGetValue(TKey key, out TValue value) => InternalDictionary.TryGetValue(key, out value); 50 | 51 | //Icollection 52 | void ICollection.CopyTo(TValue[] array, int arrayIndex) => InternalList.CopyTo(array, arrayIndex); 53 | bool ICollection.Contains(TValue item) => InternalList.Contains(item); 54 | bool ICollection.IsReadOnly => InternalList.IsReadOnly; 55 | void ICollection.Add(TValue item) => Add(item); 56 | bool ICollection.Remove(TValue item) => Remove(item); 57 | 58 | //Key Value operations 59 | public ICollection Keys => InternalDictionary.Keys; 60 | public ICollection Values => InternalDictionary.Values; 61 | void ICollection>.Add(KeyValuePair item) => Add(item.Key, item.Value); 62 | bool ICollection>.Remove(KeyValuePair item) => Remove(item.Key); 63 | bool ICollection>.Contains(KeyValuePair item) => InternalDictionary.Contains(item); 64 | void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => InternalDictionary.CopyTo(array, arrayIndex); bool ICollection>.IsReadOnly => InternalDictionary.IsReadOnly; 65 | 66 | //Enumerator 67 | public IEnumerator GetEnumerator() => InternalList.GetEnumerator(); 68 | 69 | 70 | IEnumerator IEnumerable.GetEnumerator() => InternalList.GetEnumerator(); 71 | IEnumerator> IEnumerable>.GetEnumerator() => InternalDictionary.GetEnumerator(); 72 | 73 | 74 | //ICollection 75 | void ICollection.CopyTo(Array array, int arrayIndex) => ((ICollection)InternalList).CopyTo(array, arrayIndex); 76 | bool ICollection.IsSynchronized => ((ICollection)InternalList).IsSynchronized; 77 | object ICollection.SyncRoot => ((ICollection)InternalList).SyncRoot; 78 | 79 | 80 | //IList 81 | public bool IsReadOnly => throw new NotImplementedException(); 82 | 83 | public bool IsFixedSize => throw new NotImplementedException(); 84 | 85 | object IList.this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 86 | int IList.Add(object value) => throw new NotImplementedException(); 87 | bool IList.Contains(object value) => throw new NotImplementedException(); 88 | int IList.IndexOf(object value) => throw new NotImplementedException(); 89 | void IList.Insert(int index, object value) => throw new NotImplementedException(); 90 | void IList.Remove(object value) => throw new NotImplementedException(); 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/BaseObservableSortedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Gstc.Collections.Observable.Base { 7 | public abstract class BaseObservableSortedList : 8 | 9 | NotifyDictionaryCollection, 10 | IObservableCollection, 11 | 12 | IDictionary, ICollection>, 13 | IEnumerable>, IDictionary, ICollection, IEnumerable { 14 | 15 | protected abstract SortedList InternalSortedList { get; } 16 | 17 | public abstract TValue this[TKey key] { get; set; } 18 | public abstract void Clear(); 19 | public abstract void Add(TKey key, TValue value); 20 | public abstract bool Remove(TKey key); 21 | 22 | 23 | public int Count => InternalSortedList.Count; 24 | public bool ContainsKey(TKey key) => InternalSortedList.ContainsKey(key); 25 | public bool TryGetValue(TKey key, out TValue value) => InternalSortedList.TryGetValue(key, out value); 26 | 27 | //IDictionary 28 | void IDictionary.Add(object obj1, object obj2) => Add((TKey)obj1, (TValue)obj2); 29 | void IDictionary.Remove(object obj) => Remove((TKey)obj); 30 | 31 | public object this[object key] { 32 | get => this[(TKey)key]; 33 | set => this[(TKey)key] = (TValue)value; 34 | } 35 | 36 | bool IDictionary.IsFixedSize => ((IDictionary)InternalSortedList).IsFixedSize; 37 | bool IDictionary.IsReadOnly => ((IDictionary)InternalSortedList).IsReadOnly; 38 | bool IDictionary.Contains(object obj) => ((IDictionary)InternalSortedList).Contains(obj); 39 | 40 | ICollection IDictionary.Keys => ((IDictionary)InternalSortedList).Keys; 41 | ICollection IDictionary.Values => ((IDictionary)InternalSortedList).Values; 42 | 43 | //ICollection 44 | void ICollection.CopyTo(Array array, int arrayIndex) => ((ICollection)InternalSortedList).CopyTo(array, arrayIndex); 45 | bool ICollection.IsSynchronized => ((ICollection)InternalSortedList).IsSynchronized; 46 | object ICollection.SyncRoot => ((ICollection)InternalSortedList).SyncRoot; 47 | 48 | 49 | //Key Value operations 50 | public ICollection Keys => InternalSortedList.Keys; 51 | public ICollection Values => InternalSortedList.Values; 52 | void ICollection>.Add(KeyValuePair item) => Add(item.Key, item.Value); 53 | bool ICollection>.Remove(KeyValuePair item) => Remove(item.Key); 54 | bool ICollection>.Contains(KeyValuePair item) => InternalSortedList.Contains(item); 55 | void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => ((ICollection>)InternalSortedList).CopyTo(array, arrayIndex); 56 | bool ICollection>.IsReadOnly => ((ICollection>)InternalSortedList).IsReadOnly; 57 | 58 | //Enumerator 59 | IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)InternalSortedList).GetEnumerator(); 60 | IEnumerator IEnumerable.GetEnumerator() => InternalSortedList.GetEnumerator(); 61 | IEnumerator> IEnumerable>.GetEnumerator() => ((IDictionary)InternalSortedList).GetEnumerator(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/INotifyListChanged.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Specialized; 2 | 3 | namespace Gstc.Collections.Observable.Base { 4 | /// 5 | /// Provides a set of events that will be triggered by changes to a List. 6 | /// 7 | public interface INotifyListChanged { 8 | 9 | /// 10 | /// Triggers events when an item or items are added. 11 | /// 12 | event NotifyCollectionChangedEventHandler Added; 13 | 14 | /// 15 | /// Triggers events when an item or items are removed. 16 | /// 17 | event NotifyCollectionChangedEventHandler Removed; 18 | 19 | /// 20 | /// Triggers events when an item has changed position. 21 | /// 22 | event NotifyCollectionChangedEventHandler Moved; 23 | 24 | /// 25 | /// Triggers events when an item has been replaced. 26 | /// 27 | event NotifyCollectionChangedEventHandler Replaced; 28 | 29 | /// 30 | /// Triggers events when an the list has changed substantially such as a Clear(). 31 | /// 32 | event NotifyCollectionChangedEventHandler Reset; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/NotifyCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Specialized; 3 | 4 | namespace Gstc.Collections.Observable.Base { 5 | 6 | public abstract class NotifyCollection : 7 | NotifyProperty, 8 | INotifyCollectionChanged { 9 | 10 | #region Events 11 | /// 12 | /// Triggers events on any change of collection. 13 | /// 14 | public event NotifyCollectionChangedEventHandler CollectionChanged; 15 | 16 | /// 17 | /// Triggers events when an item or items are added. 18 | /// 19 | public event NotifyCollectionChangedEventHandler Added; 20 | /// 21 | /// Triggers events when an item or items are removed. 22 | /// 23 | public event NotifyCollectionChangedEventHandler Removed; 24 | /// 25 | /// Triggers events when an item has changed position. 26 | /// 27 | public event NotifyCollectionChangedEventHandler Moved; 28 | /// 29 | /// Triggers events when an item has been replaced. 30 | /// 31 | public event NotifyCollectionChangedEventHandler Replaced; 32 | /// 33 | /// Triggers events when an the list has changed substantially such as a Clear(). 34 | /// 35 | public event NotifyCollectionChangedEventHandler Reset; 36 | #endregion 37 | 38 | #region Methods 39 | protected void OnCollectionChangedReset() { 40 | var eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); 41 | using (BlockReentrancy()) { 42 | CollectionChanged?.Invoke(this, eventArgs); 43 | Reset?.Invoke(this, eventArgs); 44 | } 45 | } 46 | 47 | protected void OnCollectionChangedAdd(object value, int index) { 48 | var eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value, index); 49 | using (BlockReentrancy()) { 50 | CollectionChanged?.Invoke(this, eventArgs); 51 | Added?.Invoke(this, eventArgs); 52 | } 53 | } 54 | 55 | protected void OnCollectionChangedAddMany(IList valueList, int index) { 56 | var eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, valueList, index); 57 | using (BlockReentrancy()) { 58 | CollectionChanged?.Invoke(this, eventArgs); 59 | Added?.Invoke(this, eventArgs); 60 | } 61 | } 62 | 63 | protected void OnCollectionChangedRemove(object value, int index) { 64 | var eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value, index); 65 | using (BlockReentrancy()) { 66 | CollectionChanged?.Invoke(this, eventArgs); 67 | Removed?.Invoke(this, eventArgs); 68 | } 69 | } 70 | 71 | protected void OnCollectionChangedMove(object value, int index, int oldIndex) { 72 | var eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, value, index, oldIndex); 73 | using (BlockReentrancy()) { 74 | CollectionChanged?.Invoke(this, eventArgs); 75 | Moved?.Invoke(this, eventArgs); 76 | } 77 | } 78 | 79 | protected void OnCollectionChangedReplace(object oldValue, object newValue, int index) { 80 | var eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue, oldValue, index); 81 | using (BlockReentrancy()) { 82 | CollectionChanged?.Invoke(this, eventArgs); 83 | Replaced?.Invoke(this, eventArgs); 84 | } 85 | } 86 | #endregion 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/NotifyDictionary.cs: -------------------------------------------------------------------------------- 1 | namespace Gstc.Collections.Observable.Base { 2 | 3 | public abstract class NotifyDictionary : 4 | NotifyProperty, 5 | INotifyDictionaryChanged { 6 | 7 | public event NotifyDictionaryChangedEventHandler DictionaryChanged; 8 | 9 | protected virtual void OnDictionaryChanged(NotifyDictionaryChangedEventArgs e) { 10 | if (DictionaryChanged == null) return; 11 | using (BlockReentrancy()) { DictionaryChanged(this, e); } 12 | } 13 | 14 | //Reset 15 | protected void OnDictionaryReset() 16 | => OnDictionaryChanged(new NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction.Reset)); 17 | 18 | //Add / Remove 19 | protected void OnDictionaryAdd(TKey key, TValue item) 20 | => OnDictionaryChanged(new NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction.Add, key, item)); 21 | 22 | protected void OnDictionaryRemove(TKey key, TValue item) 23 | => OnDictionaryChanged(new NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction.Remove, key, item)); 24 | 25 | //Replace 26 | protected void OnDictionaryReplace(TKey key, TValue oldItem, TValue newItem) 27 | => OnDictionaryChanged(new NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction.Replace, key, oldItem, newItem)); 28 | 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/NotifyDictionaryChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Gstc.Collections.Observable.Base { 6 | public class NotifyDictionaryChangedEventArgs { 7 | 8 | #region contructors 9 | /// 10 | /// Construct a NotifyDictionaryChangedEventArgs that describes a reset change. 11 | /// 12 | /// The action that caused the event; must be Reset action. 13 | public NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction action) { 14 | if (action != NotifyDictionaryChangedAction.Reset) throw new ArgumentException(SR.GetString(SR.WrongActionForCtor, NotifyDictionaryChangedAction.Reset), nameof(action)); 15 | InitializeAdd(action, null, null); 16 | } 17 | 18 | /// 19 | /// Construct a NotifyDictionaryChangedEventArgs that describes a one-item change. 20 | /// 21 | /// The action that caused the event; must be Add or Remove action. 22 | /// The item affected by the change. 23 | /// The key where the change occurred. 24 | public NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction action, object key, object changedItem) { 25 | if (action == NotifyDictionaryChangedAction.Add) InitializeAdd(action, new[] { key }, new[] { changedItem }); 26 | else if (action == NotifyDictionaryChangedAction.Remove) InitializeRemove(action, new[] { key }, new[] { changedItem }); 27 | else throw new ArgumentException(SR.GetString(SR.MustBeResetAddOrRemoveActionForCtor), "action"); 28 | } 29 | 30 | /// 31 | /// Construct a NotifyDictionaryChangedEventArgs that describes a multi-item change; can only be Add or Remove action. 32 | /// 33 | /// The action that caused the event. 34 | /// The items affected by the change. 35 | /// Keys of items that have been changed. 36 | public NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction action, IList keys, IList changedItems) { 37 | 38 | if (changedItems == null) throw new ArgumentNullException("changedItems"); 39 | if (keys == null || keys.Count == 0) throw new ArgumentException(SR.GetString(SR.KeyMustNotBeNullOrZeroLength), "Keys"); 40 | 41 | if (action == NotifyDictionaryChangedAction.Add) InitializeAdd(action, keys, changedItems); 42 | else if (action == NotifyDictionaryChangedAction.Remove) InitializeRemove(action, keys, changedItems); 43 | else throw new ArgumentException(SR.GetString(SR.MustBeResetAddOrRemoveActionForCtor), "action"); 44 | 45 | } 46 | 47 | 48 | /// 49 | /// Construct a NotifyDictionaryChangedEventArgs that describes a one-item Replace event, can only be Replace. 50 | /// 51 | /// Can only be a Replace action. 52 | /// The new item replacing the original item. 53 | /// The original item that is replaced. 54 | /// Keys of items that have been replaced. 55 | public NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction action, object key, object oldItem, object newItem) { 56 | if (action != NotifyDictionaryChangedAction.Replace) throw new ArgumentException(SR.GetString(SR.WrongActionForCtor, NotifyDictionaryChangedAction.Replace), "action"); 57 | InitializeReplace(action, new[] { key }, new[] { oldItem }, new[] { newItem }); 58 | } 59 | 60 | /// 61 | /// Construct a NotifyDictionaryChangedEventArgs that describes a multi-item Replace event. 62 | /// 63 | /// Can only be a Replace action. 64 | /// The new items replacing the original items. 65 | /// The original items that are replaced. 66 | /// Keys of items that have been replaced. 67 | public NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction action, IList keys, IList newItems, IList oldItems) { 68 | if (action != NotifyDictionaryChangedAction.Replace) throw new ArgumentException(SR.GetString(SR.WrongActionForCtor, NotifyDictionaryChangedAction.Replace), "action"); 69 | if (newItems == null) throw new ArgumentNullException("newItems"); 70 | if (oldItems == null) throw new ArgumentNullException("oldItems"); 71 | InitializeReplace(action, keys, oldItems, newItems); 72 | } 73 | 74 | 75 | #endregion 76 | private void InitializeAdd(NotifyDictionaryChangedAction action, IList newKeys, IList newItems) { 77 | Action = action; 78 | NewItems = newItems; 79 | NewKeys = newKeys; 80 | } 81 | 82 | private void InitializeRemove(NotifyDictionaryChangedAction action, IList oldKeys, IList oldItems) { 83 | Action = action; 84 | OldItems = oldItems; 85 | OldKeys = oldKeys; 86 | } 87 | 88 | private void InitializeReplace(NotifyDictionaryChangedAction action, IList keys, IList oldItems, IList newItems) { 89 | Action = action; 90 | NewItems = newItems; 91 | NewKeys = keys; 92 | OldItems = oldItems; 93 | OldKeys = keys; 94 | } 95 | 96 | /* 97 | private void InitializeMoveOrReplace(NotifyDictionaryChangedAction action, IList newItems, IList oldItems, IList newKeys, IList oldKeys) { 98 | InitializeAdd(action, newItems, newKeys); 99 | InitializeRemove(action, oldItems, oldKeys); 100 | } 101 | */ 102 | 103 | 104 | #region Public Properties 105 | 106 | /// 107 | /// The action that caused the event. 108 | /// 109 | public NotifyDictionaryChangedAction Action { get; private set; } 110 | 111 | /// 112 | /// The items affected by the change. 113 | /// 114 | public IList NewItems { get; private set; } 115 | 116 | /// 117 | /// The old items affected by the change (for Replace events). 118 | /// 119 | public IList OldItems { get; private set; } 120 | 121 | /// 122 | /// The index where the change occurred. 123 | /// 124 | public IList NewKeys { get; private set; } 125 | 126 | /// 127 | /// The old index where the change occurred (for Move events). 128 | /// 129 | public IList OldKeys { get; private set; } 130 | 131 | #endregion 132 | 133 | } 134 | 135 | 136 | public interface INotifyDictionaryChanged { 137 | event NotifyDictionaryChangedEventHandler DictionaryChanged; 138 | } 139 | 140 | public delegate void NotifyDictionaryChangedEventHandler(object sender, NotifyDictionaryChangedEventArgs e); 141 | 142 | public enum NotifyDictionaryChangedAction { 143 | Add, 144 | Remove, 145 | Replace, 146 | Reset, 147 | //Move, 148 | } 149 | 150 | /// 151 | /// Kludge to keep code consistent with the .NET core error handling signatures. 152 | /// 153 | static class SR { 154 | internal const string WrongActionForCtor = "WrongActionForCtor"; 155 | internal const string MustBeResetAddOrRemoveActionForCtor = "MustBeResetAddOrRemoveActionForCtor"; 156 | internal const string ResetActionRequiresNullItem = "ResetActionRequiresNullItem"; 157 | internal const string ResetActionRequiresNullKey = "ResetActionRequiresNullKey"; 158 | internal const string KeyMustNotBeNullOrZeroLength = "KeyMustNotBeNullOrZeroLength"; 159 | 160 | public static string GetString(string name, params object[] args) { 161 | var stringBuilder = new StringBuilder(); 162 | stringBuilder.Append(name); 163 | stringBuilder.Append(" : "); 164 | foreach (var arg in args) { 165 | stringBuilder.Append(arg); 166 | stringBuilder.Append(", "); 167 | } 168 | return name; 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/NotifyDictionaryChangedEventArgs_Generic.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Gstc.Collections.Observable.Base { 4 | public class NotifyDictionaryChangedEventArgs : NotifyDictionaryChangedEventArgs { 5 | 6 | public NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction action) : base(action) { } 7 | 8 | public NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction action, TKey key, TValue changedItem) : base(action, key, changedItem) { } 9 | 10 | public NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction action, IList keys, IList changedItems) : base(action, keys, changedItems) { } 11 | 12 | public NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction action, TKey key, TValue oldItem, TValue newItem) : base(action, key, oldItem, newItem) { } 13 | 14 | public NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction action, IList keys, IList newItems, IList oldItems) : base(action, keys, newItems, oldItems) { } 15 | 16 | 17 | //public new IList NewItems => (IList) base.NewItems; 18 | 19 | //public new IList OldItems => (IList) base.OldItems; 20 | 21 | //public new IList NewKeys => (IList) base.NewKeys; 22 | 23 | //public new IList OldKeys => (IList) base.OldKeys; 24 | 25 | } 26 | 27 | public interface INotifyDictionaryChanged { 28 | event NotifyDictionaryChangedEventHandler DictionaryChanged; 29 | } 30 | 31 | public delegate void NotifyDictionaryChangedEventHandler(object sender, NotifyDictionaryChangedEventArgs e); 32 | } 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/NotifyDictionaryCollection.cs: -------------------------------------------------------------------------------- 1 | namespace Gstc.Collections.Observable.Base { 2 | public abstract class NotifyDictionaryCollection : 3 | NotifyCollection, 4 | INotifyDictionaryChanged { 5 | 6 | public event NotifyDictionaryChangedEventHandler DictionaryChanged; 7 | 8 | protected virtual void OnDictionaryChanged(NotifyDictionaryChangedEventArgs e) { 9 | if (DictionaryChanged == null) return; 10 | using (BlockReentrancy()) { DictionaryChanged?.Invoke(this, e); } 11 | } 12 | 13 | //Reset 14 | protected void OnDictionaryReset() 15 | => OnDictionaryChanged(new NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction.Reset)); 16 | 17 | //Add / Remove 18 | protected void OnDictionaryChangedAdd(TKey key, TValue item) 19 | => OnDictionaryChanged(new NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction.Add, key, item)); 20 | 21 | protected void OnDictionaryRemove(TKey key, TValue item) 22 | => OnDictionaryChanged(new NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction.Remove, key, item)); 23 | 24 | //Replace 25 | protected void OnDictionaryReplace(TKey key, TValue oldItem, TValue newItem) 26 | => OnDictionaryChanged(new NotifyDictionaryChangedEventArgs(NotifyDictionaryChangedAction.Replace, key, oldItem, newItem)); 27 | 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Base/NotifyProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace Gstc.Collections.Observable.Base { 5 | 6 | public abstract class NotifyProperty : INotifyPropertyChanged { 7 | protected const string CountString = "Count"; 8 | protected const string IndexerName = "Item[]"; 9 | 10 | public event PropertyChangedEventHandler PropertyChanged; 11 | 12 | protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e); 13 | 14 | protected void OnPropertyChanged(string propertyName) => OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); 15 | 16 | protected void OnPropertyChangedCountAndIndex() { 17 | OnPropertyChanged(CountString); 18 | OnPropertyChanged(IndexerName); 19 | } 20 | 21 | protected void OnPropertyChangedIndex() { 22 | OnPropertyChanged(IndexerName); 23 | } 24 | 25 | #region Reentrancy 26 | private readonly SimpleMonitor _monitor = new SimpleMonitor(); 27 | 28 | 29 | protected IDisposable BlockReentrancy() { 30 | _monitor.Enter(); 31 | return _monitor; 32 | } 33 | 34 | //TODO: Add Monitor for Collection Changed and Dictionary Changed 35 | protected void CheckReentrancy() { 36 | if (!_monitor.Busy) return; 37 | //if ((CollectionChanged == null) || (CollectionChanged.GetInvocationList().Length <= 1)) return; 38 | //throw new InvalidOperationException("ObservableCollectionReentrancyNotAllowed"); 39 | } 40 | 41 | private class SimpleMonitor : IDisposable { 42 | int _busyCount; 43 | public void Enter() => ++_busyCount; 44 | public void Dispose() => --_busyCount; 45 | public bool Busy => _busyCount > 0; 46 | } 47 | 48 | #endregion 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Extended/INotifyPropertySyncChanged.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Gstc.Collections.Observable.Extended { 4 | public interface INotifyPropertySyncChanged : INotifyPropertyChanged { 5 | void OnPropertyChanged(object sender, PropertyChangedEventArgs args); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Extended/ObservableDictionaryCollection.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable.Base; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | 5 | namespace Gstc.Collections.Observable.Extended { 6 | 7 | 8 | /// 9 | /// An observable Dictionary with a backing list and observable Icollection. 10 | /// 11 | /// 12 | /// 13 | public class ObservableDictionaryCollection : BaseObservableDictionaryCollection { 14 | 15 | private IDictionary _dictionary; 16 | private Collection _collection = new Collection(); 17 | 18 | protected override ICollection InternalCollection => _collection; 19 | protected override IDictionary InternalDictionary => _dictionary; 20 | 21 | //Constructors 22 | public ObservableDictionaryCollection() { 23 | _dictionary = new Dictionary(); 24 | } 25 | 26 | public ObservableDictionaryCollection(Dictionary dictionary) { 27 | Dictionary = _dictionary; 28 | } 29 | 30 | //Properties 31 | public IDictionary Dictionary { 32 | get => _dictionary; 33 | set { 34 | _dictionary = value; 35 | _collection.Clear(); 36 | 37 | var list = new List(); 38 | foreach (var item in Dictionary) { 39 | list.Add(item.Value); 40 | _collection.Add(item.Value); 41 | } 42 | OnDictionaryReset(); 43 | OnCollectionChangedReset(); 44 | OnPropertyChangedCountAndIndex(); 45 | } 46 | } 47 | 48 | //Overrides 49 | public override TValue this[TKey key] { 50 | get => Dictionary[key]; 51 | set { 52 | var item = value; 53 | if (_dictionary.ContainsKey(key)) { 54 | var oldItem = _dictionary[key]; 55 | var index = _collection.IndexOf(item); 56 | _dictionary[key] = item; 57 | _collection[index] = item; 58 | OnPropertyChangedIndex(); 59 | OnCollectionChangedReplace(oldItem, item, index); 60 | OnDictionaryReplace(key, oldItem, item); 61 | } else { 62 | _dictionary[key] = item; 63 | _collection.Add(item); 64 | OnPropertyChangedCountAndIndex(); 65 | OnCollectionChangedAdd(item, _collection.IndexOf(item)); 66 | OnDictionaryChangedAdd(key, item); 67 | } 68 | } 69 | } 70 | 71 | public override void Add(TKey key, TValue value) { 72 | _dictionary.Add(key, value); 73 | _collection.Add(value); 74 | OnPropertyChangedCountAndIndex(); 75 | OnCollectionChangedAdd(value, _collection.IndexOf(value)); 76 | OnDictionaryChangedAdd(key, value); 77 | } 78 | 79 | public override bool Remove(TKey key) { 80 | if (!_dictionary.ContainsKey(key)) return false; 81 | var item = _dictionary[key]; 82 | 83 | var index = _collection.IndexOf(item); 84 | _collection.RemoveAt(index); 85 | _dictionary.Remove(key); 86 | 87 | OnPropertyChangedCountAndIndex(); 88 | OnCollectionChangedRemove(item, index); 89 | OnDictionaryRemove(key, item); 90 | 91 | return true; 92 | } 93 | 94 | public override void Clear() { 95 | _dictionary.Clear(); 96 | _collection.Clear(); 97 | OnCollectionChangedReset(); 98 | OnDictionaryReset(); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Extended/ObservableListAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Specialized; 3 | using System.ComponentModel; 4 | 5 | namespace Gstc.Collections.Observable.Extended { 6 | /// 7 | /// The Observable list adapter is a one-way synchronizer between a source observable list of type 8 | /// TInput and a destination observable list of TOutput. The method Convert(...) must be implemented 9 | /// as an adapter between the source element and destination element. 10 | /// 11 | /// This class is intended to serve as a one-way map between a collection of models and a collection of 12 | /// ViewModels though can be used for arbitrary purpose. It is recommened the destination element should 13 | /// contain a reference to the source element and propogate changes to the destination element via 14 | /// INotifyPropertyChanged or direct mapping to the source. 15 | /// 16 | /// Source element type (e.g. Model class) 17 | /// Destination element type (e.g. ViewModel class 18 | public abstract class ObservableListAdapter : ObservableList { 19 | 20 | /// 21 | /// Method for converting an item of type TInput to TOutput. 22 | /// 23 | /// The source TInput Item. 24 | /// An output item type. 25 | public abstract TOutput Convert(TInput item); 26 | 27 | /// 28 | /// Method for converting an item of type TOuput back to its TInput type. 29 | /// 30 | /// The ouput item to be converted back to its original type. 31 | /// The source item type. 32 | public abstract TInput Convert(TOutput item); 33 | 34 | private IObservableCollection _sourceCollection; 35 | 36 | protected ObservableListAdapter() { } 37 | 38 | /// 39 | /// Initializes a ObservableListAdapter with a collection implementing IObservable collection. 40 | /// 41 | /// 42 | protected ObservableListAdapter(IObservableCollection sourceCollection) { 43 | if (sourceCollection == null) throw new ArgumentNullException("BaseCollection can not be null"); 44 | SourceCollection = sourceCollection; 45 | } 46 | 47 | /// 48 | /// A collection that implements IObservableCollection that the Adapter can watch. 49 | /// 50 | public IObservableCollection SourceCollection { 51 | get => _sourceCollection; 52 | set { 53 | if (_sourceCollection != null) _sourceCollection.CollectionChanged -= SourceCollectionChanged; 54 | _sourceCollection = value; 55 | if (_sourceCollection == null) return; 56 | 57 | SourceCollection.CollectionChanged += SourceCollectionChanged; 58 | 59 | Clear(); 60 | foreach (var element in _sourceCollection) { 61 | var item = Convert(element); 62 | Add(item); 63 | Notifier(item); 64 | } 65 | } 66 | } 67 | 68 | public void Notifier(TOutput item) { 69 | var notifyPropertyChanged = item as INotifyPropertyChanged; 70 | if (notifyPropertyChanged != null) 71 | notifyPropertyChanged.PropertyChanged += 72 | (sender, args) => { 73 | OnCollectionChangedReplace(item, item, IndexOf(item)); 74 | Console.WriteLine(args.PropertyName); 75 | }; 76 | } 77 | 78 | 79 | //TODO: Add an optional dispatcher method to execute update code on a UI thread. 80 | public void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) { 81 | switch (args.Action) { 82 | case NotifyCollectionChangedAction.Add: 83 | for (var i = 0; i < args.NewItems.Count; i++) { 84 | var item = Convert((TInput)args.NewItems[i]); 85 | Insert(args.NewStartingIndex + i, item); 86 | Notifier(item); 87 | } 88 | 89 | break; 90 | case NotifyCollectionChangedAction.Remove: 91 | for (var i = 0; i < args.OldItems.Count; i++) RemoveAt(args.OldStartingIndex + i); 92 | break; 93 | case NotifyCollectionChangedAction.Replace: 94 | for (var i = 0; i < args.NewItems.Count; i++) { 95 | var item = Convert((TInput)args.NewItems[i]); 96 | this[args.OldStartingIndex + i] = item; 97 | Notifier(item); 98 | } 99 | 100 | break; 101 | case NotifyCollectionChangedAction.Move: 102 | for (var i = 0; i < args.OldItems.Count; i++) 103 | Move(args.OldStartingIndex + i, args.NewStartingIndex + i); 104 | break; 105 | case NotifyCollectionChangedAction.Reset: 106 | Clear(); 107 | foreach (var element in SourceCollection) { 108 | var item = Convert(element); 109 | Add(item); 110 | Notifier(item); 111 | } 112 | 113 | break; 114 | default: 115 | throw new ArgumentOutOfRangeException(); 116 | } 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Extended/ObservableListAdapterFunc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Gstc.Collections.Observable.Extended { 4 | 5 | /// 6 | /// This class is an implementation of the abstract class ObservableListAdapter with the 7 | /// Convert method defined at instantiation using an anonymous function in the constructor. 8 | /// 9 | /// For Details see the ObservableListAdapter class. 10 | /// 11 | /// Source element type (e.g. Model class) 12 | /// Destination element type (e.g. ViewModel class 13 | public class ObservableListAdapterFunc : ObservableListAdapter { 14 | 15 | private Func _convert; 16 | 17 | private Func _convertBack; 18 | 19 | 20 | public ObservableListAdapterFunc(Func convert, Func convertBack) { 21 | _convert = convert; 22 | _convertBack = convertBack; 23 | } 24 | 25 | public ObservableListAdapterFunc(IObservableCollection sourceCollection, Func convert, Func convertBack) : base(sourceCollection) { 26 | _convert = convert; 27 | _convertBack = convertBack; 28 | } 29 | 30 | public override TOutput Convert(TInput item) => _convert(item); 31 | 32 | public override TInput Convert(TOutput item) => _convertBack(item); 33 | 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Extended/ObservableListKeyed.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable.Base; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | namespace Gstc.Collections.Observable.Extended { 7 | 8 | 9 | /// 10 | /// This class is an Observable List wherein items are accessed via index using a Key defined in the 11 | /// object, functioning somewhat similar to a Keyed Collection. Standard integer indexing can be used 12 | /// using the exposed List property. 13 | /// 14 | /// This class can be used to wrap a standard list via constructor. 15 | /// 16 | /// The class implements the INotifyCollectionChanged and INotifyDictionaryChanged providing notification 17 | /// on the change of objects. 18 | /// 19 | /// The class contains a backing List, wherein elements can be accessed via index, and a backing dictionary 20 | /// providing O(1) index access times. The dictionary key is defined by the GetKey method and created 21 | /// when added to the list. Care should be taken when updating the key property in the element, as it 22 | /// will not propogate to the dictionary. 23 | /// 24 | /// 25 | /// 26 | /// 27 | public abstract class ObservableListKeyed : BaseObservableListDictionary { 28 | 29 | //Backing list, and dictionary to store keyValuePairs for fast lookup 30 | private List _list; 31 | private readonly Dictionary _dictionary = new Dictionary(); 32 | 33 | protected override IList InternalList => _list; 34 | protected override IDictionary InternalDictionary => _dictionary; 35 | 36 | 37 | //Constructors 38 | protected ObservableListKeyed() { _list = new List(); } 39 | 40 | protected ObservableListKeyed(List list) { List = list; } 41 | 42 | //Properties 43 | 44 | /// 45 | /// This is the map between your object and its internal key. It must be overridden, or alternatively use the ObservableKeyedListFunc class, 46 | /// and supply it with a Delegate. 47 | /// 48 | /// 49 | /// 50 | public abstract TKey GetKey(TValue item); 51 | 52 | 53 | /// 54 | /// Sets the internal list the ListKeyed is bound too. 55 | /// 56 | public List List { 57 | get => _list; 58 | set { 59 | _list = value; 60 | _dictionary.Clear(); 61 | foreach (var element in _list) _dictionary.Add(GetKey(element), element); 62 | OnPropertyChangedCountAndIndex(); 63 | OnCollectionChangedReset(); 64 | OnDictionaryReset(); 65 | } 66 | } 67 | 68 | /// 69 | /// Returns the internal dictionary that is used to track keys on the list. 70 | /// 71 | public Dictionary Dictionary => _dictionary; 72 | 73 | public TValue GetOrDefault(TKey key) { 74 | _dictionary.TryGetValue(key, out var item); 75 | return item; 76 | } 77 | 78 | //Override 79 | public override TValue this[int index] { 80 | get => InternalList[index]; 81 | set { 82 | TValue item = value; 83 | TKey key = GetKey(value); 84 | if (_dictionary.ContainsKey(key)) { 85 | TValue oldItem = _dictionary[key]; 86 | _dictionary[key] = item; 87 | _list[index] = item; 88 | OnPropertyChangedIndex(); 89 | OnCollectionChangedReplace(oldItem, item, index); 90 | OnDictionaryReplace(key, oldItem, item); 91 | } else { 92 | //TODO: Fix for cases of adding vs. replacing. Index or IndexAndProperty, change replaced or added 93 | 94 | _dictionary[key] = item; 95 | _list[index] = item; 96 | OnPropertyChangedIndex(); 97 | OnCollectionChangedAdd(item, _list.IndexOf(item)); 98 | OnDictionaryChangedAdd(key, item); 99 | } 100 | } 101 | } 102 | 103 | public override TValue this[TKey key] { 104 | get => _dictionary[key]; 105 | set { 106 | if (!_dictionary.ContainsKey(key)) Add(key, value); 107 | else { 108 | var item = _dictionary[key]; 109 | var index = _list.IndexOf(item); 110 | if (!Equals(key, GetKey(value))) throw new ArgumentException("Explicit Key must match Item Key."); 111 | _dictionary[key] = value; 112 | _list[index] = value; 113 | 114 | OnPropertyChangedIndex(); 115 | OnDictionaryReplace(key, item, value); 116 | OnCollectionChangedReplace(item, value, index); 117 | } 118 | } 119 | } 120 | 121 | public override void Add(TValue item) { 122 | if (_dictionary.ContainsKey(GetKey(item))) throw new ArgumentException("Duplicate Keyed items are not allowed."); 123 | _dictionary.Add(GetKey(item), item); 124 | _list.Add(item); 125 | 126 | OnPropertyChangedCountAndIndex(); 127 | OnCollectionChangedAdd(item, _list.IndexOf(item)); 128 | OnDictionaryChangedAdd(GetKey(item), item); 129 | } 130 | 131 | public void AddRange(IList items) { 132 | var count = _list.Count; 133 | _list.AddRange(items); 134 | OnPropertyChangedCountAndIndex(); 135 | OnCollectionChangedAddMany((IList)items, count); 136 | } 137 | 138 | public override void Add(TKey key, TValue item) { 139 | if (!Equals(key, GetKey(item))) throw new ArgumentException("Explicit Key must match Item Key."); 140 | if (_dictionary.ContainsKey(key)) throw new ArgumentException("Duplicate Keyed items are not allowed."); 141 | _dictionary.Add(GetKey(item), item); 142 | _list.Add(item); 143 | 144 | OnPropertyChangedCountAndIndex(); 145 | OnCollectionChangedAdd(item, _list.IndexOf(item)); 146 | OnDictionaryChangedAdd(key, item); 147 | } 148 | 149 | public override void Clear() { 150 | _dictionary.Clear(); 151 | _list.Clear(); 152 | OnPropertyChangedCountAndIndex(); 153 | OnCollectionChangedReset(); 154 | OnDictionaryReset(); 155 | } 156 | 157 | public override void Insert(int index, TValue item) { 158 | var key = GetKey(item); 159 | if (_dictionary.ContainsKey(key)) throw new ArgumentException("Duplicate Keyed items are not allowed."); 160 | _dictionary.Add(key, item); 161 | _list.Insert(index, item); 162 | 163 | OnPropertyChangedCountAndIndex(); 164 | OnCollectionChangedAdd(item, index); 165 | OnDictionaryChangedAdd(key, item); 166 | } 167 | 168 | public override void Move(int oldIndex, int newIndex) { 169 | var removedItem = _list[oldIndex]; 170 | 171 | _list.RemoveAt(oldIndex); 172 | _list.Insert(newIndex, removedItem); 173 | 174 | OnPropertyChangedIndex(); 175 | OnCollectionChangedMove(removedItem, newIndex, oldIndex); 176 | } 177 | 178 | public override bool Remove(TKey key) { 179 | 180 | var item = _dictionary[key]; 181 | if (item == null) return false; 182 | _dictionary.Remove(key); 183 | var index = _list.IndexOf(item); 184 | _list.RemoveAt(index); 185 | 186 | OnPropertyChangedCountAndIndex(); 187 | OnCollectionChangedRemove(item, index); 188 | OnDictionaryRemove(key, item); 189 | return true; 190 | } 191 | 192 | public override bool Remove(TValue item) { 193 | var index = _list.IndexOf(item); 194 | 195 | if (index == -1) return false; 196 | var key = GetKey(item); 197 | _list.RemoveAt(index); 198 | _dictionary.Remove(key); 199 | 200 | OnPropertyChangedCountAndIndex(); 201 | OnCollectionChangedRemove(item, index); 202 | OnDictionaryRemove(key, item); 203 | return true; 204 | } 205 | 206 | public override void RemoveAt(int index) { 207 | if (index >= Count) return; 208 | var item = List[index]; 209 | var key = GetKey(item); 210 | _list.RemoveAt(index); 211 | _dictionary.Remove(key); 212 | OnPropertyChangedCountAndIndex(); 213 | OnCollectionChangedRemove(item, index); 214 | OnDictionaryRemove(key, item); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Extended/ObservableListKeyedFunc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Gstc.Collections.Observable.Extended { 5 | /// 6 | /// This class is an implementation of ListKeyed that defines its GetKey method 7 | /// via anonymous function at runtime. 8 | /// 9 | /// See the ListKeyed for more details. 10 | /// 11 | /// 12 | /// 13 | public class ObservableListKeyedFunc : ObservableListKeyed { 14 | 15 | public Func GetKeyFunc; 16 | 17 | public ObservableListKeyedFunc(Func getKeyFunc) { GetKeyFunc = getKeyFunc; } 18 | 19 | public ObservableListKeyedFunc(Func getKeyFunc, List list) : base(list) { 20 | GetKeyFunc = getKeyFunc; 21 | } 22 | 23 | public override TKey GetKey(TValue item) => GetKeyFunc(item); 24 | 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Extended/ObservableListSyncFunc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Gstc.Collections.Observable.Extended { 5 | 6 | /// 7 | /// This class is an implementation of the abstract class ObservableListAdapter with the 8 | /// Convert method defined at instantiation using an anonymous function in the constructor. 9 | /// 10 | /// For Details see the ObservableListAdapter class. 11 | /// 12 | /// Source element type (e.g. Model class) 13 | /// Destination element type (e.g. ViewModel class 14 | public class ObservableListSyncFunc : ObservableListSync { 15 | 16 | 17 | /// 18 | /// Generates an ObservableList of type TOutput that is synchronized to a list of TInput. 19 | /// This method wraps a conventional List{TInput} in an ObservableList{TInput}, then generates an 20 | /// an ObservableList{TOutput}. The ObservableList{TInput} is accessible via SourceCollection property. 21 | /// Added or removed items are propagated back to the original list. 22 | /// It is recommended that TOutput is a passthrough map to TInput. 23 | /// 24 | /// A list to make observable and convert to type TOutput. 25 | /// Conversion method from TInput to TOutput 26 | /// Conversion method from TOutput to TInput 27 | /// 28 | public static ObservableListSync FromList(List list, Func convert, Func convertBack) { 29 | var obvList = new ObservableList(list); 30 | return new ObservableListSyncFunc(obvList, convert, convertBack); 31 | } 32 | 33 | private readonly Func _convert; 34 | private readonly Func _convertBack; 35 | 36 | 37 | /// 38 | /// Creates a Observable list that automatically synchronizes between items of type TInput to TOutput. 39 | /// An input list of type ObservableList should be provided after initialization. 40 | /// 41 | /// 42 | /// 43 | public ObservableListSyncFunc(Func convert, Func convertBack) : base() { 44 | _convert = convert; 45 | _convertBack = convertBack; 46 | } 47 | 48 | /// 49 | /// Creates a Observable list that automatically synchronizes Observable lists between items of type TInput to TOutput. 50 | /// 51 | /// 52 | /// 53 | /// 54 | public ObservableListSyncFunc(ObservableList sourceObservableList, Func convert, Func convertBack) : base(sourceObservableList) { 55 | _convert = convert; 56 | _convertBack = convertBack; 57 | } 58 | 59 | /// 60 | /// Converts item of type TInput to TOutput. 61 | /// 62 | /// 63 | /// 64 | public override TOutput Convert(TInput item) => _convert(item); 65 | 66 | /// 67 | /// Converts item of type TOutput to TInput. 68 | /// 69 | /// 70 | /// 71 | public override TInput ConvertBack(TOutput item) => _convertBack(item); 72 | 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Extended/PropertySyncNotifier.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel; 3 | 4 | namespace Gstc.Collections.Observable.Extended { 5 | public class PropertySyncNotifier { 6 | 7 | public List LastArgs = new List(); 8 | public INotifyPropertySyncChanged SourceSync { get; set; } 9 | public INotifyPropertySyncChanged DestSync { get; set; } 10 | 11 | public INotifyPropertyChanged SourceNotify { get; set; } 12 | public INotifyPropertyChanged DestNotify { get; set; } 13 | public PropertySyncNotifier(INotifyPropertyChanged sourceItem, INotifyPropertyChanged destItem) { 14 | SourceNotify = sourceItem; 15 | DestNotify = destItem; 16 | 17 | SourceSync = sourceItem as INotifyPropertySyncChanged; 18 | DestSync = destItem as INotifyPropertySyncChanged; 19 | 20 | if (sourceItem == null && destItem == null) throw new System.ArgumentException("One of the objects must implement INotifyPropertySyncChanged."); ; 21 | 22 | if (DestSync != null) SourceNotify.PropertyChanged += DestTrigger; 23 | if (SourceSync != null) DestNotify.PropertyChanged += SourceTrigger; 24 | } 25 | 26 | public void DestTrigger(object sender, PropertyChangedEventArgs args) { 27 | if (LastArgs.Contains(args)) { LastArgs.Remove(args); return; } //Allows concurrant execution. 28 | LastArgs.Add(args); 29 | DestSync.OnPropertyChanged(sender, args); 30 | } 31 | 32 | public void SourceTrigger(object sender, PropertyChangedEventArgs args) { 33 | if (LastArgs.Contains(args)) { LastArgs.Remove(args); return; } //Allows concurrant execution. 34 | LastArgs.Add(args); 35 | SourceSync.OnPropertyChanged(sender, args); 36 | } 37 | 38 | /* 39 | private int updateCount = 0; 40 | 41 | public void DestTrigger(object sender, PropertyChangedEventArgs args) { 42 | if (updateCount++ == 3) { updateCount = 0; return; } 43 | DestSync.OnPropertyChanged(sender,args); 44 | } 45 | 46 | public void SourceTrigger(object sender, PropertyChangedEventArgs args) { 47 | if (updateCount++ == 3) { updateCount = 0; return; } 48 | SourceSync.OnPropertyChanged(sender, args); 49 | } 50 | */ 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Gstc.Collections.Observable.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8E22A176-7AE7-4F41-8FAE-EDC8A0ADD7A8} 8 | Library 9 | Properties 10 | Gstc.Collections.Observable 11 | Gstc.Collections.Observable 12 | v4.8 13 | 512 14 | 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | false 35 | bin\Release\Gstc.Collections.Observable.xml 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Designer 83 | PreserveNewest 84 | 85 | 86 | 87 | 88 | 95 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Gstc.Collections.Observable.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gstc.Collections.Observable 5 | 0.1.4-alpha 6 | Extended Observable Collection Library 7 | Greg Sonnenfeld 8 | Greg Sonnenfeld 9 | https://github.com/gsonnenf/ExtendedObservableCollection/blob/master/LICENSE 10 | https://github.com/gsonnenf/ExtendedObservableCollection 11 | false 12 | This library provides comprehensive observable Collection/List/Dictionary. Interfaces (Icollection, IList, etc) are also implemented and trigger INotifyCollectionChanged. The library includes utility collections such as a ObservableListAdapter which can syncronize two lists of different, but related types. 13 | Initial alpha pre-release. I've made very comprehensive unit tests to validate this library. It still needs community review. 14 | Greg Sonnenfeld Copyright 2019 15 | ObservableCollection Observable ObservableList Collections 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/IObservableCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.ComponentModel; 5 | 6 | namespace Gstc.Collections.Observable { 7 | public interface IObservableCollection : 8 | ICollection, 9 | INotifyCollectionChanged, 10 | INotifyPropertyChanged { 11 | new int Count { get; } 12 | } 13 | 14 | public interface IObservableCollection : 15 | ICollection, 16 | IObservableCollection { 17 | new int Count { get; } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/IObservableDictionary.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable.Base; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Gstc.Collections.Observable { 6 | public interface IObservableDictionary : 7 | IDictionary, 8 | IDictionary, 9 | INotifyDictionaryChanged { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/IObservableDictionaryCollection.cs: -------------------------------------------------------------------------------- 1 | namespace Gstc.Collections.Observable { 2 | public interface IObservableDictionaryCollection : 3 | IObservableCollection, 4 | IObservableDictionary { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/IObservableList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace Gstc.Collections.Observable { 5 | 6 | /// 7 | /// A List that triggers INotifyCollectionChanged and INotifyPropertyChanged when list changes. 8 | /// 9 | /// 10 | public interface IObservableList : 11 | IObservableCollection, 12 | IList, 13 | IList { 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Misc/CollectionViewAdapter.cs: -------------------------------------------------------------------------------- 1 | /* 2 | using System; 3 | using System.Collections; 4 | using System.Collections.ObjectModel; 5 | using System.Collections.Specialized; 6 | using System.ComponentModel; 7 | using System.Globalization; 8 | 9 | namespace Gstc.Collections.Observable { 10 | public abstract class CollectionViewAdapter : ICollectionView where T1 : class where T2 : class { 11 | 12 | public event NotifyCollectionChangedEventHandler CollectionChanged; 13 | public event CurrentChangingEventHandler CurrentChanging; 14 | public event EventHandler CurrentChanged; 15 | 16 | private ICollectionView DefaultCollectionView; 17 | 18 | protected CollectionViewAdapter(ObservableCollection sourceCollection ) { 19 | //SourceCollection = sourceCollection; 20 | //ObservableCollection = sourceCollection; 21 | //DefaultCollectionView = new CollectionView(sourceCollection); 22 | } 23 | 24 | protected CollectionViewAdapter(ICollectionView collectionView) { 25 | 26 | //SourceCollection = sourceCollection; 27 | //ObservableCollection = sourceCollection; 28 | DefaultCollectionView = collectionView; 29 | DefaultCollectionView.CurrentChanged += CurrentChanged; 30 | DefaultCollectionView.CollectionChanged += CollectionChanged; 31 | DefaultCollectionView.CurrentChanging += CurrentChanging; 32 | } 33 | 34 | public IEnumerable Collection { get; set; } 35 | 36 | // private ObservableCollection ObservableCollection { get; set; } 37 | public IEnumerable SourceCollection => DefaultCollectionView.SourceCollection; 38 | public IEnumerator GetEnumerator() => DefaultCollectionView.GetEnumerator();//new EnumeratorAdapter(SourceCollection.GetEnumerator(), Convert); 39 | 40 | public abstract T2 Convert(T1 item); 41 | public abstract T1 Convert(T2 item); 42 | 43 | #region Passthrough mappings 44 | 45 | public CultureInfo Culture { 46 | get { return DefaultCollectionView.Culture; } 47 | set { DefaultCollectionView.Culture = value; } 48 | } 49 | 50 | public Predicate Filter { 51 | get { return DefaultCollectionView.Filter; } 52 | set { DefaultCollectionView.Filter = value; } 53 | } 54 | 55 | 56 | public bool CanFilter => DefaultCollectionView.CanFilter; 57 | public SortDescriptionCollection SortDescriptions => DefaultCollectionView.SortDescriptions; 58 | public bool CanSort => DefaultCollectionView.CanSort; 59 | public bool CanGroup => DefaultCollectionView.CanSort; 60 | public ObservableCollection GroupDescriptions => DefaultCollectionView.GroupDescriptions; 61 | public ReadOnlyObservableCollection Groups => DefaultCollectionView.Groups; 62 | public bool IsEmpty => DefaultCollectionView.IsEmpty; 63 | public object CurrentItem => DefaultCollectionView.CurrentItem; 64 | public int CurrentPosition => DefaultCollectionView.CurrentPosition; 65 | public bool IsCurrentAfterLast => DefaultCollectionView.IsCurrentAfterLast; 66 | public bool IsCurrentBeforeFirst => DefaultCollectionView.IsCurrentBeforeFirst; 67 | public bool Contains(object item) => DefaultCollectionView.Contains(item); 68 | public void Refresh() => DefaultCollectionView.Refresh(); 69 | public IDisposable DeferRefresh() => DefaultCollectionView.DeferRefresh(); 70 | public bool MoveCurrentToFirst() => DefaultCollectionView.MoveCurrentToFirst(); 71 | public bool MoveCurrentToLast() => DefaultCollectionView.MoveCurrentToLast(); 72 | public bool MoveCurrentToNext() => DefaultCollectionView.MoveCurrentToNext(); 73 | public bool MoveCurrentToPrevious() => DefaultCollectionView.MoveCurrentToPrevious(); 74 | public bool MoveCurrentTo(object item) => DefaultCollectionView.MoveCurrentTo(item); 75 | public bool MoveCurrentToPosition(int position) => DefaultCollectionView.MoveCurrentToPosition(position); 76 | 77 | #endregion 78 | 79 | } 80 | } 81 | */ -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Misc/EnumeratorAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Gstc.Collections.Observable.misc { 6 | 7 | /// 8 | /// Enumerator that exposes model item type (T1) as model view item type (T2). 9 | /// 10 | /// 11 | /// 12 | public class EnumeratorAdapter : IEnumerator where T2 : class where T1 : class { 13 | 14 | private readonly IEnumerator _enumerator; 15 | public Func Adapt; 16 | 17 | public EnumeratorAdapter(IEnumerator enumerator, Func adapt) { 18 | _enumerator = enumerator; 19 | Adapt = adapt; 20 | } 21 | public T2 Current => Adapt?.Invoke(_enumerator.Current as T1); 22 | public bool MoveNext() => _enumerator.MoveNext(); 23 | public void Reset() => _enumerator.Reset(); 24 | object IEnumerator.Current => Current; 25 | } 26 | 27 | /// 28 | /// Enumerator that exposes model item type (T1) as model view item type (T2). 29 | /// 30 | /// Input Type 31 | /// Output type 32 | public class EnumeratorAdapterTyped : IEnumerator where T1 : class where T2 : class { 33 | 34 | private readonly IEnumerator _enumerator; 35 | public Func Adapt; 36 | 37 | public EnumeratorAdapterTyped(IEnumerator enumerator, Func adapt) { 38 | _enumerator = enumerator; 39 | Adapt = adapt; 40 | } 41 | public T2 Current => Adapt?.Invoke(_enumerator.Current as T1); 42 | public bool MoveNext() => _enumerator.MoveNext(); 43 | public void Reset() => _enumerator.Reset(); 44 | object IEnumerator.Current => Current; 45 | public void Dispose() => _enumerator.Dispose(); 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Misc/KeyedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Gstc.Collections.Observable.misc { 6 | public abstract class KeyedList : IList { 7 | 8 | private List _list; 9 | private Dictionary _dictionary; 10 | public List List { get; set; } 11 | public Dictionary Dictionary => _dictionary; 12 | 13 | public abstract TKey GetKey(TValue item); 14 | 15 | public void Clear() { 16 | _dictionary.Clear(); 17 | _list.Clear(); 18 | } 19 | 20 | public void Add(TValue item) { 21 | if (_dictionary.ContainsKey(GetKey(item))) throw new ArgumentException("Duplicate Keyed items are not allowed."); 22 | _dictionary.Add(GetKey(item), item); 23 | _list.Add(item); 24 | } 25 | 26 | public void Add(TKey key, TValue item) { 27 | if (!ValidateKey(key, item)) throw new ArgumentException("Explicit Key must match Item Key."); 28 | if (_dictionary.ContainsKey(key)) throw new ArgumentException("Duplicate Keyed items are not allowed."); 29 | _dictionary.Add(GetKey(item), item); 30 | _list.Add(item); 31 | } 32 | 33 | public void Insert(int index, TValue item) { 34 | var key = GetKey(item); 35 | if (_dictionary.ContainsKey(key)) throw new ArgumentException("Duplicate Keyed items are not allowed."); 36 | _dictionary.Add(key, item); 37 | _list.Insert(index, item); 38 | } 39 | 40 | public bool Remove(TKey key) { 41 | var item = _dictionary[key]; 42 | if (item == null) return false; 43 | _dictionary.Remove(key); 44 | _list.Remove(item); 45 | return true; 46 | } 47 | 48 | public bool Remove(TValue item) { 49 | if (!_list.Remove(item)) return false; 50 | _dictionary.Remove(GetKey(item)); 51 | return true; 52 | } 53 | 54 | public void RemoveAt(int index) { 55 | var item = _list[index]; 56 | var key = GetKey(item); 57 | _list.RemoveAt(index); 58 | _dictionary.Remove(key); 59 | } 60 | 61 | public TValue this[TKey key] { 62 | get { return _dictionary[key]; } 63 | set { 64 | if (!ValidateKey(key, value)) throw new ArgumentException("Explicit key must Match Item."); 65 | if (_dictionary.ContainsKey(key)) { 66 | var oldItem = _dictionary[key]; 67 | var index = _list.IndexOf(oldItem); 68 | _dictionary[key] = value; 69 | _list[index] = value; 70 | } else { 71 | _list.Add(value); 72 | _dictionary.Add(GetKey(value), value); 73 | } 74 | } 75 | } 76 | 77 | // Pass through functions 78 | public bool Contains(TValue item) => _list.Contains(item); 79 | 80 | public bool Contains(TKey key) => _dictionary.ContainsKey(key); 81 | 82 | public void CopyTo(TValue[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); 83 | 84 | public int IndexOf(TValue item) => _list.IndexOf(item); 85 | 86 | public int Count => _list.Count; 87 | public bool IsReadOnly => ((IList)_list).IsReadOnly; 88 | 89 | TValue IList.this[int index] { 90 | get { return _list[index]; } 91 | set { 92 | var oldKey = GetKey(_list[index]); 93 | this[oldKey] = value; 94 | } 95 | } 96 | 97 | public IEnumerator GetEnumerator() => _list.GetEnumerator(); 98 | 99 | IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); 100 | 101 | public bool ValidateKey(TKey key, TValue item) => (Equals(key, GetKey(item))); 102 | 103 | 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Misc/ObservableCollectionSync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Collections.Specialized; 4 | 5 | namespace Gstc.Collections.Observable.misc { 6 | 7 | public class ObservableCollectionSync : ObservableCollection where T1 : class where T2 : class { 8 | public ObservableCollection SourceCollection { 9 | get { return _sourceCollection; } 10 | set { 11 | _sourceCollection = value; 12 | OnNewCollection(); 13 | } 14 | } 15 | 16 | private readonly Converter _convert; 17 | private Converter _convertBack; 18 | private ObservableCollection _sourceCollection; 19 | 20 | public ObservableCollectionSync(Converter convert, Converter convertBack, ObservableCollection sourceCollection = null) { 21 | _convert = convert; 22 | _convertBack = convertBack; 23 | SourceCollection = sourceCollection ?? new ObservableCollection(); 24 | } 25 | 26 | private void OnNewCollection() { 27 | foreach (var item in SourceCollection) Add(_convert(item)); 28 | SourceCollection.CollectionChanged += SourceCollectionOnCollectionChanged; 29 | } 30 | 31 | private void SourceCollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) { 32 | int indexNew; 33 | int indexOld; 34 | switch (args.Action) { 35 | case NotifyCollectionChangedAction.Add: 36 | indexNew = args.NewStartingIndex; 37 | foreach (var item in args.NewItems) InsertItem(indexNew++, _convert(item as T1)); 38 | break; 39 | case NotifyCollectionChangedAction.Remove: 40 | indexOld = args.OldStartingIndex; 41 | foreach (var item in args.OldItems) RemoveAt(indexOld++); 42 | break; 43 | case NotifyCollectionChangedAction.Replace: 44 | indexOld = args.OldStartingIndex; 45 | indexNew = args.NewStartingIndex; 46 | foreach (var item in args.OldItems) RemoveAt(indexOld++); 47 | foreach (var item in args.NewItems) InsertItem(indexNew++, _convert(item as T1)); 48 | break; 49 | case NotifyCollectionChangedAction.Move: 50 | indexOld = args.OldStartingIndex; 51 | indexNew = args.NewStartingIndex; 52 | foreach (var item in args.OldItems) MoveItem(indexOld++, indexNew++); 53 | break; 54 | case NotifyCollectionChangedAction.Reset: 55 | Clear(); 56 | foreach (var item in SourceCollection) Add(_convert(item)); 57 | break; 58 | default: 59 | throw new ArgumentOutOfRangeException(); 60 | } 61 | } 62 | 63 | 64 | } 65 | 66 | } 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/ObservableDictionary.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable.Base; 2 | using System.Collections.Generic; 3 | 4 | namespace Gstc.Collections.Observable { 5 | 6 | /// 7 | /// Observable dictionary can serve as a stand alone dictionary, or serve as an observable wrapper for a pre-existing dictionary. 8 | /// ObservableDictionary implements INotifyPropertyChanged and INotifyDictionaryChanged. It does NOT implement ICollectionChanged. 9 | /// Use an ObservableKeyedCollection or SortedList if you need ICollectionChanged. 10 | /// 11 | /// 12 | /// Key field of Dictionary 13 | /// Value field of Dictionary 14 | public class ObservableDictionary : BaseObservableDictionary { 15 | 16 | private Dictionary _dictionary; 17 | 18 | public ObservableDictionary() { 19 | _dictionary = new Dictionary(); 20 | } 21 | 22 | public ObservableDictionary(Dictionary dictionary) { 23 | Dictionary = dictionary; 24 | } 25 | 26 | public Dictionary Dictionary { 27 | get => _dictionary; 28 | set { 29 | _dictionary = value; 30 | OnPropertyChanged(CountString); 31 | OnPropertyChanged(IndexerName); 32 | OnDictionaryReset(); 33 | } 34 | } 35 | 36 | protected override IDictionary InternalDictionary => Dictionary; 37 | 38 | public override TValue this[TKey key] { 39 | get => _dictionary[key]; 40 | set { 41 | CheckReentrancy(); 42 | if (!ContainsKey(key)) { 43 | Add(key, value); 44 | return; 45 | } 46 | var oldValue = _dictionary[key]; 47 | var newValue = value; 48 | _dictionary[key] = newValue; 49 | OnPropertyChanged(IndexerName); 50 | OnDictionaryReplace(key, oldValue, newValue); 51 | } 52 | } 53 | 54 | public override void Add(TKey key, TValue value) { 55 | CheckReentrancy(); 56 | _dictionary.Add(key, value); 57 | OnPropertyChanged(CountString); 58 | OnPropertyChanged(IndexerName); 59 | OnDictionaryAdd(key, value); 60 | } 61 | 62 | public override void Clear() { 63 | CheckReentrancy(); 64 | _dictionary.Clear(); 65 | OnPropertyChangedCountAndIndex(); 66 | OnDictionaryReset(); 67 | } 68 | 69 | public override bool Remove(TKey key) { 70 | CheckReentrancy(); 71 | var removedItem = _dictionary[key]; 72 | if (!_dictionary.Remove(key)) return false; 73 | OnPropertyChangedCountAndIndex(); 74 | OnDictionaryRemove(key, removedItem); 75 | return true; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/ObservableList.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable.Base; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Gstc.Collections.Observable { 6 | 7 | /// 8 | /// This class is a wrapper for a List that implements IList, INotifyCollectionChanged and contains 9 | /// additional utility functions. It can be instantated on its own, or instantied with an already 10 | /// existing List. In many cases using ObservableList may be preffered over using the .NET ObservableCollection 11 | /// for its compatiblity with existing collection types and interface. 12 | /// 13 | /// 14 | public class ObservableList : BaseObservableList { 15 | 16 | private List _list; 17 | 18 | /// 19 | /// Reference to internal list for use by base classes. Perhaps this should be internal instead of protected. 20 | /// 21 | protected override IList InternalList => _list; 22 | 23 | /// 24 | /// Creates an observable list. The observable list is backed internally by a new .NET List. 25 | /// 26 | public ObservableList() { _list = new List(); } 27 | 28 | /// 29 | /// Creates an observable list using the list supplied in the constructor. Events are only triggered 30 | /// when using the observable list or a downcast version of the observable list. 31 | /// 32 | /// List to wrap with observable list. 33 | public ObservableList(List list) { List = list; } 34 | 35 | #region Properties 36 | /// 37 | /// Gets the current internal list or replaces the current internal list with a new list. A Reset event will be triggered. 38 | /// 39 | public List List { 40 | get => _list; 41 | set { 42 | _list = value; 43 | OnPropertyChangedCountAndIndex(); 44 | OnCollectionChangedReset(); 45 | } 46 | } 47 | 48 | /// 49 | /// Adds a list of items and triggers a single CollectionChanged and Add event. 50 | /// 51 | /// List of items. The default .NET collection changed event args returns an IList, so this is the preferred type. 52 | public void AddRange(IList items) { 53 | var count = _list.Count; 54 | _list.AddRange(items); 55 | OnPropertyChangedCountAndIndex(); 56 | OnCollectionChangedAddMany((IList)items, count); 57 | } 58 | #endregion 59 | 60 | #region overrides 61 | /// 62 | /// Indexes an element of the list. CollectionChanged and Replaced events are triggered on assignment. 63 | /// 64 | /// 65 | /// 66 | public override TItem this[int index] { 67 | get => _list[index]; 68 | set { 69 | var oldItem = _list[index]; 70 | _list[index] = value; 71 | OnPropertyChangedIndex(); 72 | OnCollectionChangedReplace(oldItem, value, index); 73 | } 74 | } 75 | 76 | /// 77 | /// Adds an item to the list. CollectionChanged and Added event are triggered. 78 | /// 79 | /// Item to add 80 | public override void Add(TItem item) { 81 | _list.Add(item); 82 | OnPropertyChangedCountAndIndex(); 83 | OnCollectionChangedAdd(item, _list.IndexOf(item)); 84 | } 85 | 86 | 87 | /// 88 | /// Clears all item from the list. CollectionChanged and Reset event are triggered. 89 | /// 90 | public override void Clear() { 91 | _list.Clear(); 92 | OnPropertyChangedCountAndIndex(); 93 | OnCollectionChangedReset(); 94 | } 95 | 96 | /// 97 | /// Inserts an item at a specific index. CollectionChanged and Added event are triggered. 98 | /// 99 | /// 100 | /// 101 | public override void Insert(int index, TItem item) { 102 | _list.Insert(index, item); 103 | OnPropertyChangedCountAndIndex(); 104 | OnCollectionChangedAdd(item, index); 105 | } 106 | 107 | /// 108 | /// Moves an item to a new index. CollectionChanged and Moved event are triggered. 109 | /// 110 | /// 111 | /// 112 | public override void Move(int oldIndex, int newIndex) { 113 | var removedItem = this[oldIndex]; 114 | _list.RemoveAt(oldIndex); 115 | _list.Insert(newIndex, removedItem); 116 | OnPropertyChangedIndex(); 117 | OnCollectionChangedMove(removedItem, oldIndex, newIndex); 118 | } 119 | 120 | /// 121 | /// Searches for the specified object and removes the first occurance if it exists. CollectionChanged and Moved events are triggered. 122 | /// 123 | /// Item to remove. 124 | /// Returns true if item was found and removed. Returns false if item does not exist. 125 | public override bool Remove(TItem item) { 126 | var index = _list.IndexOf(item); 127 | if (index == -1) return false; 128 | _list.RemoveAt(index); 129 | OnPropertyChangedCountAndIndex(); 130 | OnCollectionChangedRemove(item, index); 131 | return true; 132 | } 133 | 134 | /// 135 | /// Removes item at specific index. CollectionChanged and Removed events are triggered. 136 | /// 137 | /// 138 | public override void RemoveAt(int index) { 139 | var item = _list[index]; 140 | _list.RemoveAt(index); 141 | OnPropertyChangedCountAndIndex(); 142 | OnCollectionChangedRemove(item, index); 143 | } 144 | #endregion 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/ObservableSortedList.cs: -------------------------------------------------------------------------------- 1 | using Gstc.Collections.Observable.Base; 2 | using System.Collections.Generic; 3 | 4 | namespace Gstc.Collections.Observable { 5 | 6 | /// 7 | /// This class is a wrapper for a sorted list that implements INotifyCollectionChanged and INotifyDictionaryChanged. 8 | /// 9 | /// 10 | /// 11 | public class ObservableSortedList : BaseObservableSortedList { 12 | 13 | private SortedList _sortedList; 14 | 15 | protected override SortedList InternalSortedList => _sortedList; 16 | 17 | public SortedList SortedList { 18 | get => _sortedList; 19 | set { 20 | _sortedList = value; 21 | OnPropertyChangedCountAndIndex(); 22 | OnCollectionChangedReset(); 23 | OnDictionaryReset(); 24 | } 25 | } 26 | 27 | //Constructors 28 | public ObservableSortedList() { _sortedList = new SortedList(); } 29 | 30 | public ObservableSortedList(SortedList sortedList) { SortedList = sortedList; } 31 | 32 | // Overrides 33 | public override TValue this[TKey key] { 34 | get => _sortedList[key]; 35 | set { 36 | if (ContainsKey(key)) { 37 | //CheckReentrancy(); 38 | var oldValue = _sortedList[key]; 39 | _sortedList[key] = value; 40 | OnPropertyChangedIndex(); 41 | OnDictionaryReplace(key, oldValue, value); 42 | OnCollectionChangedReplace(oldValue, value, _sortedList.IndexOfKey(key)); 43 | } else Add(key, value); 44 | } 45 | } 46 | 47 | public override void Add(TKey key, TValue value) { 48 | //CheckReentrancy(); 49 | _sortedList.Add(key, value); 50 | OnPropertyChangedCountAndIndex(); 51 | OnCollectionChangedAdd(value, _sortedList.IndexOfKey(key)); 52 | OnDictionaryChangedAdd(key, value); 53 | } 54 | 55 | public override void Clear() { 56 | //CheckReentrancy(); 57 | _sortedList.Clear(); 58 | OnPropertyChangedCountAndIndex(); 59 | OnCollectionChangedReset(); 60 | OnDictionaryReset(); 61 | } 62 | 63 | public override bool Remove(TKey key) { 64 | //CheckReentrancy(); 65 | var removedIndex = _sortedList.IndexOfKey(key); 66 | var removedValue = _sortedList[key]; 67 | if (!_sortedList.Remove(key)) return false; 68 | OnPropertyChangedCountAndIndex(); 69 | OnCollectionChangedRemove(removedValue, removedIndex); 70 | OnDictionaryRemove(key, removedValue); 71 | 72 | return true; 73 | } 74 | 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("Gstc.Collections.Observable")] 8 | [assembly: AssemblyDescription("An extended observable collection package for .NET")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Greg Sonnenfeld Technical Consulting")] 11 | [assembly: AssemblyProduct("Gstc.Collections.Observable")] 12 | [assembly: AssemblyCopyright("Greg Sonnenfeld Copyright © 2018")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("9b12917b-3860-474a-8a9f-30f89c2ceae8")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("0.1.0.1")] 35 | [assembly: AssemblyFileVersion("0.1.0.1")] 36 | -------------------------------------------------------------------------------- /ExtendedObservableCollection/Gstc.Collections.Observable/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repo is no longer maintained. This ObservableList and list binding from this library has been moved to: 2 | 3 | https://github.com/gsonnenf/Gstc.Collections.ObservableLists 4 | 5 | Gstc.Collections.ObservableLists is a stable release with much better code structure, documentation, bug fixes, better support for fringe cases and very extensive unit testing. The ObservableDictionary portion of this library has been ported to Gstc.ObsevableDictionary, but is still being tested to ensure reliable operation. 6 | 7 | 8 | # ExtendedObservableCollection 9 |

10 | 11 |

12 | 13 | ### New Version 14 | Observable List and List Synchronizers are being updated here: 15 | https://github.com/gsonnenf/Gstc.Collections.ObservableLists 16 | 17 | That section of the code has been moved and is being updated in that repo. The Dictionary Methods and other uncommon lists will eventually be moved to their own library as well. 18 | 19 | ### What is it? 20 | 21 | In short this library is an implementation of INotifyCollectionChanged, INotifyPropertyChanged combine with IList, ICollection, IDictionary,etc that can serve as wrappers for your non-observable collections. Events are trigger even when downcast. This library also contains various useful utilities for syncronizing lists. 22 | 23 | ### How do I get started? 24 | 25 | The standard items should work somewhat similar to a .NET ObservableCollection. First, add the nuget package or add a reference to your compiled code. The following example shows usage of an ObservableList<>: 26 | 27 | ```csharp 28 | var myObvList = new ObservableList(); 29 | myObvList.CollectionChanged += (sender, args) => Console.Writeline("Collection has changed!"); 30 | myObvList.Added += (sender, args) => Console.Writeline("First item in NewItems is: " + args.NewItems[0]); 31 | myObvList.Add("I am the first item."); 32 | 33 | //works with downcasting 34 | IList myIList = myObvList as IList; 35 | myIList.Add("I am a second item added to a downcast IList."); 36 | 37 | //Output: 38 | // Collection has changed! 39 | // First item in NewItems is: I am the first item. 40 | // First item in NewItems is: I am a second item added to a downcast IList. 41 | ``` 42 | 43 | It can also be used with existing lists: 44 | 45 | ```csharp 46 | var myList = new List() { "one","two","three" }; 47 | 48 | //Wrapping a list 49 | var myObvList = new ObservableList(); 50 | myObvList.CollectionChanged += (sender, args) => Console.Writeline("Collection has changed!"); 51 | myObvList.Reset += (sender, args) => Console.Writeline("Collection has been reset!"); 52 | myObvList.List = myList; 53 | 54 | //Events after wrapping a list 55 | myObvList.Added += ()=> Console.Writeline("Item added: "); 56 | 57 | myObvList.Add("I will trigger an event!"); 58 | myList.Add("I will not trigger an event. It may be better to copy me into an observable list if this will happen."); 59 | 60 | //Output: 61 | // Collection has changed! 62 | // Collection has been reset! 63 | // Item added: I will trigger an event! 64 | 65 | ``` 66 | 67 | # Longer Summary 68 | 69 | This library contains a set of classes for making your Lists, Dictionaries and other Collections observable. The Observable Collections contained in this library can wrap existing collections or can be used on their own, generating their own backing collection. The library attempts to implement all interfaces of its backing collections and generate events for interface method calls when downcast. The library has a comprehensive unit test which tests the ObservableCollection and its interfaces. 70 | 71 | This library also includes utility classes including a synchronization observable collection which can synchronize an observable list of viewmodels to a source list of models. 72 | 73 | ## OBSERVABLES 74 | 75 | The observable collections in this library implement INotifyCollectionChanged and INotifyPropertyChanged. The INotifyCollectionChanged implementation generates NotifyCollectionChangedEventArgs that fully implements the NotifyCollectionChangedAction and supports multiple Adds in a single event. Example libraries, such as ParallelExtensionsExtras, often take a shortcut and only generate NotifyCollectionChangedAction.Reset events. 76 | 77 | The observable dictionaries in this library implement a custom INotifyDictionaryChanged. INotifyDictionaryChanged is templated after NotifyCollectionChanged, but is used for Dictionary instead of Collection<>. Consequently, NotifyDictionaryChanged uses Keys instead of Indexes in its event args. Otherwise, the operations are analogous. 78 | 79 | 80 | ## STANDARD COLLECTION CLASSES 81 | 82 | ### ObservableList< TItem > 83 | The ObservableList implements INotifyCollectionChanged and INotifyPropertyChanged and is a wrapper for a standard List<>. It triggers NotifyCollectionChanged and NotifyPropertyChanged events for List operation: Add, AddRange, Clear, Insert, Move, Remove, RemoveAt and this[]. It implements all interfaces of List and triggers notify events for interface methods when downcast. 84 | 85 | ### ObservableDictionary< TKey, TValue > 86 | The ObservableDictionary implements a INotifyDictionaryChanged and is a wrapper for a standard Dictionary<,>. It triggers NotifyDictionaryChanged events for the Dictionary operations: Add, Clear, Remove, and this[]. It implements all interfaces of Dictionary and triggers notify events for interface methods when downcast. 87 | 88 | ### ObservableSortedList< TKey, TValue > 89 | The ObservableSortedList implements INotifyCollectionChanged, INotifyPropertyChanged and INotifyDictionaryChanged and is a wrapper for a standard SortedList<,>. It triggers events for Standad SortedList operations. It implements all interfaces of SortedList and triggers notify events for interface methods when downcast. 90 | 91 | ## EXTENDED COLLECTION CLASSES 92 | 93 | ### ObservableListKeyed< TKey, TItem > 94 | The ObservableListKeyed<,> is an ObservableList that allows indexing by a Key that is mapped to a property of the TItem. The mapping can be specified by implementing the abstract GetKey() method. Alternately, one can instantiate ObservableListKeyedFunc<,> with the mapping given as an anonymous function. It implements INotifyCollectionChanged, INotifyPropertyChanged and INotifyDictionaryChanged. 95 | 96 | ### ObservableListAdapter< TInputItem, TOutputItem > 97 | The ObservableListAdapter<,> is an ObservableList that performs a unidirectional syncronization with an SourceCollection of type IObservableCollection. The syncronization is peformed when an IObservableCollection is added via Constructor or changed using the SourceCollection property. The syncronization is maintained via added events. The The mapping can be specified by implementing the abstract "TOutput Convert(TInput item)" method. Alternately, one can instantiate ObservableListAdapterFunc<,> with the mapping given as an anonymous function. Its important to note, changes to items in the SourceCollection will propagate to the ObservableListAdapter, but changes to the ObservableListAdapter will NOT propagate back to the source collection. This option may be added in a future release. 98 | 99 | ### ObservableDictionaryCollection<,> - TBA 100 | 101 | 102 | 103 | 104 | --------------------------------------------------------------------------------