This commit is contained in:
neuecc 2024-08-26 16:33:36 +09:00
parent 68618fda10
commit 9ffd0417ba
3 changed files with 64 additions and 42 deletions

View File

@ -13,7 +13,7 @@ namespace ObservableCollections
{ {
event NotifyCollectionChangedEventHandler<T>? CollectionChanged; event NotifyCollectionChangedEventHandler<T>? CollectionChanged;
object SyncRoot { get; } object SyncRoot { get; }
ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false); ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform);
} }
public interface IReadOnlyObservableList<T> : public interface IReadOnlyObservableList<T> :
@ -36,7 +36,9 @@ namespace ObservableCollections
{ {
object SyncRoot { get; } object SyncRoot { get; }
ISynchronizedViewFilter<T> Filter { get; } ISynchronizedViewFilter<T> Filter { get; }
IEnumerable<(T Value, TView View)> Filtered { get; }
IEnumerable<(T Value, TView View)> Unfiltered { get; } IEnumerable<(T Value, TView View)> Unfiltered { get; }
int UnfilteredCount { get; }
event Action<SynchronizedViewChangedEventArgs<T, TView>>? ViewChanged; event Action<SynchronizedViewChangedEventArgs<T, TView>>? ViewChanged;
event Action<NotifyCollectionChangedAction>? CollectionStateChanged; event Action<NotifyCollectionChangedAction>? CollectionStateChanged;

View File

@ -15,9 +15,11 @@ namespace ObservableCollections.Internal
public SynchronizedViewList(ISynchronizedView<T, TView> parent) public SynchronizedViewList(ISynchronizedView<T, TView> parent)
{ {
this.parent = parent; this.parent = parent;
this.listView = parent.Select(x => x.View).ToList(); // need lock lock (parent.SyncRoot)
// TODO:add {
parent.ViewChanged += Parent_ViewChanged; this.listView = parent.ToList();
parent.ViewChanged += Parent_ViewChanged;
}
} }
private void Parent_ViewChanged(SynchronizedViewChangedEventArgs<T, TView> e) private void Parent_ViewChanged(SynchronizedViewChangedEventArgs<T, TView> e)

View File

@ -9,9 +9,9 @@ namespace ObservableCollections
{ {
public sealed partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T> public sealed partial class ObservableList<T> : IList<T>, IReadOnlyObservableList<T>
{ {
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false) public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform)
{ {
return new View<TView>(this, transform, reverse); return new View<TView>(this, transform);
} }
internal sealed class View<TView> : ISynchronizedView<T, TView> internal sealed class View<TView> : ISynchronizedView<T, TView>
@ -26,7 +26,6 @@ namespace ObservableCollections
readonly ObservableList<T> source; readonly ObservableList<T> source;
readonly Func<T, TView> selector; readonly Func<T, TView> selector;
readonly bool reverse;
readonly List<(T, TView)> list; readonly List<(T, TView)> list;
int filteredCount; int filteredCount;
@ -37,11 +36,10 @@ namespace ObservableCollections
public object SyncRoot { get; } public object SyncRoot { get; }
public View(ObservableList<T> source, Func<T, TView> selector, bool reverse) public View(ObservableList<T> source, Func<T, TView> selector)
{ {
this.source = source; this.source = source;
this.selector = selector; this.selector = selector;
this.reverse = reverse;
this.filter = SynchronizedViewFilter<T>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.SyncRoot = new object(); this.SyncRoot = new object();
lock (source.SyncRoot) lock (source.SyncRoot)
@ -63,6 +61,17 @@ namespace ObservableCollections
} }
} }
public int UnfilteredCount
{
get
{
lock (SyncRoot)
{
return list.Count;
}
}
}
public void AttachFilter(ISynchronizedViewFilter<T> filter) public void AttachFilter(ISynchronizedViewFilter<T> filter)
{ {
if (filter.IsNullFilter()) if (filter.IsNullFilter())
@ -114,25 +123,31 @@ namespace ObservableCollections
} }
} }
public IEnumerator<(T, TView)> GetEnumerator() public IEnumerator<TView> GetEnumerator()
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
if (!reverse) foreach (var item in list)
{
if (filter.IsMatch(item.Item1))
{
yield return item.Item2;
}
}
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerable<(T Value, TView View)> Filtered
{
get
{
lock (SyncRoot)
{ {
foreach (var item in list) foreach (var item in list)
{ {
if (filter.IsMatch(item.Item1, item.Item2)) if (filter.IsMatch(item.Item1))
{
yield return item;
}
}
}
else
{
foreach (var item in list.AsEnumerable().Reverse())
{
if (filter.IsMatch(item.Item1, item.Item2))
{ {
yield return item; yield return item;
} }
@ -141,7 +156,19 @@ namespace ObservableCollections
} }
} }
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public IEnumerable<(T Value, TView View)> Unfiltered
{
get
{
lock (SyncRoot)
{
foreach (var item in list)
{
yield return item;
}
}
}
}
public void Dispose() public void Dispose()
{ {
@ -171,11 +198,7 @@ namespace ObservableCollections
{ {
var v = (item, selector(item)); var v = (item, selector(item));
list.Add(v); list.Add(v);
if (filter.IsMatch(v)) this.InvokeOnAdd(ref filteredCount, ViewChanged, v, i++);
{
filteredCount++;
}
filter.InvokeOnAdd(v, i++);
} }
} }
} }
@ -186,20 +209,17 @@ namespace ObservableCollections
{ {
var v = (e.NewItem, selector(e.NewItem)); var v = (e.NewItem, selector(e.NewItem));
list.Insert(e.NewStartingIndex, v); list.Insert(e.NewStartingIndex, v);
filter.InvokeOnAdd(v, e.NewStartingIndex); this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex);
} }
else else
{ {
// inefficient copy, need refactoring
var newArray = new (T, TView)[e.NewItems.Length];
var span = e.NewItems; var span = e.NewItems;
for (var i = 0; i < span.Length; i++) for (var i = 0; i < span.Length; i++)
{ {
var v = (span[i], selector(span[i])); var v = (span[i], selector(span[i]));
newArray[i] = v; list.Insert(e.NewStartingIndex + i, v); // should we use InsertRange?
filter.InvokeOnAdd(v, e.NewStartingIndex + i); this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex + i);
} }
list.InsertRange(e.NewStartingIndex, newArray);
} }
} }
break; break;
@ -208,7 +228,7 @@ namespace ObservableCollections
{ {
var v = list[e.OldStartingIndex]; var v = list[e.OldStartingIndex];
list.RemoveAt(e.OldStartingIndex); list.RemoveAt(e.OldStartingIndex);
filter.InvokeOnRemove(v, e.OldStartingIndex); this.InvokeOnRemove(ref filteredCount, ViewChanged, v, e.OldStartingIndex);
} }
else else
{ {
@ -216,10 +236,9 @@ namespace ObservableCollections
for (var i = e.OldStartingIndex; i < len; i++) for (var i = e.OldStartingIndex; i < len; i++)
{ {
var v = list[i]; var v = list[i];
filter.InvokeOnRemove(v, e.OldStartingIndex + i); list.RemoveAt(e.OldStartingIndex + i); // should we use RemoveRange?
this.InvokeOnRemove(ref filteredCount, ViewChanged, v, e.OldStartingIndex + i);
} }
list.RemoveRange(e.OldStartingIndex, e.OldItems.Length);
} }
break; break;
case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Replace:
@ -228,7 +247,7 @@ namespace ObservableCollections
var v = (e.NewItem, selector(e.NewItem)); var v = (e.NewItem, selector(e.NewItem));
var ov = (e.OldItem, list[e.OldStartingIndex].Item2); var ov = (e.OldItem, list[e.OldStartingIndex].Item2);
list[e.NewStartingIndex] = v; list[e.NewStartingIndex] = v;
filter.InvokeOnReplace(v, ov, e.NewStartingIndex); this.InvokeOnReplace(ref filteredCount, ViewChanged, v, ov, e.NewStartingIndex);
break; break;
} }
case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Move:
@ -237,18 +256,17 @@ namespace ObservableCollections
list.RemoveAt(e.OldStartingIndex); list.RemoveAt(e.OldStartingIndex);
list.Insert(e.NewStartingIndex, removeItem); list.Insert(e.NewStartingIndex, removeItem);
filter.InvokeOnMove(removeItem, e.NewStartingIndex, e.OldStartingIndex); this.InvokeOnMove(ref filteredCount, ViewChanged, removeItem, e.NewStartingIndex, e.OldStartingIndex);
} }
break; break;
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
list.Clear(); list.Clear();
filter.InvokeOnReset(); this.InvokeOnReset(ref filteredCount, ViewChanged);
break; break;
default: default:
break; break;
} }
RoutingCollectionChanged?.Invoke(e);
CollectionStateChanged?.Invoke(e.Action); CollectionStateChanged?.Invoke(e.Action);
} }
} }