Skip to content

Commit c32cccb

Browse files
committed
Merge remote-tracking branch 'origin/winforms-derivedbinginglist'
2 parents 8f1f6e5 + fb7d76f commit c32cccb

6 files changed

Lines changed: 294 additions & 129 deletions

File tree

ReactiveUI.Platforms/ReactiveUI.Winforms_Net45.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@
126126
<ItemGroup>
127127
<Compile Include="ComponentModelTypeConverter.cs" />
128128
<Compile Include="PlatformUnitTestDetector.cs" />
129+
<Compile Include="Winforms\ObservableCollectionChangedToListChangedTransformer.cs" />
130+
<Compile Include="Winforms\ReactiveDerivedBindingListMixins.cs" />
129131
<Compile Include="Winforms\RoutedViewHost.cs">
130132
<SubType>UserControl</SubType>
131133
</Compile>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System.Collections.Generic;
2+
using System.Collections.Specialized;
3+
using System.ComponentModel;
4+
using System.Linq;
5+
6+
namespace ReactiveUI.Winforms
7+
{
8+
static class ObservableCollectionChangedToListChangedTransformer
9+
{
10+
/// <summary>
11+
/// Transforms a NotifyCollectionChangedEventArgs into zero or more ListChangedEventArgs
12+
/// </summary>
13+
/// <param name="ea"></param>
14+
/// <returns></returns>
15+
internal static IEnumerable<ListChangedEventArgs> AsListChangedEventArgs(this NotifyCollectionChangedEventArgs ea)
16+
{
17+
if (ea == null) {
18+
yield break;
19+
}
20+
21+
switch (ea.Action) {
22+
case NotifyCollectionChangedAction.Reset:
23+
yield return new ListChangedEventArgs(ListChangedType.Reset, -1);
24+
break;
25+
case NotifyCollectionChangedAction.Replace:
26+
yield return new ListChangedEventArgs(ListChangedType.ItemChanged, ea.NewStartingIndex);
27+
break;
28+
case NotifyCollectionChangedAction.Remove:
29+
foreach (int index in Enumerable.Range(ea.OldStartingIndex, ea.OldItems.Count)) {
30+
yield return new ListChangedEventArgs(ListChangedType.ItemDeleted, index);
31+
}
32+
break;
33+
case NotifyCollectionChangedAction.Add:
34+
foreach (int index in Enumerable.Range(ea.NewStartingIndex, ea.NewItems.Count)) {
35+
yield return new ListChangedEventArgs(ListChangedType.ItemAdded, index);
36+
}
37+
break;
38+
case NotifyCollectionChangedAction.Move:
39+
//this one is actually not supported by the default BindingList<T> implementation
40+
//maybe we should do a reset instead?
41+
yield return new ListChangedEventArgs(ListChangedType.ItemDeleted, ea.OldStartingIndex);
42+
yield return new ListChangedEventArgs(ListChangedType.ItemAdded, ea.NewStartingIndex);
43+
break;
44+
}
45+
}
46+
}
47+
}
Lines changed: 26 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
namespace ReactiveUI.Winforms
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections;
4+
using System.Collections.Specialized;
5+
using System.ComponentModel;
6+
using System.Runtime.CompilerServices;
7+
using ReactiveUI;
8+
9+
namespace ReactiveUI.Winforms
210
{
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Linq;
6-
using System.Collections;
7-
using System.Collections.Specialized;
8-
using System.ComponentModel;
9-
10-
using ReactiveUI;
11-
12-
public class ReactiveBindingList<T> : ReactiveList<T>, IList<T>, ICollection<T>, IEnumerable<T>, ICollection, IEnumerable, IList, IBindingList, ICancelAddNew, IRaiseItemChangedEvents
11+
public class ReactiveBindingList<T> : ReactiveList<T>,
12+
IList<T>, ICollection<T>, IEnumerable<T>,
13+
ICollection, IEnumerable, IList, IBindingList,
14+
ICancelAddNew, IRaiseItemChangedEvents
1315
{
14-
public ReactiveBindingList()
15-
: this(null)
16-
{}
17-
18-
#region Implementation of ICancelAddNew
16+
public ReactiveBindingList() : this(null) {}
1917

2018
public void CancelNew(int itemIndex)
2119
{
@@ -27,16 +25,7 @@ public void EndNew(int itemIndex)
2725
//throw new NotImplementedException();
2826
}
2927

30-
#endregion
31-
32-
#region Implementation of IRaiseItemChangedEvents
33-
34-
public bool RaisesItemChangedEvents {get
35-
{
36-
return base.ChangeTrackingEnabled;
37-
}}
38-
39-
#endregion
28+
public bool RaisesItemChangedEvents { get { return base.ChangeTrackingEnabled; } }
4029

4130
/// <summary>
4231
/// ReactiveBindingList constructor
@@ -49,51 +38,11 @@ public ReactiveBindingList(IEnumerable<T> items)
4938
protected override void raiseCollectionChanged(NotifyCollectionChangedEventArgs e)
5039
{
5140
base.raiseCollectionChanged(e);
52-
this.transformAndRaise(e);
53-
}
54-
55-
/// <summary>
56-
/// Transforms NotifyCollectionChangedEventArgs into 1 or more ListChangedEventsArgs
57-
/// and raises them if there are any attached handlers
58-
/// </summary>
59-
/// <param name="ea"></param>
60-
void transformAndRaise(NotifyCollectionChangedEventArgs ea)
61-
{
62-
if (this.ListChanged == null) return;
63-
64-
var events = new List<ListChangedEventArgs>();
65-
66-
switch (ea.Action){
67-
case NotifyCollectionChangedAction.Reset:
68-
events.Add(new ListChangedEventArgs(ListChangedType.Reset, -1));
69-
break;
70-
case NotifyCollectionChangedAction.Replace:
71-
events.Add(new ListChangedEventArgs(ListChangedType.ItemChanged, ea.NewStartingIndex));
72-
break;
73-
case NotifyCollectionChangedAction.Remove:
74-
events.AddRange(
75-
Enumerable.Range(ea.OldStartingIndex, ea.OldItems.Count)
76-
.Select(index => new ListChangedEventArgs(ListChangedType.ItemDeleted, index)));
77-
break;
78-
case NotifyCollectionChangedAction.Add:
79-
events.AddRange(
80-
Enumerable.Range(ea.NewStartingIndex, ea.NewItems.Count)
81-
.Select(index => new ListChangedEventArgs(ListChangedType.ItemAdded, index)));
82-
break;
83-
case NotifyCollectionChangedAction.Move:
84-
//this one is actually not supported by the default BindingList<T> implementation
85-
//maybe we should do a reset instead?
86-
events.Add(
87-
new ListChangedEventArgs(ListChangedType.ItemMoved, ea.NewStartingIndex, ea.OldStartingIndex));
88-
break;
41+
if (this.ListChanged != null) {
42+
e.AsListChangedEventArgs().ForEach(x => this.ListChanged(this, x));
8943
}
90-
91-
events.ForEach(x=>this.ListChanged(this, x));
9244
}
9345

94-
95-
#region Implementation of IBindingList
96-
9746
public object AddNew()
9847
{
9948
return Activator.CreateInstance<T>();
@@ -124,44 +73,24 @@ public void RemoveSort()
12473
throw new NotSupportedException();
12574
}
12675

127-
public bool AllowNew {
128-
get { return true; }
129-
}
76+
public bool AllowNew { get { return true; } }
13077

131-
public bool AllowEdit {
132-
get { return true; }
133-
}
78+
public bool AllowEdit { get { return true; } }
13479

135-
public bool AllowRemove {
136-
get { return true; }
137-
}
80+
public bool AllowRemove { get { return true; } }
13881

139-
public bool SupportsChangeNotification {
140-
get { return true; }
141-
}
82+
public bool SupportsChangeNotification { get { return true; } }
14283

143-
public bool SupportsSearching {
144-
get { return false; }
145-
}
84+
public bool SupportsSearching { get { return false; } }
14685

147-
public bool SupportsSorting {
148-
get { return false; }
149-
}
86+
public bool SupportsSorting { get { return false; } }
15087

151-
public bool IsSorted {
152-
get{ return false; }
153-
}
88+
public bool IsSorted { get { return false; } }
15489

155-
public PropertyDescriptor SortProperty {
156-
get{ return null; }
157-
}
90+
public PropertyDescriptor SortProperty { get { return null; } }
15891

159-
public ListSortDirection SortDirection {
160-
get { return ListSortDirection.Ascending; }
161-
}
92+
public ListSortDirection SortDirection { get { return ListSortDirection.Ascending; } }
16293

16394
public event ListChangedEventHandler ListChanged;
164-
165-
#endregion
16695
}
16796
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Specialized;
4+
using System.ComponentModel;
5+
using System.Diagnostics.Contracts;
6+
using System.Reactive;
7+
using System.Reactive.Linq;
8+
9+
namespace ReactiveUI.Winforms
10+
{
11+
/// <summary>
12+
/// IReactiveDerivedList represents a bindinglist whose contents will "follow" another
13+
/// collection; this method is useful for creating ViewModel collections
14+
/// that are automatically updated when the respective Model collection is updated.
15+
/// </summary>
16+
public interface IReactiveDerivedBindingList<T> : IReactiveDerivedList<T>, IBindingList {}
17+
18+
class ReactiveDerivedBindingList<TSource, TValue> :
19+
ReactiveDerivedCollection<TSource, TValue>, IReactiveDerivedBindingList<TValue>
20+
{
21+
public ReactiveDerivedBindingList(
22+
IEnumerable<TSource> source,
23+
Func<TSource, TValue> selector,
24+
Func<TSource, bool> filter,
25+
Func<TValue, TValue, int> orderer,
26+
IObservable<Unit> signalReset)
27+
: base(source, selector, filter, orderer, signalReset) {}
28+
29+
protected override void raiseCollectionChanged(NotifyCollectionChangedEventArgs e)
30+
{
31+
base.raiseCollectionChanged(e);
32+
if (this.ListChanged != null) {
33+
e.AsListChangedEventArgs().ForEach(x => this.ListChanged(this, x));
34+
}
35+
}
36+
37+
const string readonlyExceptionMessage = "Derived collections cannot be modified.";
38+
39+
public object AddNew()
40+
{
41+
throw new NotSupportedException(readonlyExceptionMessage);
42+
}
43+
44+
public void AddIndex(PropertyDescriptor property)
45+
{
46+
throw new NotSupportedException();
47+
}
48+
49+
public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
50+
{
51+
throw new NotSupportedException();
52+
}
53+
54+
public int Find(PropertyDescriptor property, object key)
55+
{
56+
throw new NotSupportedException();
57+
}
58+
59+
public void RemoveIndex(PropertyDescriptor property)
60+
{
61+
throw new NotSupportedException();
62+
}
63+
64+
public void RemoveSort()
65+
{
66+
throw new NotSupportedException();
67+
}
68+
69+
public bool AllowNew { get { return false; } }
70+
71+
public bool AllowEdit { get { return false; } }
72+
73+
public bool AllowRemove { get { return false; } }
74+
75+
public bool SupportsChangeNotification { get { return true; } }
76+
77+
public bool SupportsSearching { get { return false; } }
78+
79+
public bool SupportsSorting { get { return false; } }
80+
81+
public bool IsSorted { get { return false; } }
82+
83+
public PropertyDescriptor SortProperty { get { return null; } }
84+
85+
public ListSortDirection SortDirection { get { return ListSortDirection.Ascending; } }
86+
87+
public event ListChangedEventHandler ListChanged;
88+
}
89+
90+
public static class ObservableCollectionMixin
91+
{
92+
/// <summary>
93+
/// Creates a collection whose contents will "follow" another
94+
/// collection; this method is useful for creating ViewModel collections
95+
/// that are automatically updated when the respective Model collection
96+
/// is updated.
97+
///
98+
/// Note that even though this method attaches itself to any
99+
/// IEnumerable, it will only detect changes from objects implementing
100+
/// INotifyCollectionChanged (like ReactiveList). If your source
101+
/// collection doesn't implement this, signalReset is the way to signal
102+
/// the derived collection to reorder/refilter itself.
103+
/// </summary>
104+
/// <param name="selector">A Select function that will be run on each
105+
/// item.</param>
106+
/// <param name="filter">A filter to determine whether to exclude items
107+
/// in the derived collection.</param>
108+
/// <param name="orderer">A comparator method to determine the ordering of
109+
/// the resulting collection.</param>
110+
/// <param name="signalReset">When this Observable is signalled,
111+
/// the derived collection will be manually
112+
/// reordered/refiltered.</param>
113+
/// <returns>A new collection whose items are equivalent to
114+
/// Collection.Select().Where().OrderBy() and will mirror changes
115+
/// in the initial collection.</returns>
116+
public static IReactiveDerivedBindingList<TNew> CreateDerivedBindingList<T, TNew, TDontCare>(
117+
this IEnumerable<T> This,
118+
Func<T, TNew> selector,
119+
Func<T, bool> filter = null,
120+
Func<TNew, TNew, int> orderer = null,
121+
IObservable<TDontCare> signalReset = null)
122+
{
123+
Contract.Requires(selector != null);
124+
125+
IObservable<Unit> reset = null;
126+
127+
if (signalReset != null) {
128+
reset = signalReset.Select(_ => Unit.Default);
129+
}
130+
131+
return new ReactiveDerivedBindingList<T, TNew>(This, selector, filter, orderer, reset);
132+
}
133+
134+
/// <summary>
135+
/// Creates a collection whose contents will "follow" another
136+
/// collection; this method is useful for creating ViewModel collections
137+
/// that are automatically updated when the respective Model collection
138+
/// is updated.
139+
///
140+
/// Be aware that this overload will result in a collection that *only*
141+
/// updates if the source implements INotifyCollectionChanged. If your
142+
/// list changes but isn't a ReactiveList/ObservableCollection,
143+
/// you probably want to use the other overload.
144+
/// </summary>
145+
/// <param name="selector">A Select function that will be run on each
146+
/// item.</param>
147+
/// <param name="filter">A filter to determine whether to exclude items
148+
/// in the derived collection.</param>
149+
/// <param name="orderer">A comparator method to determine the ordering of
150+
/// the resulting collection.</param>
151+
/// <returns>A new collection whose items are equivalent to
152+
/// Collection.Select().Where().OrderBy() and will mirror changes
153+
/// in the initial collection.</returns>
154+
public static IReactiveDerivedBindingList<TNew> CreateDerivedBindingList<T, TNew>(
155+
this IEnumerable<T> This,
156+
Func<T, TNew> selector,
157+
Func<T, bool> filter = null,
158+
Func<TNew, TNew, int> orderer = null)
159+
{
160+
return This.CreateDerivedBindingList(selector, filter, orderer, (IObservable<Unit>)null);
161+
}
162+
}
163+
}

0 commit comments

Comments
 (0)