2017-02-16 37 views
1

我的DataGrid的項目是一個IList。 IList更新的唯一方法是通過屬於它的類的方法。如何手動更新WPF DataGrid?

class SomeObject 
{ 
    public ReadOnlyCollection<SomeType> Items { get; } 

    public void AddItem(SomeType someType); 

    public event Action<SomeType> ItemAdded; 
} 

該列表本身是一個只讀集合,不能直接更新 - 很好,它絕對不是一個ObservableCollection。有沒有一種方法可以綁定到DataGrid用於顯示目的,但是使用綁定/數據網格鉤來處理創建,更新和刪除項目?

回答

1

爲您的集合創建一個實現IBindingList和ICancelAddNew的代理。有了這兩個,你可以「攔截」呼叫來添加新項目並刪除現有項目。添加新項目時會調用AddNew()方法,並在刪除項目時調用Remove/RemoveAt。您可以調用爲此設計的API方法,而不是直接修改集合。以下是最低要求的實施。需要注意的幾件事情:

  1. 新項目不會立即添加到集合 - 在被暫時儲存在_newItem場

  2. 如果在編輯新項目,首先CancelNew,然後逃逸被擊中EndNew被稱爲

  3. 假設「項」是可觀察到的集合,事件從該觸發對應事件的ListChanged

  4. 這種技術並不提供到int的裝置現有項目

對屬性所做ercept修改...

class SomeTypeBindingList : IBindingList, ICancelAddNew 
{ 
    public SomeTypeBindingList(SomeObject someObject) 
    { 
     _someObject = someObject; 

     var observableCollection = _someObject.Items as ObservableCollection<SomeType>; 
     if (observableCollection != null) 
      observableCollection.CollectionChanged += ObservableCollectionOnCollectionChanged; 
    } 

    public IEnumerator GetEnumerator() 
    { 
     return new SomeTypeEnumerator(this); 
    } 

    public int Count => _someObject.Items.Count + (_newItem == null ? 0 : 1); 

    public object SyncRoot { get; } = new object(); 

    public bool IsSynchronized { get; } = false; 

    public bool Contains(object value) 
    { 
     return IndexOf(value) != -1; 
    } 

    public int IndexOf(object value) 
    { 
     if (ReferenceEquals(value, _newItem)) 
      return _someObject.Items.Count; 

     return _someObject.Items.IndexOf((SomeType)value); 
    } 

    public void Remove(object value) 
    { 
     var someType = (SomeType)value; 
     _someObject.RemoveItem(someType); 
    } 

    public void RemoveAt(int index) 
    { 
     var someType = _someObject.Items[index]; 
     _someObject.RemoveItem(someType); 
    } 

    public object this[int index] 
    { 
     get 
     { 
      if (index >= _someObject.Items.Count) 
      { 
       if(_newItem == null) 
        throw new IndexOutOfRangeException(); 

       return _newItem; 
      } 

      return _someObject.Items[index]; 
     } 
     set 
     { 
      throw new NotImplementedException(); 
     } 
    } 

    public object AddNew() 
    { 
     _newItem = new SomeType(); 

     ListChanged?.Invoke(this, new ListChangedEventArgs(ListChangedType.ItemAdded, _someObject.Items.Count)); 
     return _newItem; 
    } 

    public void CancelNew(int itemIndex) 
    { 
     _newItem = null; 
     ListChanged?.Invoke(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, itemIndex)); 
    } 

    public void EndNew(int itemIndex) 
    { 
     if (_newItem != null) 
     { 
      var someType = _newItem; 
      _newItem = null; 
      _someObject.AddItem(someType); 
     } 
    } 

    private void ObservableCollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     if (e.Action == NotifyCollectionChangedAction.Remove) 
      Enumerable.Range(e.OldStartingIndex, e.OldItems.Count).ForEach(i => ListChanged?.Invoke(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, i))); 
     else if(e.Action == NotifyCollectionChangedAction.Add) 
      Enumerable.Range(e.NewStartingIndex, e.NewItems.Count).ForEach(i => ListChanged?.Invoke(this, new ListChangedEventArgs(ListChangedType.ItemAdded, i))); 
    } 

    private readonly SomeObject _someObject; 
    private SomeType _newItem; 

    class SomeTypeEnumerator : IEnumerator 
    { 
     public SomeTypeEnumerator(SomeObject someObject) 
     { 
      _someObject = someObject; 
      Reset(); 
     } 

     public void Dispose() 
     { 

     } 

     public bool MoveNext() 
     { 
      _index++; 
      return _someObject.Items.Count < _index; 
     } 

     public void Reset() 
     { 
      _index = -1; 
     } 

     public object Current => _someObject.Items[_index]; 

     object IEnumerator.Current 
     { 
      get { return Current; } 
     } 

     private readonly SomeObject _someObject; 
     private int _index; 
    } 
} 
1

您可以將DataGridItemsSource屬性綁定到任何公共財產返回IEnumerable,inclucing的IList<T>財產。你不能綁定到字段,但這樣你應該做Items的屬性,如果你打算綁定到它:

public IList<SomeType> Items { get; private set; } 

但你能夠項目在運行時動態添加到源集合,並有新項目自動顯示在DataGrid中,源集合必須實現INotifyCollectionChanged接口。只有ObservableCollection<T>類在.NET Framework中執行此操作。 A List<T>沒有。

IList<T>不是隻讀集合,因爲它有一個Add方法:https://msdn.microsoft.com/en-us/library/system.collections.ilist.add%28v=vs.110%29.aspx。所以我想你不妨使用ObservableCollection<T>

編輯:

如果你真的想refesh的DataGrid「手動」,你可以訂閱ItemAdded事件視圖的代碼隱藏您的對象,並使用BindingExpressionUpdateTarget()方法:

someObject.ItemAdded += (se, ee) => 
{ 
    var be = BindingOperations.GetBindingExpression(theDataGrid, DataGrid.ItemsSourceProperty); 
    if (be != null) 
     be.UpdateTarget(); 

}; 

或者您也可以重置屬性ItemsSource

someObject.ItemAdded += (se, ee) => 
{ 
    theDataGrid.ItemsSource = someObject.Items; 

}; 

編輯2:

我的問題是,我需要一個可靠的方式來攔截網的站綁定機制,使我可以調用的AddItem()時添加一個新行,例如。我已經用IBindingList進行了試驗,看看我能否使用它,但是至今還沒有完成。

如果您在DataGridItemsSource屬性綁定到一個ObservableCollection<SomeType>,你可以處理CollectionChanged情況下,本集合:

observableCollection.CollectionChanged += (ss, ee) => 
{ 
    if(ee.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) 
    { 
     SomeType newItem = ee.NewItems[0] as SomeType; 
     someObject.AddItem(newItem); 
    } 
}; 

DataGrid增加了一個新的項目到該事件將引發源集合。

+0

感謝我更新根據您的反饋我的榜樣。關鍵是我無法對Items進行更改,但我仍然希望使用DataGrid功能來編輯現有項目和創建新項目。但是當你使用內置的綁定時,這些似乎只能起作用。 – Dan

+0

您「無法更改」Items集合,但您希望能夠創建新項目並將它們添加到項目中?這沒有意義。如果您希望能夠編輯集合中的項目,則應設置DataGrid的ItemsSource屬性或將其綁定到此集合。如果您不想將項目添加到Items集合,則創建另一個集合以進行綁定。 – mm8

+0

上面的示例類是我必須編碼的現有API的簡化。該對象包含一個您可以閱讀的集合,但對其進行修改需要調用一個方法,例如AddItem()。 – Dan