我有一个共同的问题,我希望(希望)找到一个更好的解决方案,以便向前发展。我有一个包含主数据列表的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数据绑定场景中询问。
答案 0 :(得分: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() { ... }
...
}
答案 1 :(得分:0)
为什么不直接绑定到observable
,并选择所需的属性。
e.g:
public IEnumerable<Item> Items { get { return this.observable;} }
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
答案 2 :(得分: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定义为一个类,并从选择器函数返回它。