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

发布于 2024-09-02 08:32:57 字数 591 浏览 4 评论 0原文

在模型中,我有:

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

在 ViewModel 中,我有相应的 ItemViewModel 列表。我希望此列表以双向方式绑定到模型的列表:

public ObservableCollection<ItemViewModel> ItemViewModels ...

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

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

谢谢!

埃里克

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

平定天下 2024-09-09 08:32:57

您可以使用以下类:

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 将更新,但反之则不然(您必须重写很少有方法可以做到这一点)

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)

过期以后 2024-09-09 08:32:57

是的,您的担心是真的,您必须包装所有 ObservableCollection 功能。

我的返回问题是,为什么你想要在看起来不错的模型周围使用视图模型包装器?如果您的数据模型基于某些不可绑定的业务逻辑,则视图模型非常有用。通常,此业务/数据层有一种或两种方法来检索数据并将其更改通知外部观察者,这些更改很容易由视图模型处理并转换为 ObservableCollection 的更改。事实上,在 .NET 3.5 中,ObservableCollectionWindowsBase.dll 的一部分,因此通常它一开始就不会在数据模型中使用。

我的建议是,填充/修改 ObservableCollection 的逻辑应该从数据模型移到视图模型中,或者您应该直接绑定到当前调用数据模型的层并直接调用它的名称。一个视图模型。

显然,您可以编写一个帮助器类,它将使用一些转换器 lambda(从 ItemItemViewModel 以及向后)同步两个集合,并在像这样的地方使用它(确保虽然你正确地处理了项目的唯一性),但是恕我直言,这种方法会产生多余数量的包装类,并且每一层都会减少功能并增加复杂性。这与 MVVM 目标恰恰相反。

Yes, your fears are true, you'd have to wrap all ObservableCollection functionality.

My return question is though, why would you want to have view-model wrapper around already what seems to be nice model? View model is useful if your data model is based on some unbindable business logic. Normally this business/data layer has one or two ways of retrieving data and notifying external observers of its changes which are easily handled by view model and converted into changes to ObservableCollection. In fact in .NET 3.5 ObservableCollection was part of WindowsBase.dll, so normally it wouldn't be used in data models in the first place.

My suggestion is either the logic which populates/modifies ObservableCollection should be moved from your data model into view model, or you should just bind directly to the layer you currently call data model and just call it what it is. A view model.

You can obviously write a helper class which will be syncing two collections using some converter lambdas (from Item to ItemViewModel and backward) and use it all over places like this (make sure you handle item uniqueness properly though), however IMHO this approach spawns redundant amount of wrapper classes, and each layer reduces functionality and adds complexity. Which is exactly the opposite of MVVM goals.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文