かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

ObservableCollectionとReadOnlyObservableCollectionの同期

ReactivePropertyのAPIに依存してますが、こういうのを用意しておけばいい感じ??

public static class ObservableCollectionExtensions
{
    public static IDisposable ConnectToReadOnlyCollection<TType, TResult>(
        this ObservableCollection<TType> self, 
        out ReadOnlyObservableCollection<TResult> target, 
        Func<TType, TResult> converter)
    {
        var d = new CompositeDisposable();

        ObservableCollection<TResult> proxy = new ObservableCollection<TResult>(self.Select(t => converter(t)).ToArray());
        target = new ReadOnlyObservableCollection<TResult>(proxy);

        var collectionChanged = self.CollectionChangedAsObservable();
        collectionChanged.Where(e => e.Action == NotifyCollectionChangedAction.Add)
            .Select(e => new { Index = e.NewStartingIndex, Value = converter(e.NewItems.Cast<TType>().First()) })
            .Subscribe(v => proxy.Insert(v.Index, v.Value))
            .AddTo(d);
        collectionChanged.Where(e => e.Action == NotifyCollectionChangedAction.Move)
            .Select(e => new { OldIndex = e.OldStartingIndex, NewIndex = e.NewStartingIndex })
            .Subscribe(v => 
            {
                var item = proxy[v.OldIndex];
                proxy.RemoveAt(v.OldIndex);
                proxy.Insert(v.NewIndex, item);
            })
            .AddTo(d);
        collectionChanged.Where(e => e.Action == NotifyCollectionChangedAction.Remove)
            .Select(e => new { Index = e.OldStartingIndex })
            .Subscribe(v => proxy.RemoveAt(v.Index))
            .AddTo(d);
        collectionChanged.Where(e => e.Action == NotifyCollectionChangedAction.Replace)
            .Select(e => new { Index = e.NewStartingIndex, Item = converter(e.NewItems.Cast<TType>().First()) })
            .Subscribe(v => proxy[v.Index] = v.Item)
            .AddTo(d);
        collectionChanged.Where(e => e.Action == NotifyCollectionChangedAction.Reset)
            .Subscribe(_ =>
            {
                proxy.Clear();
                foreach (var item in self.ToArray())
                {
                    proxy.Add(converter(item));
                }
            })
            .AddTo(d);

        return d;
    }
}

使い方

// TodoItemsがObservableCollectionでthis.todoItemsがReadOnlyObservableCollection
TodoManager.Curret.TodoItems.ConnectToReadOnlyCollection(
    out this.todoItems,
    t => new TodoItemViewModel(t));