2010-12-19 12 views
15

DataGrid.CanUserAddRows = trueフィーチャを使用したいと思います。残念ながら、デフォルトコンストラクタを持つ具象クラスでのみ動作するようです。私のビジネスオブジェクトのコレクションは、デフォルトコンストラクターを提供しません。DataGrid.CanUserAddRows = trueのファクトリを使用する方法

私はDataGridのオブジェクトを作成する方法を知っているファクトリを登録する方法を探しています。私はDataGridとListCollectionViewを見ましたが、それらのどれも私のシナリオをサポートしていないようです。

答えて

4

私はIEditableCollectionViewAddNewItemを見ましたが、この機能を追加しているようです。

MSDN

からIEditableCollectionViewAddNewItem インタフェースは、コレクションに追加する オブジェクトの種類を指定するには、アプリケーション開発者は を可能にします。この インターフェイスは IEditableCollectionViewに拡張されますので、 コレクションのアイテムを追加、編集、および削除することができます。 IEditableCollectionViewAddNewItemは、 コレクションに追加された オブジェクトを取るAddNewItemメソッドを に追加します。

  • CollectionView内のオブジェクトは、さまざまな種類です:あなたが追加したい コレクションとオブジェクトが 次のような特徴の1つ以上を有する場合は、この方法が便利です。
  • オブジェクトにはデフォルトコンストラクタがありません。
  • オブジェクトは既に存在します。
  • nullオブジェクトをコレクションに追加するとします。

Bea Stollnitz blogで、あなたは以下の

  • を読むことができますが、ソースは何 デフォルトコンストラクタを持っていないときに新しい項目を追加することができないの制限は非常によくすることによって理解 ですチーム。 WPF 4.0 Beta 2 には、 という解決策を近づける新しい機能が追加されました。 IEditableCollectionViewAddNewItem AddNewItemメソッドが含まれています。 は、 についてのMSDNのドキュメントを読むことができます。 MSDNのサンプルでは、​​独自の カスタムUIを作成して新しい項目を追加するときに( ListBoxを使用してデータを表示し、 ダイアログボックスを使用して新しい項目を入力する)、その使用方法を で示しています。 DataGridはまだ 4.0ベータ2ビットを分解しないので、100%確実にするのは難しいですが、この方法を使用していません。答え

はので、多分それは今

+2

あなたの素晴らしい答えをありがとう。 ListCollectionViewクラスは、IEditableCollectionViewAddNewItemインターフェイスを実装します。 Reflectorを使って実装を見ました。マイクロソフトはこのクラスで多くのパフォーマンスの最適化を行いました。私は、ファクトリメソッドを使うために、このインターフェースを自分で実装したくありません。 – jbe

+0

@jbe。私は理解しています:)また、IEditableCollectionViewAddNewItemに関する情報がたくさんありませんでした。あなたの仕事を達成する方法を見つけたら、更新してください。 –

0

DataGridの使用可能だ2009年から元クラスのコンストラクタが呼び出されますここで私はデフォルトコンストラクタなしでクラスのラッパーを提供することをお勧め可能性が最も簡単な方法であり、 。 は、たとえば、あなたは、デフォルトコンストラクタなしでこのクラスを持っている:

/// <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; 
} 

分離コード:

/// <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; } 
} 

はそれのためのラッパーを書きます。 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

27

問題:

「私はDataGridのオブジェクトを作成する方法を知っている工場を登録する方法を探しています」 。 (ビジネスオブジェクトの私のコレクションは、デフォルトコンストラクタを提供していませんので。)

症状:

我々はDataGrid.CanUserAddRows = trueを設定し、項目はデフォルトを持っていないデータグリッドにアイテムのコレクションをバインドする場合DataGridに '新しい項目行'は表示されません。拘束されているコレクションがBindingList<T>とき

  1. BindingListCollectionView

    原因:アイテムのコレクションは、任意のWPF ItemControlにバインドされている場合

    、WPFは、いずれかでコレクションをラップします。 BindingListCollectionViewIEditableCollectionViewを実装しますが、IEditableCollectionViewAddNewItemは実装していません。

  2. a ListCollectionViewバインドされているコレクションが他のコレクションである場合。 ListCollectionViewIEditableCollectionViewAddNewItem(したがってIEditableCollectionView)を実装します。

オプション2)の場合、DataGridは新しいアイテムの作成をListCollectionViewに委任します。 ListCollectionViewは内部的にデフォルトコンストラクタの存在をテストし、存在しない場合は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に委譲し、BindingListViewはBindingListに委譲します。 BindingList<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); 
    } 
} 

非ソリューション内の関連するコードは次のとおりです。データグリッドによって

  • サポート(利用できない)

それは予想するのが妥当だろうDataGridを使用して、クライアントが上記のBindingList<T>のようにデフォルトの新しい項目を要求するコールバックをアタッチすることができます。これにより、クライアントが必要なときに新しいアイテムを作成する際の最初の亀裂が生じます。

残念ながら、これは.NET 4.5でもDataGridから直接サポートされていません。

.NET 4.5には以前は利用できなかった新しいイベント「AddingNewItem」が表示されますが、これにより新しい項目が追加されていることがわかります。

回避策:同じアセンブリ内のツールによって作成された

  • ビジネス・オブジェクト:このシナリオは非常に考えにくい

部分クラスを使用しますが、Entity Frameworkのは、そのエンティティクラスを作成したことを想像デフォルトのコンストラクタを持たない(おそらくシリアル化できない可能性は低い)ので、単にデフォルトのコンストラクタを使って部分クラスを作成することができます。問題が解決しました。

  • ビジネスオブジェクトは別のアセンブリにあり、シールされていません。ビジネスオブジェクトのスーパータイプを作成します。

ここでは、ビジネスオブジェクトタイプから継承し、デフォルトのコンストラクタを追加できます。

最初は良いアイデアのようでしたが、2番目の考えでは、ビジネスレイヤーで生成されたデータをビジネスオブジェクトのスーパータイプバージョンにコピーする必要があるため、これ以上の作業が必要になることがあります。

我々は

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

のようなコードが必要になります。そして、いくつかのLINQは、これらのオブジェクトのリスト間で投影します。

  • ビジネスオブジェクトは別のアセンブリ内にあり、シールされているかどうかに関係なく、ビジネスオブジェクトをカプセル化します。

このは、今、私たちが行う必要があるすべては、これらのオブジェクトのリスト間に突出するようにいくつかのLINQを使用して、データグリッドでMyBusinessObject.BusinessObjectに結合している

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

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

はるかに簡単です。プロパティの不規則な折り返しや値のコピーは必要ありません。

ソリューション:我々はいくつかと、BindingList<BusinessObject>でビジネスオブジェクトのコレクションをラップして、これにデータグリッドをバインドする場合に使用BindingList<T>

  • を(万歳が1を見つけました)コード行で問題が解決され、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

7

私はこの問題を解決する別の方法を見つけました。私の場合、私のオブジェクトは工場を使って初期化する必要があり、それを回避する方法はありません。

BindingList<T>はグループ化、並べ替え、フィルタリングをサポートしている必要があります(BindingList<T>はサポートしていません)。

DataGridのAddingNewItemイベントを使用してこの問題を解決しました。これはほぼentirely undocumented eventだけでなく、新しい項目が追加されているだけでなく、allows lets you choose which item is being addedを通知します。 AddingNewItemは何よりも先に発火します。 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; 
    } 
} 

我々は、我々はまだ表示される追加行オプションのために(それはダミーであっても)コンストラクタを持っている必要がある理由だから、これは説明しません。答えは、CanAddNewItemではなく、CanAddNewを使用してNewItemPlaceholder行の可視性を決定するコードにあると思います。これはある種のバグと考えられます。

関連する問題