El problema:
"Estoy buscando una manera de registrar una fábrica que sabe cómo crear los objetos de la cuadrícula de datos" . (Debido a que mi colección de objetos de negocio no proporciona un constructor por defecto.)
Los síntomas:
Si fijamos DataGrid.CanUserAddRows = true
y luego enlazar una colección de elementos de la cuadrícula de datos en el que el elemento no tiene un valor predeterminado constructor, entonces DataGrid no muestra una 'nueva fila de elementos'.
Las causas:
Cuando una colección de elementos está ligado a cualquier ItemControl WPF, WPF envuelve la colección en ya sea:
un BindingListCollectionView cuando la colección estando unido es un BindingList<T>
. BindingListCollectionView
implementa IEditableCollectionView pero no implementa IEditableCollectionViewAddNewItem
.
a ListCollectionView cuando la colección está vinculada es cualquier otra colección. ListCollectionView
implementa IEditableCollectionViewAddNewItem (y por lo tanto IEditableCollectionView
).
Para la opción 2) el DataGrid delega la creación de nuevos elementos en el ListCollectionView
. ListCollectionView
prueba internamente la existencia de un constructor predeterminado y deshabilita AddNew
si no existe. Aquí está el código relevante de ListCollectionView usando DotPeek.
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;
}
}
No parece haber una manera fácil de anular este comportamiento.
Para la opción 1) la situación es mucho mejor. El DataGrid delega la creación de nuevos elementos en BindingListView, que a su vez delega en BindingList. BindingList<T>
también comprueba la existencia de un constructor predeterminado, pero afortunadamente BindingList<T>
también permite al cliente establecer la propiedad AllowNew y asociar un controlador de eventos para el suministro de un nuevo elemento.Ver la solución más tarde, pero aquí está el código relevante en 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);
}
}
no-soluciones:
- El apoyo de cuadrícula de datos (no disponible)
Sería razonable esperar que el DataGrid para permitir al cliente adjuntar una devolución de llamada, a través de la cual DataGrid solicitaría un nuevo elemento predeterminado, al igual que BindingList<T>
anterior. Esto le daría al cliente la primera grieta en la creación de un nuevo elemento cuando se requiera uno.
Desafortunadamente, esto no se admite directamente desde DataGrid, incluso en .NET 4.5.
.NET 4.5 parece tener un nuevo evento 'AddingNewItem' que no estaba disponible anteriormente, pero esto solo le permite saber que se está agregando un nuevo elemento.
arounds de trabajo:
- objeto de negocios creado por una herramienta en el mismo conjunto: utilizar una clase parcial
parece muy poco probable este escenario, pero imagina que Entity Framework creado sus clases de entidad sin un constructor predeterminado (no es probable, ya que no serían serializables), entonces podríamos simplemente crear una clase parcial con un constructor predeterminado. Problema resuelto.
- El objeto comercial está en otro ensamblaje y no está sellado: cree un super-tipo del objeto comercial.
Aquí podemos heredar del tipo de objeto de negocio y agregar un constructor predeterminado.
Inicialmente, esto pareció una buena idea, pero, reflexionando sobre esto, esto puede requerir más trabajo de lo necesario porque necesitamos copiar los datos generados por la capa empresarial en nuestra versión super-tipo del objeto comercial.
Necesitaríamos un código como
class MyBusinessObject : BusinessObject
{
public MyBusinessObject(BusinessObject bo){ ... copy properties of bo }
public MyBusinessObject(){}
}
Y a continuación, algunos de LINQ para proyectar entre las listas de estos objetos.
- El objeto comercial está en otro ensamblaje y está sellado (o no): encapsula el objeto comercial.
Esto es mucho más fácil
class MyBusinessObject
{
public BusinessObject{ get; private set; }
public MyBusinessObject(BusinessObject bo){ BusinessObject = bo; }
public MyBusinessObject(){}
}
Ahora todo lo que tenemos que hacer es usar alguna LINQ para proyectar entre las listas de estos objetos, y luego se unen a MyBusinessObject.BusinessObject
en la cuadrícula de datos. No es necesario envolver sus propiedades o copiar valores.
La solución: (hurra encontró uno)
Si envolvemos nuestra colección de objetos de negocio en un BindingList<BusinessObject>
y luego obligar a la cuadrícula de datos a esto, con unos pocos líneas de código, nuestro problema está resuelto y DataGrid mostrará apropiadamente una nueva fila de elementos.
public void BindData()
{
var list = new BindingList<BusinessObject>(GetBusinessObjects());
list.AllowNew = true;
list.AddingNew += (sender, e) =>
{e.NewObject = new BusinessObject(... some default params ...);};
}
Otras soluciones
- implementan IEditableCollectionViewAddNewItem en la parte superior de un tipo de colección existente. Probablemente mucho trabajo.
- heredan de ListCollectionView y anulan la funcionalidad. Tuve parcialmente éxito al intentar esto, probablemente se puede hacer con más esfuerzo.
Gracias por su gran respuesta. La clase ListCollectionView implementa la interfaz IEditableCollectionViewAddNewItem. Eché un vistazo a la implementación a través de Reflector.Microsoft hizo muchas optimizaciones de rendimiento en esta clase. No quiero implementar esta interfaz solo para usar un método de fábrica. – jbe
@jbe. Entiendo eso :) Además, no había mucha información sobre IEditableCollectionViewAddNewItem, al menos no que pude encontrar. Asegúrese de actualizar si encuentra una manera de lograr su tarea –