WPF绑定到Model对象列表的ViewModel列表

时间:2010-05-17 22:15:44

标签: c# wpf xaml observablecollection mvvm

在模型中,我有:

public ObservableCollection<Item> Items { get; private set; }

在ViewModel中,我有一个相应的ItemViewModel列表。我希望这个列表与模型列表双向绑定:

public ObservableCollection<ItemViewModel> ItemViewModels ...

在XAML中,我将绑定(在本例中为TreeView)到ItemViewModels属性。

我的问题是,上面显示的ViewModel中的“...”是什么?我希望一行或两行代码绑定这两个ObservableCollections(提供ViewModel的类型来为每个模型对象构造)。但是,我担心的是需要处理Items.CollectionChanged事件的一堆代码,并根据需要通过构造ViewModels手动更新ItemViewModels列表,并根据对ItemViewModels的更改来更新Items集合。 / p>

谢谢!

埃里克

2 个答案:

答案 0 :(得分:5)

您可以使用以下课程:

public class BoundObservableCollection<T, TSource> : ObservableCollection<T>
{
    private ObservableCollection<TSource> _source;
    private Func<TSource, T> _converter;
    private Func<T, TSource, bool> _isSameSource;

    public BoundObservableCollection(
        ObservableCollection<TSource> source,
        Func<TSource, T> converter,
        Func<T, TSource, bool> isSameSource)
        : base()
    {
        _source = source;
        _converter = converter;
        _isSameSource = isSameSource;

        // Copy items
        AddItems(_source);

        // Subscribe to the source's CollectionChanged event
        _source.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_source_CollectionChanged);
    }

    private void AddItems(IEnumerable<TSource> items)
    {
        foreach (var sourceItem in items)
        {
            Add(_converter(sourceItem));
        }
    }

    void _source_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                AddItems(e.NewItems.Cast<TSource>());
                break;
            case NotifyCollectionChangedAction.Move:
                // Not sure what to do here...
                break;
            case NotifyCollectionChangedAction.Remove:
                foreach (var sourceItem in e.OldItems.Cast<TSource>())
                {
                    var toRemove = this.First(item => _isSameSource(item, sourceItem));
                    this.Remove(toRemove);
                }
                break;
            case NotifyCollectionChangedAction.Replace:
                for (int i = e.NewStartingIndex; i < e.NewItems.Count; i++)
                {
                    this[i] = _converter((TSource)e.NewItems[i]);
                }
                break;
            case NotifyCollectionChangedAction.Reset:
                this.Clear();
                this.AddItems(_source);
                break;
            default:
                break;
        }
    }
}

按如下方式使用:

var models = new ObservableCollection<Model>();
var viewModels =
    new BoundObservableCollection<ViewModel, Model>(
        models,
        m => new ViewModel(m), // creates a ViewModel from a Model
        (vm, m) => vm.Model.Equals(m)); // checks if the ViewModel corresponds to the specified model

BoundObservableCollection将在ObservableCollection发生变化时更新,但不会相反(您必须覆盖一些方法才能执行此操作)

答案 1 :(得分:2)

是的,你的恐惧是真的,你必须包装所有ObservableCollection功能。

我的回答问题是,为什么你想要看看模型包装器已经看起来好看的模型?如果您的数据模型基于某些不可绑定的业务逻辑,则View模型非常有用。通常,此业务/数据层具有一种或两种方式来检索数据并通知外部观察者其更改,这些更改可由视图模型轻松处理并转换为ObservableCollection的更改。实际上在.NET 3.5 ObservableCollection WindowsBase.dll 的一部分,所以通常它不会在数据模型中首先使用。

我的建议是填充/修改ObservableCollection的逻辑应该从数据模型移动到视图模型中,或者你应该直接绑定到当前调用数据模型的层,只需调用它就是什么。视图模型。

你显然可以写一个帮助类,它将使用一些转换器lambdas(从ItemItemViewModel并向后)同步两个集合,并在这样的地方使用它(确保你处理项目但是,尽管如此,恕我直言,这种方法会产生多余的包装类,每层都会降低功能并增加复杂性。这与MVVM目标完全相反。