5

我有一个共同的问题,我想(希望)找到一个更好的解决方案来继续前进。我有一个包含数据主列表的 ObservableCollection。在我的客户端代码中,我需要将数据“转换”为新表单以显示给用户。我使用如下 LINQ 语句:

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

我知道这很初级,但足以证明问题。(注意投影,这不是一个简单的过滤器或分组语句。)然后我通过数据绑定在我的 UI 中显示 newList。

问题是,当原始集合发生变化时,UI 不会。到目前为止,我应用的解决方案是将事件处理程序附加到原始集合的 CollectionChanged 事件,该事件重新评估查询并更新绑定属性。

虽然这很好用,但每次我遇到这种情况时都会重复很多代码。有没有办法让 LINQ 查询返回一个 ObservableCollection,当源 Observable 更改时“自动”更新?

换句话说,我想实现一些“固定”功能,以便在遇到这种情况时可以简单地重用它。

更新

感谢 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 回答