WPF绑定到Model对象列表的ViewModels列表 [英] WPF List of ViewModels bound to list of Model objects

查看:302
本文介绍了WPF绑定到Model对象列表的ViewModels列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在模型中,我有:

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

在ViewModel中,我有一个对应的ItemViewModels列表.我希望此列表可以双向绑定到模型的列表:

public ObservableCollection<ItemViewModel> ItemViewModels ...

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

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

谢谢!

埃里克

解决方案

您可以使用以下类:

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

ObservableCollection更改时,BoundObservableCollection将被更新,但反之则不行(您必须重写一些方法来做到这一点)

In the model, I have:

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

In the ViewModel, I have a corresponding list of ItemViewModels. I would like this list to be two-way bound to the model's list:

public ObservableCollection<ItemViewModel> ItemViewModels ...

In the XAML, I will bind (in this case a TreeView) to the ItemViewModels property.

My question is, what goes in the "..." in the ViewModel shown above? I am hoping for a line or two of code to binds these two ObservableCollections (providing the type of the ViewModel to construct for each model object). However, what I'm fearing is necessary is a bunch of code to handle the Items.CollectionChanged event and manually updates the ItemViewModels list by constructing ViewModels as necessary, and the corresponding opposite that will update the Items collection based on changes to ItemViewModels.

Thanks!

Eric

解决方案

You can use the following class :

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

Use it as follows :

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

The BoundObservableCollection will be updated when the ObservableCollection will change, but not the other way around (you would have to override a few methods to do that)

这篇关于WPF绑定到Model对象列表的ViewModels列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆