none
DataGridViewComboBoxColumn con ObjectDataSources RRS feed

  • Pregunta

  • Buenas a todos,

    Tengo un proyecto en el cual tengo un DataGridView que se alimenta de un ObjectDataSource a través de un BindingSource. Este DataGridView tiene una columna DataGridViewComboBoxColumn que a su vez se alimenta de otro ObjectDataSource. Entre las dos clases hay una relación padre hijo implementada como una composición donde el hijo tiene una propiedad Padre que contiene el objeto padre. Cada clase tiene métodos para obtener una colección en forma de List<> como se muestra:

        class Padre:IEquatable<Padre>
        {
            private static List<Padre> padres = (from i in Enumerable.Range(1, 8)
                                                 select new Padre() { Nombre = "Padre " + i.ToString() }).ToList<Padre>();
            public static List<Padre> TodosLosPadres()
            {
                return padres;
            }        
            public string Nombre { get; set; }
            public bool Equals(Padre other)
            {
                return this.Nombre.Equals(other.Nombre);
            }
            public override string ToString()
            {
                return this.Nombre;
            }
        }
    
        class Hijo : IEquatable<Hijo>
        {
            public string Nombre { get; set; }
            public Padre Padre { get; set; }
            public bool Equals(Hijo other)
            {
                return this.Nombre.Equals(other.Nombre);
            }
            public static List<Hijo> TodosLosHijos()
            {
                return (from i in Enumerable.Range(1, 100)
                        select new Hijo()
                        {
                            Nombre = "Hijo " + i.ToString("000"),
                            Padre = Padre.TodosLosPadres()[i % 8]
                        }).ToList<Hijo>();
            }
        }
    

    La columna ComboBox se inicializa de esta forma:

                this.padreDataGridViewTextBoxColumn.DataPropertyName = "Padre";
                this.padreDataGridViewTextBoxColumn.DataSource = this.padreBindingSource;
                this.padreDataGridViewTextBoxColumn.DisplayMember = "Nombre";
                this.padreDataGridViewTextBoxColumn.HeaderText = "Padre";
                this.padreDataGridViewTextBoxColumn.Name = "padreDataGridViewTextBoxColumn";
                this.padreDataGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.True;
                this.padreDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
                this.padreDataGridViewTextBoxColumn.Width = 250;
    

    Y en el Load del formulario hago lo siguiente:

     private void Form1_Load(object sender, EventArgs e)
            {
                padreBindingSource.DataSource = Padre.TodosLosPadres();
                hijoBindingSource.DataSource = Hijo.TodosLosHijos();
            }

    Lo que ocurre es que al ejecutar el código, salta repetidamente una excepción System.FormatException: DataGridViewComboBoxColumn value is invalid.

    Según he investigado, este error ocurre cuando el enumerable de Hijos tiene algún Padre que no esté en el enumerable de Padres. No es este el caso ya que todos los hijos tienen un padre válido. Supongo que hay algún problema con el ValueMember de la columna. Yo pensaría que al dejarlo sin asignar, el ValueMember sería el mismo objeto, pero no logro hacer que funcione... y pareciera algo sumamente fácil.

    ¿Qué estoy haciendo mal?


    logo osoft
    Si he contestado tu pregunta, por favor marca mi post como respuesta.
    ...Y si mi post te ha servido, márcalo como útil smile

    domingo, 20 de enero de 2013 17:53

Respuestas

  • algo no entiendo, si la columna es del tipo combo porque la variable se llama

    padreDataGridViewTextBoxColumn

    porque ..TextBoxColumn ? no es que seria un DataGridViewComboBoxColumn

    -----

    porque usas esta linea

    padreDataGridViewTextBoxColumn.DataPropertyName = "Padre";

    cuando deberia ser el nombre de la propiedad

    padreDataGridViewTextBoxColumn.DataPropertyName = "Nombre";

    ------

    no deberias definir un

    padreDataGridViewTextBoxColumn.ValueMember = "Nombre";

    para que el DatapropertyName tome efecto

    saludos


    Leandro Tuttini

    Blog
    Buenos Aires
    Argentina

    • Marcado como respuesta Yván Ecarri martes, 22 de enero de 2013 11:39
    domingo, 20 de enero de 2013 19:11
  • He solucionado el problema y creo que puede ser de utilidad a otros.

    Recapitulando: el problema es que tenía una composición con las clases Padre e Hijo donde Hijo tiene una propiedad Padre de la clase Padre y quería implementar un DataGridView con una DataGridViewComboBoxColumn para mostrar y seleccionar el atributo Padre de cada hijo, pero daba un error:

    System.FormatException: DataGridViewComboBoxColumn value is invalid.

    El problema se presenta si la instancia del objeto Padre en el objeto Hijo no es exactamente la misma instancia que la que hay en el enumerable que sirve como DataSource para el DataGridViewComboBox. En mi caso, esto ocurría porque yo no mantengo la colección en memoria, sino que creo una instancia del objeto cada vez que lo leo de base de datos. El DataGridView usa el método Equals de Object para encontrar en la colección el objeto que coincide con el valor de la propiedad cuando se está usando databinding, pero la comparación entre objetos es por referencia y no por valor, así que dos instancias de la misma clase con los mismos datos no reportan Verdadero cuando se llama a object.Equals.

    La solución es muy fácil: La clase padre debe sobreescribir el método Equals.

    public class Padre
    {
       // Código de la clase...
    
       public override bool Equals(Object obj)
       {
          // Código para que devuelva Verdadero si los dos objetos son lógicamente iguales
       }
    }
    

    Hay que destacar que NO FUNCIONA implementar IEquatable<T> porque el control se apoya en el método Equals de Object, no en el de IEquatable y técnicamente el de IEquatable no sobreescribe al de object, aunque cuando haces la llamada directamente por código pasándole un objeto de clase T el método que se llama es el que implementa IEquatable.

    Espero que a alguien le sirva y le ahorre un quebradero de cabeza :)


    logo osoft
    Si he contestado tu pregunta, por favor marca mi post como respuesta.
    ...Y si mi post te ha servido, márcalo como útil smile

    • Marcado como respuesta Yván Ecarri sábado, 26 de enero de 2013 11:49
    sábado, 26 de enero de 2013 11:48

Todas las respuestas

  • algo no entiendo, si la columna es del tipo combo porque la variable se llama

    padreDataGridViewTextBoxColumn

    porque ..TextBoxColumn ? no es que seria un DataGridViewComboBoxColumn

    -----

    porque usas esta linea

    padreDataGridViewTextBoxColumn.DataPropertyName = "Padre";

    cuando deberia ser el nombre de la propiedad

    padreDataGridViewTextBoxColumn.DataPropertyName = "Nombre";

    ------

    no deberias definir un

    padreDataGridViewTextBoxColumn.ValueMember = "Nombre";

    para que el DatapropertyName tome efecto

    saludos


    Leandro Tuttini

    Blog
    Buenos Aires
    Argentina

    • Marcado como respuesta Yván Ecarri martes, 22 de enero de 2013 11:39
    domingo, 20 de enero de 2013 19:11
  • Hola Leandro, gracias por tu respuesta.

    algo no entiendo, si la columna es del tipo combo porque la variable se llama padreDataGridViewTextBoxColumn 

    Tienes razón. Lo que pasa es que la columna se creó cuando asigné el BindingSource en el diseñador y no le cambié el nombre.

    porque usas esta linea padreDataGridViewTextBoxColumn.DataPropertyName = "Padre";

    El objeto Hijo tiene una propiedad llamada "Padre". Lo que quiero es que al seleccionar en el combo box le asigne a la propiedad Hijo.Padre el objeto seleccionado. Si lo hago como tú sugieres me parece que le estaría asignando a la propiedad Hijo.Nombre el valor seleccionado. Puede que yo me equivoque :-/

    Si defino el ValueMember="Nombre" lo que voy a obtener es asignarle el valor de Padre.Nombre a Hijo.Nombre. Esto no es lo que quiero. Lo que busco es asignarle a Hijo.Padre el objeto padre.

    De momento lo he logrado solucionar creando una propiedad YoMismo de tipo Padre en la clase Padre:

    public Padre YoMismo { get { return this; } }

    Luego, le asigno a ValueMember="YoMismo" y a DataPropertyName="Padre" y de esa forma funciona. La verdad me parece una solución muy poco elegante pero entiendo que la implementación espera que haya un nombre de propiedad y si no lo hay devuelve el valor de ToString()

    Ahora tengo otro problema con las mismas clases (en realidad no se llaman Padre e Hijo, lo he puesto de esa forma para que fuera más fácil de entender). Mi problema es que Padre deriva de SuperPadre e hijo deriva de SuperHijo así que la propiedad Padre del objeto hijo de clase Hijo realmente está devolviendo un SuperPadre y no un Padre por lo que la comparación con los objetos de la colección de Padres da un error.... ¿Qué me recomiendas? ¿Declaro la propiedad Padre en SuperHijo virtual y la sobreescribo en Hijo?




    logo osoft
    Si he contestado tu pregunta, por favor marca mi post como respuesta.
    ...Y si mi post te ha servido, márcalo como útil smile

    domingo, 20 de enero de 2013 20:00
  • He solucionado el problema y creo que puede ser de utilidad a otros.

    Recapitulando: el problema es que tenía una composición con las clases Padre e Hijo donde Hijo tiene una propiedad Padre de la clase Padre y quería implementar un DataGridView con una DataGridViewComboBoxColumn para mostrar y seleccionar el atributo Padre de cada hijo, pero daba un error:

    System.FormatException: DataGridViewComboBoxColumn value is invalid.

    El problema se presenta si la instancia del objeto Padre en el objeto Hijo no es exactamente la misma instancia que la que hay en el enumerable que sirve como DataSource para el DataGridViewComboBox. En mi caso, esto ocurría porque yo no mantengo la colección en memoria, sino que creo una instancia del objeto cada vez que lo leo de base de datos. El DataGridView usa el método Equals de Object para encontrar en la colección el objeto que coincide con el valor de la propiedad cuando se está usando databinding, pero la comparación entre objetos es por referencia y no por valor, así que dos instancias de la misma clase con los mismos datos no reportan Verdadero cuando se llama a object.Equals.

    La solución es muy fácil: La clase padre debe sobreescribir el método Equals.

    public class Padre
    {
       // Código de la clase...
    
       public override bool Equals(Object obj)
       {
          // Código para que devuelva Verdadero si los dos objetos son lógicamente iguales
       }
    }
    

    Hay que destacar que NO FUNCIONA implementar IEquatable<T> porque el control se apoya en el método Equals de Object, no en el de IEquatable y técnicamente el de IEquatable no sobreescribe al de object, aunque cuando haces la llamada directamente por código pasándole un objeto de clase T el método que se llama es el que implementa IEquatable.

    Espero que a alguien le sirva y le ahorre un quebradero de cabeza :)


    logo osoft
    Si he contestado tu pregunta, por favor marca mi post como respuesta.
    ...Y si mi post te ha servido, márcalo como útil smile

    • Marcado como respuesta Yván Ecarri sábado, 26 de enero de 2013 11:49
    sábado, 26 de enero de 2013 11:48