2010-12-19 85 views
15

我想使用DataGrid.CanUserAddRows = true功能。不幸的是,它似乎只適用於具有默認構造函數的具體類。我的業務對象集合不提供默認構造函數。如何使用DataGrid.CanUserAddRows = true的工廠

我正在尋找一種方法來註冊知道如何爲DataGrid創建對象的工廠。我看了一下DataGrid和ListCollectionView,但他們都沒有支持我的場景。

回答

4

我看了看IEditableCollectionViewAddNewItem,它似乎正在添加此功能。

從MSDN

的IEditableCollectionViewAddNewItem 接口使應用程序開發人員 指定要添加到集合什麼類型的 對象。此 界面延伸 IEditableCollectionView,因此您可以 添加,編輯和刪除 集合中的項目。 IEditableCollectionViewAddNewItem將 添加到AddNewItem方法中,該方法將 對象添加到 集合中。

  • 中的CollectionView的對象是不同的類型:當 你 要添加的收集和對象具有的 以下一項或多項特徵此方法非常有用。
  • 對象沒有默認構造函數。
  • 該對象已經存在。
  • 您想要將一個空對象添加到集合中。

雖然在Bea Stollnitz blog,你可以閱讀不能夠添加新項目時,源沒有 默認構造函數以下

  • 的限制是非常好 通過了解團隊。 WPF 4.0 Beta 2 有一個新功能,使我們有一個更近一步的解決方案: IEditableCollectionViewAddNewItem 包含AddNewItem方法的介紹。您可以閱讀有關 此功能的MSDN文檔。 MSDN中的示例顯示 如何在創建自己的 自定義UI以添加新項目(使用 ListBox顯示數據,使用 對話框輸入新項目)時如何使用它。 從我可以告訴,雖然DataGrid不 仍然使用此方法(儘管 有點難以100%確定 ,因爲反射器不反編譯 4.0 Beta 2位)。

這個答案是從2009年,所以也許這是爲DataGrid可用現在

+2

感謝您的出色答案。 ListCollectionView類實現IEditableCollectionViewAddNewItem接口。我看了一下通過Reflector的實現。微軟在這個課上做了很多性能優化。我不想爲自己實現這個接口只是使用工廠方法。 – jbe 2010-12-21 19:56:13

+0

@jbe。我明白了:)另外,IEditableCollectionViewAddNewItem上沒有很多信息,至少不是我能找到的。如果你找到一種方法來完成你的任務,一定要更新 – 2010-12-21 23:57:14

0

我可以建議你的類沒有默認構造函數提供包裝最簡單的方法,在其中構造源類將被命名。 例如,你有這個類沒有默認構造函數:

/// <summary> 
/// Complicate class without default constructor. 
/// </summary> 
public class ComplicateClass 
{ 
    public ComplicateClass(string name, string surname) 
    { 
     Name = name; 
     Surname = surname; 
    } 

    public string Name { get; set; } 
    public string Surname { get; set; } 
} 

編寫一個包裝它:

/// <summary> 
/// Wrapper for complicated class. 
/// </summary> 
public class ComplicateClassWraper 
{ 
    public ComplicateClassWraper() 
    { 
     _item = new ComplicateClass("def_name", "def_surname"); 
    } 

    public ComplicateClassWraper(ComplicateClass item) 
    { 
     _item = item; 
    } 

    public ComplicateClass GetItem() { return _item; } 

    public string Name 
    { 
     get { return _item.Name; } 
     set { _item.Name = value; } 
    } 
    public string Surname 
    { 
     get { return _item.Surname; } 
     set { _item.Surname = value; } 
    } 

    ComplicateClass _item; 
} 

代碼隱藏。 在您的ViewModel中,您需要爲您的源集合創建包裝器集合,它將處理在datagrid中添加/刪除項目。

public MainWindow() 
    { 
     // Prepare collection with complicated objects. 
     _sourceCollection = new List<ComplicateClass>(); 
     _sourceCollection.Add(new ComplicateClass("a1", "b1")); 
     _sourceCollection.Add(new ComplicateClass("a2", "b2")); 

     // Do wrapper collection. 
     WrappedSourceCollection = new ObservableCollection<ComplicateClassWraper>(); 
     foreach (var item in _sourceCollection) 
      WrappedSourceCollection.Add(new ComplicateClassWraper(item)); 

     // Each time new item was added to grid need add it to source collection. 
     // Same on delete. 
     WrappedSourceCollection.CollectionChanged += new NotifyCollectionChangedEventHandler(Items_CollectionChanged); 

     InitializeComponent(); 
     DataContext = this; 
    } 

    void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     if (e.Action == NotifyCollectionChangedAction.Add) 
      foreach (ComplicateClassWraper wrapper in e.NewItems) 
       _sourceCollection.Add(wrapper.GetItem()); 
     else if (e.Action == NotifyCollectionChangedAction.Remove) 
      foreach (ComplicateClassWraper wrapper in e.OldItems) 
       _sourceCollection.Remove(wrapper.GetItem()); 
    } 

    private List<ComplicateClass> _sourceCollection; 

    public ObservableCollection<ComplicateClassWraper> WrappedSourceCollection { get; set; } 
} 

最後,XAML代碼:

<DataGrid CanUserAddRows="True" AutoGenerateColumns="False" 
      ItemsSource="{Binding Path=Items}"> 
    <DataGrid.Columns> 
     <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/> 
     <DataGridTextColumn Header="SecondName" Binding="{Binding Path=Surname}"/> 
    </DataGrid.Columns> 
</DataGrid> 
+1

你甚至不需要包裝。你可以繼承現有的類並提供一個默認的構造函數。 – Phil 2012-03-23 13:23:58

27

問題:

「我正在尋找一種方式來註冊一個工廠,它知道如何創建對象爲DataGrid」 。 (因爲我的業務對象的集合不提供默認的構造函數)。

症狀:

如果我們設置DataGrid.CanUserAddRows = true,然後項目的集合綁定到DataGrid在項目沒有默認構造函數,那麼DataGrid不會顯示「新項目行」。

原因:

當項的集合被綁定到任何WPF ItemControl,WPF包裝集合中任一:

  1. 一個BindingListCollectionView時的約束集合是一個BindingList<T>BindingListCollectionView執行IEditableCollectionView但不執行IEditableCollectionViewAddNewItem

  2. a ListCollectionView當綁定的集合是任何其他集合時。 ListCollectionView實施IEditableCollectionViewAddNewItem(因此IEditableCollectionView)。

對於選項2)DataGrid委託創建新項目到ListCollectionViewListCollectionView內部測試是否存在默認構造函數,如果不存在,則禁用AddNew。以下是使用DotPeek的ListCollectionView的相關代碼。

public bool CanAddNewItem (method from IEditableCollectionView) 
{ 
    get 
    { 
    if (!this.IsEditingItem) 
     return !this.SourceList.IsFixedSize; 
    else 
     return false; 
    } 
} 

bool CanConstructItem 
{ 
    private get 
    { 
    if (!this._isItemConstructorValid) 
     this.EnsureItemConstructor(); 
    return this._itemConstructor != (ConstructorInfo) null; 
    } 
} 

似乎沒有一種簡單的方法來覆蓋此行爲。

對於選項1)情況好得多。 DataGrid將新項目的創建委託給BindingListView,後者又轉到BindingListBindingList<T>還檢查是否存在默認構造函數,但幸運的是,BindingList<T>還允許客戶端設置AllowNew屬性並附加用於提供新項目的事件處理程序。見解決過去了,但這裏的在BindingList<T>

public bool AllowNew 
{ 
    get 
    { 
    if (this.userSetAllowNew || this.allowNew) 
     return this.allowNew; 
    else 
     return this.AddingNewHandled; 
    } 
    set 
    { 
    bool allowNew = this.AllowNew; 
    this.userSetAllowNew = true; 
    this.allowNew = value; 
    if (allowNew == value) 
     return; 
    this.FireListChanged(ListChangedType.Reset, -1); 
    } 
} 

非解決方案的相關代碼:通過數據網格(不可用)

  • 支持

這將合理地預期數據網格,以允許客戶端連接一個回調,通過這些數據網格將要求一個默認的新項目,就像上面BindingList<T>。如果需要的話,這會給客戶提供第一個創建新項目的方法。

不幸的是,這是不是直接從數據網格的支持,即使是在.NET 4.5。

.NET 4.5看起來似乎有一個以前不可用的新事件'AddingNewItem',但這隻能讓你知道正在添加一個新項目。

變通:在同一組件上的工具創建

  • 業務對象:使用部分類

這種情況似乎不太可能,但可以想象的是實體框架創建了實體類有沒有默認構造函數(不太可能,因爲他們不會被序列化),那麼我們可以簡單地創建一個默認的構造函數的局部類。問題解決了。

  • 業務對象在另一個程序集中,並且不是密封的:創建一個超類型的業務對象。

這裏我們可以從業務對象類型繼承並添加一個默認的構造函數。

這最初似乎是個好主意,但轉念一想,這可能需要更多的工作比是必要的,因爲我們需要複製由業務層到我們的業務對象的超強型版本生成的數據。

我們需要像

class MyBusinessObject : BusinessObject 
{ 
    public MyBusinessObject(BusinessObject bo){ ... copy properties of bo } 
    public MyBusinessObject(){} 
} 

代碼,然後一些LINQ到這些對象的列表之間項目。

  • 業務對象在另一個程序集中,並且被密封(或不):封裝業務對象。

這是很容易

class MyBusinessObject 
{ 
    public BusinessObject{ get; private set; } 

    public MyBusinessObject(BusinessObject bo){ BusinessObject = bo; } 
    public MyBusinessObject(){} 
} 

現在我們需要做的是使用一些LINQ這些對象的列表之間的項目,然後綁定到MyBusinessObject.BusinessObject在DataGrid中。沒有凌亂的屬性包裝或需要複製的值。

解決辦法:(歡呼找到一個)

  • 使用BindingList<T>

如果我們結束我們的業務對象的集合中BindingList<BusinessObject>,然後將DataGrid綁定到這一點,有幾個代碼行我們的問題已解決,DataGrid將適當地顯示一個新的項目行。

public void BindData() 
{ 
    var list = new BindingList<BusinessObject>(GetBusinessObjects()); 
    list.AllowNew = true; 
    list.AddingNew += (sender, e) => 
     {e.NewObject = new BusinessObject(... some default params ...);}; 
} 

其他解決方案

  • 在現有的集合類型之上實現IEditableCollectionViewAddNewItem。可能很多工作。
  • 繼承自ListCollectionView並覆蓋功能。我在這方面取得了部分成功,可能會做更多的努力。
+1

請注意其他人報告BindingList不能很好地擴展http://www.themissingdocs.net/wordpress/?p=465 – gap 2016-08-08 19:36:32

7

我發現了另一個解決這個問題的方法。在我的情況下,我的對象需要使用工廠初始化,並且沒有任何方法可以解決這個問題。

我無法使用BindingList<T>,因爲我的收藏必須支持分組,排序和過濾,而BindingList<T>不支持。

我使用DataGrid的AddingNewItem事件解決了這個問題。這幾乎entirely undocumented event不僅告訴你一個新的項目正在添加,但也allows lets you choose which item is being added。其他任何事情都會發生; EventArgsNewItem財產僅僅是null

即使您爲該事件提供了處理程序,如果該類沒有默認構造函數,DataGrid也會拒絕允許用戶添加行。然而,奇怪的是(但幸好)如果你有一個,並且設置了AddingNewItemEventArgsNewItem屬性,它將永遠不會被調用。

如果您選擇這樣做,您可以使用屬性(如[Obsolete("Error", true)][EditorBrowsable(EditorBrowsableState.Never)])以確保沒有人調用構造函數。你也可以讓構造函數體拋出異常

反編譯控件讓我們看看那裏發生了什麼。

private object AddNewItem() 
{ 
    this.UpdateNewItemPlaceholder(true); 
    object newItem1 = (object) null; 
    IEditableCollectionViewAddNewItem collectionViewAddNewItem = (IEditableCollectionViewAddNewItem) this.Items; 
    if (collectionViewAddNewItem.CanAddNewItem) 
    { 
    AddingNewItemEventArgs e = new AddingNewItemEventArgs(); 
    this.OnAddingNewItem(e); 
    newItem1 = e.NewItem; 
    } 
    object newItem2 = newItem1 != null ? collectionViewAddNewItem.AddNewItem(newItem1) : this.EditableItems.AddNew(); 
    if (newItem2 != null) 
    this.OnInitializingNewItem(new InitializingNewItemEventArgs(newItem2)); 
    CommandManager.InvalidateRequerySuggested(); 
    return newItem2; 
} 

正如我們所看到的,4.5版本,DataGrid中確實使用AddNewItem。的CollectionListView.CanAddNewItem內容是簡單的:

public bool CanAddNewItem 
{ 
    get 
    { 
    if (!this.IsEditingItem) 
     return !this.SourceList.IsFixedSize; 
    else 
     return false; 
    } 
} 

所以這並不能解釋爲什麼我們還需要有一個構造函數(即使它是一個虛擬的),以便添加行選項出現。我相信答案在於某些代碼使用CanAddNew而不是CanAddNewItem來確定NewItemPlaceholder行的可見性。這可能被認爲是某種錯誤。