public static IDisposable connect_item_hooks <T>(ObservableCollection <T> source, Func <T, IDisposable> get_item_hook, bool clear_source_on_dispose = false) { var item_hooks = source.Select(get_item_hook).ToList(); return(new CompositeDisposable( clear_source_on_dispose ? Disposable.Create(source.Clear) : Disposable.wrap_collection(item_hooks), source.Subscribe((s, e) => CollectionHelper.reflect_change(item_hooks, source, get_item_hook, d => d.Dispose(), e)))); }
public static IDisposable connect_item_hooks <T>(IReadOnlyObservableCollection <T> source, Func <T, IDisposable> get_item_hook) { var item_hooks = source.Select(get_item_hook).ToList(); return(new CompositeDisposable( Disposable.wrap_collection(item_hooks), source.Subscribe((s, e) => CollectionHelper.reflect_change(item_hooks, source, get_item_hook, d => d.Dispose(), e)))); }
/// <summary> /// Establishes a 2-way connection between a target collection and a source collection. /// First, the target is mutated to contain the exact same elements as the source. /// After that, subsequent changes to either collection are replicated in the other. /// </summary> public static IDisposable connect_two_way <T>(IObservable <ObservableCollection <T> > source, ObservableCollection <T> target) { IDisposable curr_connection = null; IDisposable source_sub = source.Subscribe(c => { Disposable.dispose(ref curr_connection); curr_connection = connect_two_way(c, target); }); return(Disposable.Create(() => { Disposable.dispose(ref source_sub); Disposable.dispose(ref curr_connection); })); }
/// <summary> /// Establishes a 2-way connection between a target collection and a source collection. /// First, the target is mutated to contain the exact same elements as the source. /// After that, subsequent changes to either collection are replicated in the other. /// </summary> public static IDisposable connect_two_way <T>(ObservableCollection <T> source, ObservableCollection <T> target) { target.SyncWith(source); source.CollectionChanged += source_changed; target.CollectionChanged += target_changed; return(Disposable.Create(() => { source.CollectionChanged -= source_changed; target.CollectionChanged -= target_changed; })); void source_changed(object sender, NotifyCollectionChangedEventArgs e) { using (target.SuspendHandler(target_changed)) CollectionHelper.reflect_change(target, source, _ => _, null, e); } void target_changed(object sender, NotifyCollectionChangedEventArgs e) { using (source.SuspendHandler(source_changed)) CollectionHelper.reflect_change(target, source, _ => _, null, e); } }
public static IDisposable connect <TCollection, T, TResult>(TCollection source, IList <TResult> target, Func <T, TResult> create, Action <TResult> on_removed = null) where TCollection : IReadOnlyList <T>, INotifyCollectionChanged { on_collection_changed(source, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)source)); source.CollectionChanged += on_collection_changed; return(Disposable.Create(() => source.CollectionChanged -= on_collection_changed)); void on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: if (e.NewStartingIndex < 0) { for (int i = 0; i < e.NewItems.Count; i++) { target.Add(create((T)e.NewItems[i])); } } else { for (int i = 0; i < e.NewItems.Count; i++) { target.Insert(e.NewStartingIndex + i, create((T)e.NewItems[i])); } } break; case NotifyCollectionChangedAction.Remove: validate_old_index(); for (int i = 0; i < e.OldItems.Count; i++) { on_removed?.Invoke(target[e.OldStartingIndex]); target.RemoveAt(e.OldStartingIndex); } break; case NotifyCollectionChangedAction.Replace: validate_old_index(); // remove for (int i = 0; i < e.OldItems.Count; i++) { on_removed?.Invoke(target[e.OldStartingIndex]); target.RemoveAt(e.OldStartingIndex); } // add if (e.NewStartingIndex != e.OldStartingIndex) { throw new InvalidOperationException(fmt_error($"{nameof(e.OldStartingIndex)} is not equal to {nameof(e.NewStartingIndex)}")); } if (e.NewStartingIndex < 0) { for (int i = 0; i < e.NewItems.Count; i++) { target.Add(create((T)e.NewItems[i])); } } else { for (int i = 0; i < e.NewItems.Count; i++) { target.Insert(e.NewStartingIndex + i, create((T)e.NewItems[i])); } } break; case NotifyCollectionChangedAction.Move: validate_old_index(); validate_new_index(); if (e.OldStartingIndex < 0) { throw new InvalidOperationException($"Cannot process '{e.Action}' event when e.OldStartingIndex is less than 0."); } for (int i = 0; i < e.OldItems.Count; i++) { target.RemoveAt(e.OldStartingIndex); } if (e.NewStartingIndex < 0) { throw new InvalidOperationException($"Cannot process '{e.Action}' event when e.NewStartingIndex is less than 0."); } for (int i = 0; i < e.NewItems.Count; i++) { } break; case NotifyCollectionChangedAction.Reset: if (on_removed != null) { for (int i = 0; i < target.Count; i++) { on_removed(target[i]); } } target.Clear(); for (int i = 0; i < source.Count; i++) { target.Add(create(source[i])); } break; } void validate_old_index() => validate_index(e.OldStartingIndex, nameof(NotifyCollectionChangedEventArgs.OldStartingIndex)); void validate_new_index() => validate_index(e.NewStartingIndex, nameof(NotifyCollectionChangedEventArgs.NewStartingIndex)); void validate_index(int i, string name) { if (i < 0) { throw new InvalidOperationException(fmt_error($"{name} is less than 0.")); } } string fmt_error(string msg) => $"Cannot process '{e.Action}' event when {msg}"; } }