2010-12-19 10 views
15

Me gustaría utilizar la función 0 DataGrid.CanUserAddRows = true. Desafortunadamente, parece funcionar solo con clases concretas que tienen un constructor predeterminado. Mi colección de objetos comerciales no proporciona un constructor predeterminado.Cómo utilizar una fábrica para DataGrid.CanUserAddRows = true

Estoy buscando una forma de registrar una fábrica que sepa cómo crear los objetos para DataGrid. Eché un vistazo a DataGrid y ListCollectionView, pero ninguno de ellos parece ser compatible con mi escenario.

Respuesta

4

Eché un vistazo a IEditableCollectionViewAddNewItem y parece agregar esta funcionalidad.

De MSDN

La interfaz IEditableCollectionViewAddNewItem permite la aplicación desarrolladores para especificar qué tipo de objeto para añadir a una colección. Esta interfaz se extiende a IEditableCollectionView, por lo que puede agregar, editar y eliminar elementos en una colección . IEditableCollectionViewAddNewItem agrega el método AddNewItem, que toma un objeto que se agrega a la colección . Este método es útil cuando la colección y los objetos que desea agregar tienen una o más de las siguientes características :

  • Los objetos en el CollectionView son diferentes tipos.
  • Los objetos no tienen un constructor predeterminado.
  • El objeto ya existe.
  • Desea agregar un objeto nulo a la colección.

Aunque en Bea Stollnitz blog, se puede leer la siguiente

  • La limitación de no ser capaz de añadir un nuevo elemento cuando la fuente no tiene constructor por defecto y es muy bien entendido por el equipo. WPF 4.0 Beta 2 tiene una nueva característica que nos brinda un paso más cerca de tener una solución: introducción de IEditableCollectionViewAddNewItem que contiene el método AddNewItem. Usted puede leer la documentación de MSDN sobre esta característica. El ejemplo en MSDN muestra cómo usarlo al crear su propia interfaz de usuario personalizada para agregar un nuevo elemento (usando un ListBox para mostrar los datos y un cuadro de diálogo para ingresar el nuevo elemento). Por lo que puedo decir, DataGrid no usa aunque use este método (aunque es un poco difícil estar 100% seguro porque Reflector no descompila 4.0 Beta 2 bits).

Esa respuesta es a partir de 2009 así que quizás es utilizable para la cuadrícula de datos ahora

+2

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

+0

@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 –

0

La forma más sencilla que podría sugerir para proporcionar envoltorio para su clase sin constructor predeterminado, en el que será llamado constructor de clase de origen . Por ejemplo usted tiene esta clase sin constructor predeterminado:

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

Escribir un envoltorio para ello:

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

Codebehind. En su ViewModel necesita crear una colección de contenedor para su colección de origen, que manejará la adición/eliminación de elementos en la cuadrícula de datos.

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

Y, por último, el código 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

Ni siquiera necesita un contenedor. Simplemente podría heredar de la clase existente y proporcionar un constructor predeterminado. – Phil

27

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:

  1. un BindingListCollectionView cuando la colección estando unido es un BindingList<T>. BindingListCollectionView implementa IEditableCollectionView pero no implementa IEditableCollectionViewAddNewItem.

  2. 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)

  • Uso BindingList<T>

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.
+1

Tenga en cuenta que otros informan que BindingList no escala bien http://www.themissingdocs.net/wordpress/?p=465 – gap

7

He encontrado otra solución a este problema. En mi caso, mis objetos necesitan ser inicializados usando una fábrica, y realmente no hay forma de evitar eso.

No pude usar BindingList<T> porque mi colección debe ser compatible con la agrupación, la clasificación y el filtrado, que BindingList<T> no es compatible.

He resuelto el problema utilizando el evento AddingNewItem de DataGrid. Esta casi entirely undocumented event no solo le dice que se está agregando un nuevo artículo, sino también allows lets you choose which item is being added. AddingNewItem incendios antes que cualquier otra cosa; la propiedad NewItem del EventArgs es simplemente null.

Incluso si proporciona un controlador para el evento, DataGrid se negará a permitir que el usuario agregue filas si la clase no tiene un constructor predeterminado. Sin embargo, extrañamente (afortunadamente) si tiene uno, y establece la propiedad NewItem del AddingNewItemEventArgs, nunca será llamado.

Si elige hacer esto, puede hacer uso de atributos como [Obsolete("Error", true)] y [EditorBrowsable(EditorBrowsableState.Never)] para asegurarse de que nadie invoque el constructor. También puede hacer que el cuerpo constructor genere una excepción

Descompilar el control nos permite ver qué está sucediendo allí.

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

Como podemos ver, en la versión 4.5, la cuadrícula de datos en efecto, hacer uso de AddNewItem. El contenido de CollectionListView.CanAddNewItem son simplemente:

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

Así que esto no explica por qué todavía tenemos que tener un constructor (incluso si se trata de un ficticio) a fin de que la opción fila añadir a aparecer. Creo que la respuesta se encuentra en algún código que determina la visibilidad de la fila NewItemPlaceholder usando CanAddNew en lugar de CanAddNewItem. Esto podría ser considerado algún tipo de error.

Cuestiones relacionadas