-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathobservablecollection.cs
427 lines (365 loc) · 14.8 KB
/
observablecollection.cs
1
2
3
4
5
6
7
8
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
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
//---------------------------------------------------------------------------
//
// <copyright file="ObservableCollection.cs" company="Microsoft">
// Copyright (C) 2003 by Microsoft Corporation. All rights reserved.
// </copyright>
//
//
// Description: Implementation of an Collection<T> implementing INotifyCollectionChanged
// to notify listeners of dynamic changes of the list.
//
// See spec at http://avalon/connecteddata/Specs/Collection%20Interfaces.mht
//
// History:
// 11/22/2004 : Microsoft - created
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace System.Collections.ObjectModel
{
/// <summary>
/// Implementation of a dynamic data collection based on generic Collection<T>,
/// implementing INotifyCollectionChanged to notify listeners
/// when items get added, removed or the whole list is refreshed.
/// </summary>
#if !FEATURE_NETCORE
[Serializable()]
[TypeForwardedFrom("WindowsBase, Version=3.0.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
#endif
public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
/// <summary>
/// Initializes a new instance of ObservableCollection that is empty and has default initial capacity.
/// </summary>
public ObservableCollection() : base() { }
/// <summary>
/// Initializes a new instance of the ObservableCollection class
/// that contains elements copied from the specified list
/// </summary>
/// <param name="list">The list whose elements are copied to the new list.</param>
/// <remarks>
/// The elements are copied onto the ObservableCollection in the
/// same order they are read by the enumerator of the list.
/// </remarks>
/// <exception cref="ArgumentNullException"> list is a null reference </exception>
public ObservableCollection(List<T> list)
: base((list != null) ? new List<T>(list.Count) : list)
{
// Workaround for VSWhidbey bug 562681 (tracked by Windows bug 1369339).
// We should be able to simply call the base(list) ctor. But Collection<T>
// doesn't copy the list (contrary to the documentation) - it uses the
// list directly as its storage. So we do the copying here.
//
CopyFrom(list);
}
/// <summary>
/// Initializes a new instance of the ObservableCollection class that contains
/// elements copied from the specified collection and has sufficient capacity
/// to accommodate the number of elements copied.
/// </summary>
/// <param name="collection">The collection whose elements are copied to the new list.</param>
/// <remarks>
/// The elements are copied onto the ObservableCollection in the
/// same order they are read by the enumerator of the collection.
/// </remarks>
/// <exception cref="ArgumentNullException"> collection is a null reference </exception>
public ObservableCollection(IEnumerable<T> collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
CopyFrom(collection);
}
private void CopyFrom(IEnumerable<T> collection)
{
IList<T> items = Items;
if (collection != null && items != null)
{
using (IEnumerator<T> enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
items.Add(enumerator.Current);
}
}
}
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
#region Public Methods
/// <summary>
/// Move item at oldIndex to newIndex.
/// </summary>
public void Move(int oldIndex, int newIndex)
{
MoveItem(oldIndex, newIndex);
}
#endregion Public Methods
//------------------------------------------------------
//
// Public Events
//
//------------------------------------------------------
#region Public Events
//------------------------------------------------------
#region INotifyPropertyChanged implementation
/// <summary>
/// PropertyChanged event (per <see cref="INotifyPropertyChanged" />).
/// </summary>
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add
{
PropertyChanged += value;
}
remove
{
PropertyChanged -= value;
}
}
#endregion INotifyPropertyChanged implementation
//------------------------------------------------------
/// <summary>
/// Occurs when the collection changes, either by adding or removing an item.
/// </summary>
/// <remarks>
/// see <seealso cref="INotifyCollectionChanged"/>
/// </remarks>
#if !FEATURE_NETCORE
[field:NonSerializedAttribute()]
#endif
public virtual event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion Public Events
//------------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
#region Protected Methods
/// <summary>
/// Called by base class Collection<T> when the list is being cleared;
/// raises a CollectionChanged event to any listeners.
/// </summary>
protected override void ClearItems()
{
CheckReentrancy();
base.ClearItems();
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnCollectionReset();
}
/// <summary>
/// Called by base class Collection<T> when an item is removed from list;
/// raises a CollectionChanged event to any listeners.
/// </summary>
protected override void RemoveItem(int index)
{
CheckReentrancy();
T removedItem = this[index];
base.RemoveItem(index);
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItem, index);
}
/// <summary>
/// Called by base class Collection<T> when an item is added to list;
/// raises a CollectionChanged event to any listeners.
/// </summary>
protected override void InsertItem(int index, T item)
{
CheckReentrancy();
base.InsertItem(index, item);
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
}
/// <summary>
/// Called by base class Collection<T> when an item is set in list;
/// raises a CollectionChanged event to any listeners.
/// </summary>
protected override void SetItem(int index, T item)
{
CheckReentrancy();
T originalItem = this[index];
base.SetItem(index, item);
OnPropertyChanged(IndexerName);
OnCollectionChanged(NotifyCollectionChangedAction.Replace, originalItem, item, index);
}
/// <summary>
/// Called by base class ObservableCollection<T> when an item is to be moved within the list;
/// raises a CollectionChanged event to any listeners.
/// </summary>
protected virtual void MoveItem(int oldIndex, int newIndex)
{
CheckReentrancy();
T removedItem = this[oldIndex];
base.RemoveItem(oldIndex);
base.InsertItem(newIndex, removedItem);
OnPropertyChanged(IndexerName);
OnCollectionChanged(NotifyCollectionChangedAction.Move, removedItem, newIndex, oldIndex);
}
/// <summary>
/// Raises a PropertyChanged event (per <see cref="INotifyPropertyChanged" />).
/// </summary>
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
/// <summary>
/// PropertyChanged event (per <see cref="INotifyPropertyChanged" />).
/// </summary>
#if !FEATURE_NETCORE
[field:NonSerializedAttribute()]
#endif
protected virtual event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise CollectionChanged event to any listeners.
/// Properties/methods modifying this ObservableCollection will raise
/// a collection changed event through this virtual method.
/// </summary>
/// <remarks>
/// When overriding this method, either call its base implementation
/// or call <see cref="BlockReentrancy"/> to guard against reentrant collection changes.
/// </remarks>
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
{
using (BlockReentrancy())
{
CollectionChanged(this, e);
}
}
}
/// <summary>
/// Disallow reentrant attempts to change this collection. E.g. a event handler
/// of the CollectionChanged event is not allowed to make changes to this collection.
/// </summary>
/// <remarks>
/// typical usage is to wrap e.g. a OnCollectionChanged call with a using() scope:
/// <code>
/// using (BlockReentrancy())
/// {
/// CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, item, index));
/// }
/// </code>
/// </remarks>
protected IDisposable BlockReentrancy()
{
_monitor.Enter();
return _monitor;
}
/// <summary> Check and assert for reentrant attempts to change this collection. </summary>
/// <exception cref="InvalidOperationException"> raised when changing the collection
/// while another collection change is still being notified to other listeners </exception>
protected void CheckReentrancy()
{
if (_monitor.Busy)
{
// we can allow changes if there's only one listener - the problem
// only arises if reentrant changes make the original event args
// invalid for later listeners. This keeps existing code working
// (e.g. Selector.SelectedItems).
if ((CollectionChanged != null) && (CollectionChanged.GetInvocationList().Length > 1))
throw new InvalidOperationException(SR.GetString(SR.ObservableCollectionReentrancyNotAllowed));
}
}
#endregion Protected Methods
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
/// <summary>
/// Helper to raise a PropertyChanged event />).
/// </summary>
private void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Helper to raise CollectionChanged event to any listeners
/// </summary>
private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
}
/// <summary>
/// Helper to raise CollectionChanged event to any listeners
/// </summary>
private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index, int oldIndex)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index, oldIndex));
}
/// <summary>
/// Helper to raise CollectionChanged event to any listeners
/// </summary>
private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
}
/// <summary>
/// Helper to raise CollectionChanged event with action == Reset to any listeners
/// </summary>
private void OnCollectionReset()
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Types
//
//------------------------------------------------------
#region Private Types
// this class helps prevent reentrant calls
#if !FEATURE_NETCORE
[Serializable()]
[TypeForwardedFrom("WindowsBase, Version=3.0.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
#endif
private class SimpleMonitor : IDisposable
{
public void Enter()
{
++ _busyCount;
}
public void Dispose()
{
-- _busyCount;
}
public bool Busy { get { return _busyCount > 0; } }
int _busyCount;
}
#endregion Private Types
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private const string CountString = "Count";
// This must agree with Binding.IndexerName. It is declared separately
// here so as to avoid a dependency on PresentationFramework.dll.
private const string IndexerName = "Item[]";
private SimpleMonitor _monitor = new SimpleMonitor();
#endregion Private Fields
}
}