Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit ea40c9a

Browse files
committed
添加项目文件。
1 parent a2317a0 commit ea40c9a

File tree

4 files changed

+352
-0
lines changed

4 files changed

+352
-0
lines changed

WpfObservableRangeCollection.sln

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.6.33829.357
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfObservableRangeCollection", "WpfObservableRangeCollection\WpfObservableRangeCollection.csproj", "{2B00D450-FA66-4F4E-A6A1-D6A89AEBB7FB}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{2B00D450-FA66-4F4E-A6A1-D6A89AEBB7FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{2B00D450-FA66-4F4E-A6A1-D6A89AEBB7FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{2B00D450-FA66-4F4E-A6A1-D6A89AEBB7FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{2B00D450-FA66-4F4E-A6A1-D6A89AEBB7FB}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {F88B1A45-A94E-482E-A093-033E287CEE69}
24+
EndGlobalSection
25+
EndGlobal
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Collections.Specialized;
5+
using System.ComponentModel;
6+
using System.Linq;
7+
8+
namespace CodingNinja.Wpf.ObjectModel;
9+
10+
/// <summary>
11+
/// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
12+
/// <para>
13+
/// Forked from <see href="https://github.com/xamarin/XamarinCommunityToolkit/blob/main/src/CommunityToolkit/Xamarin.CommunityToolkit/ObjectModel/ObservableRangeCollection.shared.cs">XamarinCommunityToolkit/src/CommunityToolkit/Xamarin.CommunityToolkit/ObjectModel/ObservableRangeCollection.shared.cs</see>
14+
/// </para>
15+
/// <para>
16+
/// Instead, it is merged from <see href="https://github.com/jamesmontemagno/mvvm-helpers/blob/master/MvvmHelpers/ObservableRangeCollection.cs">mvvm-helpers/MvvmHelpers/ObservableRangeCollection.cs</see>
17+
/// </para>
18+
/// </summary>
19+
/// <typeparam name="T"></typeparam>
20+
public class ObservableRangeCollection<T> : ObservableCollection<T>
21+
{
22+
/// <summary>
23+
/// Initializes a new instance of the <seealso cref="ObservableCollection{T}"/> class.
24+
/// </summary>
25+
public ObservableRangeCollection() : base()
26+
{ }
27+
28+
/// <summary>
29+
/// Initializes a new instance of the <seealso cref="ObservableCollection{T}"/> class that contains elements copied from the specified collection.
30+
/// </summary>
31+
/// <param name="collection">collection: The collection from which the elements are copied.</param>
32+
/// <exception cref="ArgumentNullException">The collection parameter cannot be null.</exception>
33+
public ObservableRangeCollection(IEnumerable<T> collection) : base(collection)
34+
{ }
35+
36+
/// <summary>
37+
/// Adds the elements of the specified collection to the end of the <seealso cref="ObservableCollection{T}"/>.
38+
/// </summary>
39+
public void AddRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Add)
40+
{
41+
if (notificationMode is not NotifyCollectionChangedAction.Add and not NotifyCollectionChangedAction.Reset)
42+
{
43+
throw new ArgumentException("Mode must be either Add or Reset for AddRange.", nameof(notificationMode));
44+
}
45+
46+
if (collection.TryGetNonEnumeratedCount(out int count) && count == 1)
47+
{
48+
Add(collection.First());
49+
50+
return;
51+
}
52+
53+
var changedItems = collection is List<T> list ? list : new List<T>(collection);
54+
55+
if (changedItems.Count == 1)
56+
{
57+
Add(changedItems[0]);
58+
59+
return;
60+
}
61+
62+
CheckReentrancy();
63+
64+
int startIndex = Count;
65+
66+
bool itemsAdded = AddArrangeCore(collection);
67+
68+
if (!itemsAdded)
69+
{
70+
return;
71+
}
72+
73+
if (notificationMode == NotifyCollectionChangedAction.Reset)
74+
{
75+
RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset);
76+
77+
return;
78+
}
79+
80+
RaiseChangeNotificationEvents(
81+
action: NotifyCollectionChangedAction.Add,
82+
changedItems: changedItems,
83+
startingIndex: startIndex);
84+
}
85+
86+
/// <summary>
87+
/// Removes the first occurence of each item in the specified collection from <seealso cref="ObservableCollection{T}"/>.
88+
/// <para>
89+
/// NOTE: with notificationMode = Remove, removed items starting index is not set because items are not guaranteed to be consecutive.
90+
/// </para>
91+
/// </summary>
92+
public void RemoveRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Reset)
93+
{
94+
if (notificationMode is not NotifyCollectionChangedAction.Remove and not NotifyCollectionChangedAction.Reset)
95+
{
96+
throw new ArgumentException("Mode must be either Remove or Reset for RemoveRange.", nameof(notificationMode));
97+
}
98+
99+
CheckReentrancy();
100+
101+
if (notificationMode == NotifyCollectionChangedAction.Reset)
102+
{
103+
bool raiseEvents = false;
104+
105+
foreach (var item in collection)
106+
{
107+
Items.Remove(item);
108+
raiseEvents = true;
109+
}
110+
111+
if (raiseEvents)
112+
{
113+
RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset);
114+
}
115+
116+
return;
117+
}
118+
119+
var changedItems = new List<T>(collection);
120+
121+
for (int i = 0; i < changedItems.Count; i++)
122+
{
123+
if (!Items.Remove(changedItems[i]))
124+
{
125+
changedItems.RemoveAt(i); // Can't use a foreach because changedItems is intended to be (carefully) modified
126+
i--;
127+
}
128+
}
129+
130+
if (changedItems.Count == 0)
131+
{
132+
return;
133+
}
134+
135+
RaiseChangeNotificationEvents(
136+
action: NotifyCollectionChangedAction.Remove,
137+
changedItems: changedItems);
138+
}
139+
140+
/// <summary>
141+
/// Clears the current collection and replaces it with the specified item.
142+
/// </summary>
143+
public void Replace(T item)
144+
{
145+
ReplaceRange(new T[] { item });
146+
}
147+
148+
/// <summary>
149+
/// Clears the current collection and replaces it with the specified collection.
150+
/// </summary>
151+
public void ReplaceRange(IEnumerable<T> collection)
152+
{
153+
CheckReentrancy();
154+
155+
bool previouslyEmpty = Items.Count == 0;
156+
157+
Items.Clear();
158+
159+
AddArrangeCore(collection);
160+
161+
bool currentlyEmpty = Items.Count == 0;
162+
163+
if (previouslyEmpty && currentlyEmpty)
164+
{
165+
return;
166+
}
167+
168+
RaiseChangeNotificationEvents(action: NotifyCollectionChangedAction.Reset);
169+
}
170+
171+
private bool AddArrangeCore(IEnumerable<T> collection)
172+
{
173+
bool itemAdded = false;
174+
175+
foreach (var item in collection)
176+
{
177+
Items.Add(item);
178+
itemAdded = true;
179+
}
180+
181+
return itemAdded;
182+
}
183+
184+
private void RaiseChangeNotificationEvents(NotifyCollectionChangedAction action, List<T>? changedItems = null, int startingIndex = -1)
185+
{
186+
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
187+
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
188+
189+
if (changedItems == null)
190+
{
191+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action));
192+
}
193+
else
194+
{
195+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, changedItems: changedItems, startingIndex: startingIndex));
196+
}
197+
}
198+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Collections.Specialized;
5+
using System.Linq;
6+
using System.Reflection;
7+
using System.Windows.Data;
8+
9+
namespace CodingNinja.Wpf.ObjectModel;
10+
11+
/// <summary>
12+
/// Forked from <see href="https://gist.github.com/weitzhandler/65ac9113e31d12e697cb58cd92601091#file-wpfobservablerangecollection-cs"/>
13+
/// <para>see <see href="https://stackoverflow.com/a/670579/4380178"/></para>
14+
/// <para>If the <seealso cref="NotSupportedException"/> still occurred, try using <seealso cref="BindingOperations.EnableCollectionSynchronization"/>.</para>
15+
/// </summary>
16+
/// <typeparam name="T"></typeparam>
17+
public class WpfObservableRangeCollection<T> : ObservableRangeCollection<T>
18+
{
19+
private class DeferredEventsCollection : List<NotifyCollectionChangedEventArgs>, IDisposable
20+
{
21+
private readonly WpfObservableRangeCollection<T> _collection;
22+
23+
public DeferredEventsCollection(WpfObservableRangeCollection<T> collection)
24+
{
25+
_collection = collection;
26+
_collection._deferredEvents = this;
27+
}
28+
29+
public void Dispose()
30+
{
31+
_collection._deferredEvents = null;
32+
33+
var handlers = _collection
34+
.GetHandlers()
35+
.ToLookup(h => h.Target is CollectionView);
36+
37+
foreach (var handler in handlers[false])
38+
{
39+
foreach (var e in this)
40+
{
41+
handler(_collection, e);
42+
}
43+
}
44+
45+
foreach (var cv in handlers[true]
46+
.Select(h => h.Target)
47+
.Cast<CollectionView>()
48+
.Distinct())
49+
{
50+
cv.Refresh();
51+
}
52+
}
53+
}
54+
55+
private DeferredEventsCollection? _deferredEvents;
56+
57+
public WpfObservableRangeCollection()
58+
{ }
59+
60+
public WpfObservableRangeCollection(IEnumerable<T> collection) : base(collection)
61+
{ }
62+
63+
public WpfObservableRangeCollection(List<T> list) : base(list)
64+
{ }
65+
66+
/// <summary>
67+
/// Raise CollectionChanged event to any listeners.
68+
/// Properties/methods modifying this ObservableCollection will raise
69+
/// a collection changed event through this virtual method.
70+
/// </summary>
71+
/// <remarks>
72+
/// When overriding this method, either call its base implementation
73+
/// or call <seealso cref="ObservableCollection{T}.BlockReentrancy"/> to guard against reentrant collection changes.
74+
/// </remarks>
75+
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
76+
{
77+
if (typeof(ObservableRangeCollection<T>).GetField(nameof(_deferredEvents), BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(this) is ICollection<NotifyCollectionChangedEventArgs> deferredEvents)
78+
{
79+
deferredEvents.Add(e);
80+
81+
return;
82+
}
83+
84+
foreach (var handler in GetHandlers())
85+
{
86+
if (WpfObservableRangeCollection<T>.IsRange(e) && handler.Target is CollectionView cv)
87+
{
88+
cv.Refresh();
89+
}
90+
else
91+
{
92+
handler(this, e);
93+
}
94+
}
95+
}
96+
97+
private static bool IsRange(NotifyCollectionChangedEventArgs e)
98+
{
99+
return e.NewItems?.Count > 1 || e.OldItems?.Count > 1;
100+
}
101+
102+
private IEnumerable<NotifyCollectionChangedEventHandler> GetHandlers()
103+
{
104+
var info = typeof(ObservableCollection<T>).GetField(nameof(CollectionChanged), BindingFlags.Instance | BindingFlags.NonPublic);
105+
var @event = info?.GetValue(this) as MulticastDelegate;
106+
107+
return @event?.GetInvocationList()
108+
.Cast<NotifyCollectionChangedEventHandler>()
109+
.Distinct()
110+
?? Enumerable.Empty<NotifyCollectionChangedEventHandler>();
111+
}
112+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0-windows</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<UseWPF>true</UseWPF>
7+
<RootNamespace>CodingNinja.Wpf.ObjectModel</RootNamespace>
8+
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
9+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
10+
<Description>Provides ObservableRangeCollection and its WPF version, including AddRange, RemoveRange, Replace/ReplaceRange methods for bulk operation, but only update the notification once.</Description>
11+
<PackageProjectUrl>https://github.com/CodingOctocat/WpfObservableRangeCollection</PackageProjectUrl>
12+
<Authors>CodingNinja</Authors>
13+
<Title>$(AssemblyName)</Title>
14+
<PackageTags>WpfObservableRangeCollection;ObservableRangeCollection;ObservableCollection;CollectionView;Wpf</PackageTags>
15+
</PropertyGroup>
16+
17+
</Project>

0 commit comments

Comments
 (0)