2010-05-17 73 views
2

在這個模型中,我有:WPF對象列表

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

在視圖模型,我有ItemViewModels的對應列表。我想這個名單是雙向綁定到模型的列表:

public ObservableCollection<ItemViewModel> ItemViewModels ... 

在XAML,我會結合(在這種情況下,一個TreeView)到ItemViewModels財產。

我的問題是,上面顯示的ViewModel中的「...」是什麼?我希望一行或兩行代碼綁定這兩個ObservableCollections(提供要爲每個模型對象構建的ViewModel的類型)。然而,我擔心的是有一些代碼需要處理Items.CollectionChanged事件,並根據需要通過構造ViewModel來手動更新ItemViewModels列表,以及相應的對象將根據對ItemViewModels的更改更新Items集合。

謝謝!

埃裏克

回答

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會改變,但周圍的其他方式(你會必須重寫幾個方法來做到這一點)

+0

謝謝!我知道那裏有人已經完成了封裝這個功能的工作。一個問題:當更改操作是Remove時,我看到您使用isSameSource。爲什麼不只是使用e.OldStartingIndex和e.OldItems.Count: for(int i = 0; i Eric 2010-05-18 15:47:44

+0

@Eric,是的,它可以工作,而且它實際上會更好......如果您確定這兩個集合的順序相同。由於我不處理'NotifyCollectionChangedAction.Move'案件,這並不能保證...... – 2010-05-18 16:00:53

+0

Ey謝謝。只是我在找什麼:) – 2011-09-25 10:05:29

2

是的,你的恐懼是真實的,你必須包裝所有ObservableCollection功能。

我回來的問題是,爲什麼你想有視圖模型包裝已經什麼似乎是很好的模型?如果您的數據模型基於某些不可檢測的業務邏輯,則視圖模型非常有用。通常情況下,這個業務/數據層有一個或兩個檢索數​​據的方法,並通知外部觀察者其變化,這些變化很容易被視圖模型處理,並轉換爲對ObservableCollection的更改。事實上,在.NET 3.5中,ObservableCollectionWindowsBase.dll的一部分,所以通常它不會在數據模型中首先使用。

我的建議是填充/修改的邏輯ObservableCollection應該從您的數據模型移動到視圖模型中,或者您應該直接綁定到您當前稱爲數據模型的層,並將其稱爲它的內容。視圖模型。

可以很明顯的編寫,這將使用一些轉換器的lambda(從ItemItemViewModel和向後)可同步兩個集合的輔助類,並使用它在這樣的地方(請務必妥善雖然處理項目的獨特性),但恕我直言這種方法產生了大量的包裝類,每一層都減少了功能並增加了複雜性。這與MVVM目標完全相反。