如何使用DataGrid.CanUserAddRows = true的工厂

jbe*_*jbe 19 data-binding wpf datagrid factory wpfdatagrid

我想使用DataGrid.CanUserAddRows = true功能.不幸的是,它似乎只适用于具有默认构造函数的具体类.我的业务对象集合不提供默认构造函数.

我正在寻找一种方法来注册一个知道如何为DataGrid创建对象的工厂.我看了一下DataGrid和ListCollectionView,但它们似乎都不支持我的场景.

Phi*_*hil 29

问题:

"我正在寻找一种方法来注册一个知道如何为DataGrid创建对象的工厂".(因为我的业务对象集合不提供默认构造函数.)

症状:

如果我们设置DataGrid.CanUserAddRows = true然后将项集合绑定到DataGrid,其中项没有默认构造函数,则DataGrid不会显示"新项目行".

原因:

当一个项集合绑定到任何WPF ItemControl时,WPF将集合包装在:

  1. 绑定的集合是一个BindingListCollectionViewBindingList<T>. BindingListCollectionView实现IEditableCollectionView但没有实现IEditableCollectionViewAddNewItem.

  2. 的ListCollectionView当被绑定的集合是任何其他集合.ListCollectionView实现IEditableCollectionViewAddNewItem(因此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;
  }
}
Run Code Online (Sandbox Code Playgroud)

似乎没有一种简单的方法可以覆盖此行为.

对于选项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);
  }
}
Run Code Online (Sandbox Code Playgroud)

非解决方案:

  • DataGrid支持(不提供)

期望DataGrid允许客户端附加回调是合理的,DataGrid将通过该回调请求默认的新项目,就像BindingList<T>上面一样.这将为客户提供在需要时创建新项目的第一个裂缝.

不幸的是,即使在.NET 4.5中也不直接从DataGrid支持.

.NET 4.5似乎确实有一个之前不可用的新事件"AddingNewItem",但这只会让你知道正在添加一个新项目.

解决方法:

  • 由同一程序集中的工具创建的业务对象:使用分部类

这种情况似乎不太可能,但想象实体框架创建的实体类没有默认构造函数(不太可能因为它们不可序列化),那么我们可以简单地创建一个带有默认构造函数的部分类.问题解决了.

  • 业务对象位于另一个程序集中,并且未密封:创建业务对象的超类型.

在这里,我们可以从业务对象类型继承并添加默认构造函数.

这最初似乎是一个好主意,但是第二个想法可能需要更多的工作,因为我们需要将业务层生成的数据复制到业务对象的超类型版本中.

我们需要像这样的代码

class MyBusinessObject : BusinessObject
{
    public MyBusinessObject(BusinessObject bo){ ... copy properties of bo }
    public MyBusinessObject(){}
}
Run Code Online (Sandbox Code Playgroud)

然后一些LINQ在这些对象的列表之间进行投影.

  • 业务对象位于另一个程序集中,并且是密封的(或不密封):封装业务对象.

这更容易

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

    public MyBusinessObject(BusinessObject bo){ BusinessObject = bo;  }
    public MyBusinessObject(){}
}
Run Code Online (Sandbox Code Playgroud)

现在我们需要做的就是使用一些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 ...);};
}
Run Code Online (Sandbox Code Playgroud)

其他方案

  • 在现有集合类型之上实现IEditableCollectionViewAddNewItem.可能需要做很多工作.
  • 继承自ListCollectionView并覆盖功能.我试图尝试这个部分成功,可能需要付出更多努力.

  • 请注意其他人报告BindingList不能很好地扩展http://www.themissingdocs.net/wordpress/?p=465 (2认同)

Gre*_*Ros 8

我找到了另一个解决这个问题的方法.在我的情况下,我的对象需要使用工厂进行初始化,并且没有任何方法可以解决这个问题.

我无法使用,BindingList<T>因为我的收藏必须支持不支持的分组,排序和过滤BindingList<T>.

我通过使用DataGrid的AddingNewItem事件解决了这个问题.这个几乎完全没有记录的事件不仅告诉您正在添加新项目,还允许您选择要添加的项目.AddingNewItem先发生火灾; 简单的NewItem财产.EventArgsnull

即使您为事件提供了处理程序,如果类没有默认构造函数,DataGrid也会拒绝允许用户添加行.然而,奇怪的是(但幸运的是)如果你有一个,并设置了它的NewItem属性AddingNewItemEventArgs,它永远不会被调用.

如果您选择这样做,您可以使用诸如[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;
}
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,在版本中4.5,DataGrid确实可以使用AddNewItem.内容CollectionListView.CanAddNewItem很简单:

public bool CanAddNewItem
{
  get
  {
    if (!this.IsEditingItem)
      return !this.SourceList.IsFixedSize;
    else
      return false;
  }
}
Run Code Online (Sandbox Code Playgroud)

所以这并不能解释为什么我们仍然需要一个构造函数(即使它是一个虚拟的)才能出现add row选项.我相信答案在于一些代码确定NewItemPlaceholder行的可见性CanAddNew而不是CanAddNewItem.这可能被认为是某种错误.


Fre*_*lad 4

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

\n\n

来自MSDN

\n\n
\n

IEditableCollectionViewAddNewItem 接口使应用程序开发人员能够指定要添加到集合中的对象类型。此接口扩展\n IEditableCollectionView,因此您可以\n 添加、编辑和删除集合中的项目。\n IEditableCollectionViewAddNewItem 添加\n AddNewItem 方法,该方法采用\n 添加到\n 的对象。收藏。当您要添加的集合和对象具有以下一个或多个特征时,此方法非常有用:

\n\n
    \n
  • CollectionView 中的对象是不同类型的。
  • \n
  • 这些对象没有默认构造函数。
  • \n
  • 该对象已经存在。
  • \n
  • 您想要将空对象添加到集合中。
  • \n
\n
\n\n

尽管在Bea Stollnitz 博客中,您可以阅读以下内容

\n\n
\n
    \n
  • 当源没有默认构造函数时,无法添加新项目的限制是团队非常了解的。WPF 4.0 Beta 2\n 有一个新功能,使我们离解决方案又近了一步:\n 引入了\n IEditableCollectionViewAddNewItem\n 包含 AddNewItem 方法。您可以阅读有关此功能的 MSDN 文档。MSDN 中的示例显示\n 在创建自己的\n 自定义 UI 时如何使用它来添加新项目(使用\n 列表框显示数据和\n 对话框输入新项目)。\n 来自据我所知,DataGrid 还没有使用此方法(尽管它有点难以 100% 确定,因为 Reflector 没有使用此方法)反编译\n 4.0 Beta 2 位)。
  • \n
\n
\n\n

这个答案来自 2009 年,所以也许它现在可用于 DataGrid

\n

  • 感谢您的精彩回答。ListCollectionView 类实现 IEditableCollectionViewAddNewItem 接口。我通过 Reflector 查看了实现。微软在这个类中做了很多性能优化。我不想仅仅为了使用工厂方法而为自己实现这个接口。 (2认同)