WPF列表和ObservableCollections

时间:2012-04-10 19:32:27

标签: wpf mvvm dns viewmodel

所以这可能是以更一般的形式回答的,但这是一个更具体的案例,我想知道如何解决这个问题。我正在编写WPF应用程序并尝试使用MVVM模式(第一次使用此模式)。

我的域对象Viper具有多个属性和集合,并在几个现有应用程序中使用。我现在已经在我想要在我的新WPF应用程序中绑定的所有属性上实现了INotifyPropertyChanged。我现在正在创建一个位于域对象和WPF视图之间的视图模型。问题是Viper对象中集合的所有属性都是Lists not ObservableCollections。我不能简单地将它们设为ObservableCollections,因为这将影响使用此对象模型的所有其他应用程序(不支持AddRange等内容)。

这个新的WPF应用程序维护了一个Viper对象列表,这些对象将用于(通过视图模型)来控制GUI。为了使事情变得更复杂,应用程序将以List<Viper>数据的形式接收数据。该应用程序循环遍历此Viper对象列表,并将每个传入的Viper数据合并到现有Viper(按索引)。因此,假设我的传入Viper对象将一个项添加到现有Viper(List<Event>)的Events属性中,因为这不是ObservableCollection GUI不会更新事件网格。即使我在视图模型中将List<Event>转换为OC<Event>,它也是基础Viper对象,它会对所有属性和集合进行合并,而不是视图模型,因此修改时不会触发任何事件模特OC没有更新。 Viper对象和所有子对象实现自定义MergeWith()函数,该函数确定传入数据应如何合并。有些执行替换,有些追加,有些更新。

处理这种情况的正确方法是什么?如果有什么不清楚,请告诉我。

3 个答案:

答案 0 :(得分:2)

MVVM中有两种方法可以将Model的属性从View暴露给ViewModel:通过将整个模型暴露给视图,或者通过公开单个属性View在ViewModel中关注。

这两种方法同样有效,但您经常使用的方法取决于具体情况。

在您的情况下,您必须使用未设计为通知UI更改的现有Model对象,我将使用第二种方法在ViewModel中为View创建属性。

例如,

<DataGrid ItemsSource="{Binding SelectedViper.Events}" />
public class ViperViewModel : INotifyPropertyChanged
{
    private Viper _selectedViper;

    public Viper SelectedViper 
    { 
        get { return _selectedViper; }
        set
        {
            if (value != _selectedViper)
            {
                _selectedViper= value;
                RaisePropertyChanged("SelectedViper");
            }
        }
    }
}

会变成:

<DataGrid ItemsSource="{Binding ViperEvents}" />
public class ViperViewModel : INotifyPropertyChanged;
{
    private Viper _selectedViper;
    private ObservableCollection<Event> _viperEvents;

    public ViperViewModel()
    {
        this.PropertyChanged += ViperViewModel_PropertyChanged;
    }

    void ViperViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "SelectedViper")
        {
            if (SelectedViper == null)
                ViperEvents = null;
            else
                ViperEvents = new ObservableCollection<Event>(SelectedViper.Events);
        }
    }

    public Viper SelectedViper
    { 
        get { return _selectedViper; }
        set
        {
            if (value != _selectedViper)
            {
                _selectedViper= value;
                RaisePropertyChanged("SelectedViper");
            }
        }
    }

    public ObservableCollection<Event> ViperEvents
    { 
        get { return _viperEvents; }
        set
        {
            if (value != _viperEvents)
            {
                _viperEvents = value;
                RaisePropertyChanged("ViperEvents");
            }
        }
    }
}

前面的工作要多一些,但它使维护更加简单

作为替代方法,您可以覆盖ObservableCollection类并实现您感兴趣的List<T>方法。例如,我有一个ObservableCollectionEx类,当前实现{{1} },ContainsIndexOfAddRange。如果您有兴趣,可以使用RemoveRange方法here的示例。

当然,这也意味着您可能必须更新使用该属性的所有其他内容才能使用Sort而不是ObservableCollectionEx<T>

答案 1 :(得分:1)

这里的想法基本上是在绑定中保留相同的集合并禁止通知直到您需要它们。

public class Oc<T> : ObservableCollection<T>
{
    private object _lockObject;
    private bool _suppressChangeNotification;

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (!_suppressChangeNotification)
            base.OnCollectionChanged(e);
    }

    public void Merge(IEnumerable<T> newItems )
    {           
        //don't know if you need a lock..that's your determination
        lock (_lockObject)
        {
            _suppressChangeNotification = true;
            foreach (var newItem in newItems)
            {
                //whatever you do here, insert/remove based on some condition
                //i'll just put insert for now
                InsertItem(0,newItem);
            }
             _suppressChangeNotification = false;
             OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }
}

答案 2 :(得分:0)

解决此问题的一种方法是将Merged事件添加到MergeWith()中调用的Viper对象。这样,您的视图模型可以订阅该事件,并在更新发生时自行更新。