5

先に進むためのより良い解決策を (うまくいけば) 見つけたいという共通の問題があります。データのマスター リストを含む ObservableCollection があります。私のクライアント コードでは、ユーザーに表示するために、データを新しい形式に「変換」する必要があります。次のような LINQ ステートメントを使用します。

var newList = (from item in observable
               select new { FirstInitial = item.Name[0] });

私はそれがかなり初歩的であることを知っていますが、問題を実証するには十分です。(プロジェクションに注意してください。これは単純なフィルターまたはグループ化ステートメントではありません。) 次に、データ バインディングを介して UI に newList を表示します。

問題は、元のコレクションが変更されても、UI が変更されないことです。これまでに適用した解決策は、元のコレクションの CollectionChanged イベントにイベント ハンドラーをアタッチして、クエリを再評価し、バインドされたプロパティを更新することでした。

これは問題なく機能しますが、このシナリオに出くわすたびに多くのコードが繰り返されます。ソース Observable が変更されたときに「自動的に」更新される ObservableCollection を LINQ クエリで返す方法はありますか?

言い換えれば、このシナリオが発生したときにいつでも簡単に再利用できるように、いくつかの「定型化された」機能を実装したいと考えています。

アップデート

Scroog1 に感謝します。元の投稿が、私が実際に求めていたものに対してあまりにも UI に結び付けられていることを確認するのを助けてくれました。問題のより良い説明として、次の例を取り上げます。

public class SomeClass
{
    private ObservableCollection<Employee> _allEmployees;
    private ObservableCollection<Employee> _currentEmployees;

    public ObservableCollection<Employee> CurrentEmployees
    {
        get
        {
            if (_currentEmployees == null)
                _currentEmployees = _allEmployees.Where(e => !e.IsTerminated);

            return _currentEmployees;
        }
    }
}

public class SomeViewModel
{
    private ICollectionView _view;

    public ICollectionView CurrentView
    {
        if (_view == null)
        {
            var cvs = new CollectionViewSource()
            {
                Source = someClass.CurrentEmployees
            }

            cvs.Add(new SortDescription("Name", ListSortDirection.Ascending));

            _view = cvs.View;
        }

        return _view;
    }
}

ご覧のとおり、クエリが存在するコードは、UI に直接バインドされているものではありません。この例を使用して、厳密な UI データ バインディング シナリオではなく、より一般的なユース ケースから質問していることを示します。

4

3 に答える 3

2

私はこのようなことをします(observableと呼ばれるObservableCollectionとRaisePropertyChangedメソッドでINotifyPropertyChangedを実装するクラスを想定しています):

public IEnumerable<string> NewList
{
    return from item in observable
           select item.Name;
}

observable.CollectionChanged += delegate { RaisePropertyChanged("NewList"); };

次に、observableが変更されると、UIにNewListが変更されたことが通知され、クエリが再評価されます。

複数の依存アイテムの場合、次のことができます。

observable.CollectionChanged += delegate
    {
        RaisePropertyChanged("NewList",
                             "OtherProperty",
                             "YetAnotherProperty",
                             "Etc");
    };

アップデート

上記は、プロパティにアクセスするたびに最新の値を取得し、INPCを使用して再読み取りを指示できるため、一般的にプロパティに対しては正常に機能します。

コレクションのもう少し興味深いケースでは、INotifyCollectionChangedとIEnumerableを実装し、LINQをラップするカスタムクラスを実装します。例えば、

public class CustomObservableCollection<T> : INotifyCollectionChanged,
                                             INotifyPropertyChanged,
                                             IEnumerable<T>
{
    private readonly IEnumerable<T> _collection;
    public CustomObservableCollection(IEnumerable<T> collection)
    {
        _collection = collection;
    }
    public IEnumerator<T> GetEnumerator()
    {
        _collection.GetEnumerator();
    }
    public void RaiseCollectionChanged() { ... }
    ...
}

次に、次のことができます。

var newList = new CustomObservableCollection(from item in observable
                                             select item.Name);
observable.CollectionChanged += delegate { newList.RaiseCollectionChanged(); };

アップデート2

CustomObservableCollectionに依存関係を渡すこともできます。

public class CustomObservableCollection<T> : INotifyCollectionChanged,
                                             INotifyPropertyChanged,
                                             IEnumerable<T>
{
    private readonly IEnumerable<T> _collection;
    public CustomObservableCollection(IEnumerable<T> collection,
                 params ObservableCollection[] dependencies)
    {
        _collection = collection;
        foreach (var dep in dependencies)
            dep.CollectionChanged += RaiseCollectionChanged();
    }
    public IEnumerator<T> GetEnumerator()
    {
        _collection.GetEnumerator();
    }
    public void RaiseCollectionChanged() { ... }
    ...
}
于 2012-05-01T13:19:29.237 に答える
0

に直接バインドしてobservable、必要なプロパティを選択してください。

例えば:

public IEnumerable<Item> Items { get { return this.observable;} }

<ItemsControl ItemsSource="{Binding Items}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding Name}" />
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>
于 2012-05-01T13:54:16.113 に答える
0

Scroog1 の回答へのコメントで参照したように、私は彼の回答を参考にして、それを少し修正して自分のアプリケーションで使用することができました。ここに示すように、元のコレクション、述語、およびセレクター関数をコンストラクター引数として受け入れるラッパー クラスになりました。

public class ObservableWrapper<TSource, TElement> : IEnumerable<TElement>,
                                                    INotifyCollectionChanged
{
    private Collection<TElement> _items;
    private Func<TSource, Boolean> _predicate;
    private Func<TSource, TElement> _selector;
    private ObservableCollection<TSource> _source;

    public ObservableWrapper(ObservableCollection<TSource> source,
                             Func<TSource, Boolean> predicate,
                             Func<TSource, TElement> selector)
    {
        _predicate = predicate;
        _selector = selector;
        _source = source;

        _source.CollectionChanged += SourceCollectionChanged;
    }

    public IEnumerator<TElement> GetEnumerator()
    {
        EnsureItems();

        return _items.GetEnumerator();
    }

    private void EnsureItems()
    {
        if (_items == null)
        {
            _items = new Collection<TElement>();

            RefreshItems();
        }
    }

    private void NotifyCollectionChanged(NotifyCollectionChangedAction action)
    {
        var handlers = CollectionChanged;

        if (handlers != null)
        {
            var args = new NotifyCollectionChangedEventArgs(action);

            handlers(this, args);
        }
    }

    private void RefreshItems()
    {
        _items.Clear();

        foreach (var element in _source)
        {
            if (_predicate(element))
            {
                var item = _selector(element);

                _items.Add(item);
            }
        }

        NotifyCollectionChanged(NotifyCollectionChangedAction.Reset);
    }

    private void SourceCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
    {
        RefreshItems();
    }

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

私の元の例は次のようになります。

var newList = new ObservableWrapper<Person, Char>(observable,
                                                  item => { return true; },
                                                  item => { return item.Name[0]; });

欠点は、プロジェクションに名前がないことです。しかし、より複雑なシナリオでは、単純に TElement をクラスとして定義し、それをセレクター関数から返します。

于 2012-09-27T12:18:03.273 に答える