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将集合包装在:
绑定的集合是一个BindingListCollectionViewBindingList<T>. BindingListCollectionView实现IEditableCollectionView但没有实现IEditableCollectionViewAddNewItem.
一的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将通过该回调请求默认的新项目,就像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)
其他方案
我找到了另一个解决这个问题的方法.在我的情况下,我的对象需要使用工厂进行初始化,并且没有任何方法可以解决这个问题.
我无法使用,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.这可能被认为是某种错误.
我查看了IEditableCollectionViewAddNewItem,它似乎正在添加此功能。
\n\n来自MSDN
\n\n\n\n\nIEditableCollectionViewAddNewItem 接口使应用程序开发人员能够指定要添加到集合中的对象类型。此接口扩展\n IEditableCollectionView,因此您可以\n 添加、编辑和删除集合中的项目。\n IEditableCollectionViewAddNewItem 添加\n AddNewItem 方法,该方法采用\n 添加到\n 的对象。收藏。当您要添加的集合和对象具有以下一个或多个特征时,此方法非常有用:
\n\n\n
\n- CollectionView 中的对象是不同类型的。
\n- 这些对象没有默认构造函数。
\n- 该对象已经存在。
\n- 您想要将空对象添加到集合中。
\n
尽管在Bea Stollnitz 博客中,您可以阅读以下内容
\n\n\n\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
这个答案来自 2009 年,所以也许它现在可用于 DataGrid
\n