diff --git a/.editorconfig b/.editorconfig index b93421537..434fb0c8e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,24 @@ root = true insert_final_newline = true indent_style = space indent_size = 4 +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion [project.json] indent_size = 2 @@ -54,7 +72,7 @@ dotnet_style_predefined_type_for_member_access = true:suggestion # name all constant fields using PascalCase dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_style.pascal_case_style.capitalization = pascal_case @@ -62,7 +80,7 @@ dotnet_naming_style.pascal_case_style.capitalization = pascal_case # static fields should have s_ prefix dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields -dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected @@ -72,7 +90,7 @@ dotnet_naming_style.static_prefix_style.capitalization = camel_case # internal and private fields should be _camelCase dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields -dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style dotnet_naming_symbols.private_internal_fields.applicable_kinds = field dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal dotnet_naming_style.camel_case_underscore_style.required_prefix = _ @@ -470,7 +488,7 @@ dotnet_diagnostic.SA1200.severity = none dotnet_diagnostic.SA1201.severity = none -dotnet_diagnostic.SA1202.severity = error +dotnet_diagnostic.SA1202.severity = silent dotnet_diagnostic.SA1203.severity = error @@ -676,6 +694,9 @@ dotnet_diagnostic.SX1309.severity = error dotnet_diagnostic.SX1623.severity = none dotnet_diagnostic.SX1309S.severity=silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent # C++ Files [*.{cpp,h,in}] diff --git a/src/Directory.build.targets b/src/Directory.build.targets index 51e7556d3..945cf9815 100644 --- a/src/Directory.build.targets +++ b/src/Directory.build.targets @@ -1,30 +1,33 @@ - - $(AssemblyName) ($(TargetFramework)) - + + $(AssemblyName) ($(TargetFramework)) + - - $(DefineConstants);WINDOWS_UWP;P_LINQ - + + $(DefineConstants);WINDOWS_UWP;P_LINQ + - - $(DefineConstants);P_LINQ;SUPPORTS_BINDINGLIST - + + $(DefineConstants);P_LINQ;SUPPORTS_BINDINGLIST + - - $(DefineConstants);NETSTANDARD;PORTABLE;P_LINQ;SUPPORTS_BINDINGLIST - + + $(DefineConstants);NETSTANDARD;PORTABLE;P_LINQ;SUPPORTS_BINDINGLIST + - - $(DefineConstants);NETCOREAPP;P_LINQ;SUPPORTS_BINDINGLIST - + + $(DefineConstants);NETCOREAPP;P_LINQ;SUPPORTS_BINDINGLIST + - - $(DefineConstants);NETSTANDARD;PORTABLE;P_LINQ;SUPPORTS_BINDINGLIST - + + $(DefineConstants);NETSTANDARD;PORTABLE;P_LINQ;SUPPORTS_BINDINGLIST + - - $(DefineConstants);NETSTANDARD;PORTABLE;P_LINQ;SUPPORTS_BINDINGLIST - + + $(DefineConstants);NETSTANDARD;PORTABLE;P_LINQ;SUPPORTS_BINDINGLIST + + + $(DefineConstants);NETSTANDARD;PORTABLE;P_LINQ;SUPPORTS_BINDINGLIST + diff --git a/src/DynamicData.Tests/Binding/BindingLIstBindListFixture.cs b/src/DynamicData.Tests/Binding/BindingLIstBindListFixture.cs index 86d129d3f..750aaf1ae 100644 --- a/src/DynamicData.Tests/Binding/BindingLIstBindListFixture.cs +++ b/src/DynamicData.Tests/Binding/BindingLIstBindListFixture.cs @@ -26,7 +26,10 @@ public BindingLIstBindListFixture() { _collection = new BindingList(); _source = new SourceList(); - _binder = _source.Connect().Bind(_collection).Subscribe(); + _binder = _source.Connect() + .AutoRefresh(p => p.Age) + .Bind(_collection) + .Subscribe(); } [Fact] @@ -58,12 +61,29 @@ public void Clear() _collection.Count.Should().Be(0, "Should be 100 items in the collection"); } - public void Dispose() + + + [Fact] + public void Refresh() { - _binder.Dispose(); - _source.Dispose(); + var people = _generator.Take(100).ToList(); + _source.AddRange(people); + + ListChangedEventArgs? args = null; + + _collection.ListChanged += (_, e) => + { + args = e; + }; + + people[10].Age = 100; + + args.Should().NotBeNull(); + args.ListChangedType.Should().Be(ListChangedType.ItemChanged); + args.NewIndex.Should().Be(10); } + [Fact] public void RemoveSourceRemovesFromTheDestination() { @@ -85,6 +105,13 @@ public void UpdateToSourceUpdatesTheDestination() _collection.Count.Should().Be(1, "Should be 1 item in the collection"); _collection.First().Should().Be(personUpdated, "Should be updated person"); } + + public void Dispose() + { + _binder.Dispose(); + _source.Dispose(); + } + } } -#endif \ No newline at end of file +#endif diff --git a/src/DynamicData.Tests/Binding/BindingListBindCacheFixture.cs b/src/DynamicData.Tests/Binding/BindingListBindCacheFixture.cs index e45c55516..f76ff68bf 100644 --- a/src/DynamicData.Tests/Binding/BindingListBindCacheFixture.cs +++ b/src/DynamicData.Tests/Binding/BindingListBindCacheFixture.cs @@ -57,11 +57,6 @@ public void BatchRemove() _collection.Count.Should().Be(0, "Should be 100 items in the collection"); } - public void Dispose() - { - _binder.Dispose(); - _source.Dispose(); - } [Fact] public void RemoveSourceRemovesFromTheDestination() @@ -73,6 +68,26 @@ public void RemoveSourceRemovesFromTheDestination() _collection.Count.Should().Be(0, "Should be 1 item in the collection"); } + [Fact] + public void Refresh() + { + var people = _generator.Take(100).ToList(); + _source.AddOrUpdate(people); + + ListChangedEventArgs? args = null; + + _collection.ListChanged += (_, e) => + { + args = e; + }; + + _source.Refresh(people[10]); + + args.Should().NotBeNull(); + args.ListChangedType.Should().Be(ListChangedType.ItemChanged); + args.NewIndex.Should().Be(10); + } + [Fact] public void UpdateToSourceUpdatesTheDestination() { @@ -84,6 +99,12 @@ public void UpdateToSourceUpdatesTheDestination() _collection.Count.Should().Be(1, "Should be 1 item in the collection"); _collection.First().Should().Be(personUpdated, "Should be updated person"); } + + public void Dispose() + { + _binder.Dispose(); + _source.Dispose(); + } } } -#endif \ No newline at end of file +#endif diff --git a/src/DynamicData.Tests/Binding/BindingListBindCacheSortedFixture.cs b/src/DynamicData.Tests/Binding/BindingListBindCacheSortedFixture.cs index db2080220..38fc9c2c1 100644 --- a/src/DynamicData.Tests/Binding/BindingListBindCacheSortedFixture.cs +++ b/src/DynamicData.Tests/Binding/BindingListBindCacheSortedFixture.cs @@ -70,11 +70,6 @@ public void CollectionIsInSortOrder() sorted.Should().BeEquivalentTo(_collection.ToList()); } - public void Dispose() - { - _binder.Dispose(); - _source.Dispose(); - } [Fact] public void LargeUpdateInvokesAReset() @@ -93,6 +88,28 @@ public void LargeUpdateInvokesAReset() invoked.Should().BeTrue(); } + [Fact] + public void Refresh() + { + var people = _generator.Take(100).ToList(); + _source.AddOrUpdate(people); + + ListChangedEventArgs? args = null; + + _collection.ListChanged += (_, e) => + { + args = e; + }; + + _source.Refresh(people[10]); + + args.Should().NotBeNull(); + args.ListChangedType.Should().Be(ListChangedType.ItemChanged); + + _collection[args.NewIndex].Should().Be(people[10]); + } + + [Fact] public void RemoveSourceRemovesFromTheDestination() { @@ -179,6 +196,15 @@ public void UpdateToSourceUpdatesTheDestination() _collection.Count.Should().Be(1, "Should be 1 item in the collection"); _collection.First().Should().Be(personUpdated, "Should be updated person"); } + + + public void Dispose() + { + _binder.Dispose(); + _source.Dispose(); + } } + + } -#endif \ No newline at end of file +#endif diff --git a/src/DynamicData.Tests/Binding/BindingListToChangeSetFixture.cs b/src/DynamicData.Tests/Binding/BindingListToChangeSetFixture.cs index d0c9d0638..d1e9ff861 100644 --- a/src/DynamicData.Tests/Binding/BindingListToChangeSetFixture.cs +++ b/src/DynamicData.Tests/Binding/BindingListToChangeSetFixture.cs @@ -80,8 +80,12 @@ public void RefreshCausesReplace() sourceCacheResults.Messages.First().Adds.Should().Be(1); sourceCacheResults.Messages.Last().Refreshes.Should().Be(1); - // List receives add and replace instead of refresh - collectionResults.Messages.Count.Should().Be(2); + + /* + List receives add and replace instead of refresh (and as of 23/02/2023 it receives a refresh too!) + */ + + collectionResults.Messages.Count.Should().Be(3); collectionResults.Messages.First().Adds.Should().Be(1); collectionResults.Messages.First().Refreshes.Should().Be(0); collectionResults.Messages.Last().Replaced.Should().Be(1); diff --git a/src/DynamicData/Binding/BindingListAdaptor.cs b/src/DynamicData/Binding/BindingListAdaptor.cs index eb000a74c..2775af261 100644 --- a/src/DynamicData/Binding/BindingListAdaptor.cs +++ b/src/DynamicData/Binding/BindingListAdaptor.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for full license information. #if SUPPORTS_BINDINGLIST -using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; @@ -129,10 +128,18 @@ private static void DoUpdate(IChangeSet changes, BindingList public static class BindingListEx { + internal static void Clone(this BindingList source, IEnumerable> changes) + { + // ** Copied from ListEx for binding list specific changes + if (source is null) throw new ArgumentNullException(nameof(source)); + if (changes is null) throw new ArgumentNullException(nameof(changes)); + + foreach (var item in changes) + { + source.Clone(item, EqualityComparer.Default); + } + } + + private static void Clone(this BindingList source, Change item, IEqualityComparer equalityComparer) + { + switch (item.Reason) + { + case ListChangeReason.Add: + { + var change = item.Item; + var hasIndex = change.CurrentIndex >= 0; + if (hasIndex) + { + source.Insert(change.CurrentIndex, change.Current); + } + else + { + source.Add(change.Current); + } + + break; + } + + case ListChangeReason.AddRange: + { + source.AddOrInsertRange(item.Range, item.Range.Index); + break; + } + + case ListChangeReason.Clear: + { + source.ClearOrRemoveMany(item); + break; + } + + case ListChangeReason.Replace: + { + var change = item.Item; + if (change.CurrentIndex >= 0 && change.CurrentIndex == change.PreviousIndex) + { + source[change.CurrentIndex] = change.Current; + } + else + { + if (change.PreviousIndex == -1) + { + source.Remove(change.Previous.Value); + } + else + { + // is this best? or replace + move? + source.RemoveAt(change.PreviousIndex); + } + + if (change.CurrentIndex == -1) + { + source.Add(change.Current); + } + else + { + source.Insert(change.CurrentIndex, change.Current); + } + } + + break; + } + + case ListChangeReason.Refresh: + { + var index = source.IndexOf(item.Item.Current); + if (index != -1) + source.ResetItem(index); + break; + } + + case ListChangeReason.Remove: + { + var change = item.Item; + bool hasIndex = change.CurrentIndex >= 0; + if (hasIndex) + { + source.RemoveAt(change.CurrentIndex); + } + else + { + int index = source.IndexOf(change.Current, equalityComparer); + if (index > -1) + { + source.RemoveAt(index); + } + } + + break; + } + + case ListChangeReason.RemoveRange: + { + source.RemoveMany(item.Range); + break; + } + + case ListChangeReason.Moved: + { + var change = item.Item; + bool hasIndex = change.CurrentIndex >= 0; + if (!hasIndex) + { + throw new UnspecifiedIndexException("Cannot move as an index was not specified"); + } + + if (source is IExtendedList extendedList) + { + extendedList.Move(change.PreviousIndex, change.CurrentIndex); + } + else if (source is ObservableCollection observableCollection) + { + observableCollection.Move(change.PreviousIndex, change.CurrentIndex); + } + else + { + // check this works whatever the index is + source.RemoveAt(change.PreviousIndex); + source.Insert(change.CurrentIndex, change.Current); + } + + break; + } + } + } + /// /// Observes list changed args. /// diff --git a/src/DynamicData/Binding/SortedBindingListAdaptor.cs b/src/DynamicData/Binding/SortedBindingListAdaptor.cs index 38e194e3a..2e38f479c 100644 --- a/src/DynamicData/Binding/SortedBindingListAdaptor.cs +++ b/src/DynamicData/Binding/SortedBindingListAdaptor.cs @@ -4,6 +4,7 @@ #if SUPPORTS_BINDINGLIST using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -102,10 +103,18 @@ private void DoUpdate(ISortedChangeSet changes) _list.RemoveAt(change.PreviousIndex); _list.Insert(change.CurrentIndex, change.Current); break; + + case ChangeReason.Refresh: + { + var index = _list.IndexOf(change.Current); + if (index != -1) + _list.ResetItem(index); + break; + } } } } } } -#endif \ No newline at end of file +#endif diff --git a/version.json b/version.json index 35f8c733e..8c9976ef7 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "7.12", + "version": "7.13", "publicReleaseRefSpec": [ "^refs/heads/main$", // we release out of master "^refs/heads/preview/.*", // we release previews