2

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

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

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

处理这种情况的正确方法是什么?让我知道是否有任何不清楚的地方。

4

3 回答 3

2

在 MVVM 中有两种方法可以将 a 的属性暴露ModelViewfrom ViewModel:或者通过将整个 Model 暴露给 View,或者通过在 ViewModel 中暴露 View 关心的单个属性。

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

在您的情况下,您必须使用并非旨在通知 UI 更改的现有模型对象,我将使用在 ViewModel 中为视图创建属性的第二种方法。

例如,

<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当前实现ContainsIndexOfAddRange和的类RemoveRange。如果您有兴趣,这里Sort有一个方法示例。

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

于 2012-04-10T20:09:18.353 回答
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));
        }
    }
}
于 2012-04-11T17:15:34.450 回答
0

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

于 2012-04-10T19:36:06.757 回答