かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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));