Transposed editable DataGridView

I am trying to bind my objects by columns in the datagridview component but I could not find any way to do it.
This is an example of what I am trying to achieve.

I have the Emp class

public class Emp  
{  
    public int ID { get; set; }  
    public string Name { get; set; }  
    public string City { get; set; }  
  
    public Emp(int id, string name, string city)  
    {  
       this.ID = id;  
       this.Name = name;  
       this.City = city;  
    }  
} 

and an array of Emp

var arrEmp = new[] {  
    new Emp( 1, "Devesh Omar", "Noida"),  
    new Emp( 2, "Roli", "Kanpur"),  
    new Emp( 3, "Roli Gupta", "Mainpuri"),  
    new Emp( 3, "Roli Gupta", "Kanpur"),  
    new Emp( 3, "Devesh Roli ", "Noida"),  
}; 

When I bind the data to the grid

dataGridView1.DataSource = arrEmp; 

I get this (which is ok)

Example Image

I would like that the grid has only 3 fixed rows (Id, Name, City) and the column all the values. (the matrix transpose) Also, if I add or remove an element to/from arrEmp, that the element will be added as a column.


The example was taken from here

Answer

There’s not such a built-in way or built-in component for that.

You need to create your own component, or if you want to use DataGridView for that purpose, you can achieve it by writing custom code. Here I’ve achieved that using a custom TypeDescriptor.

Type descriptor provide information about type, including list of properties and getting and setting property values. DataTable also works the same way, to show list of columns in DataGridView, it returns a list of property descriptors containing properties per column. Here I’ve used such technique.

As you can see in the screen capture, when you edit the rotated list, you are actually editing the original list:

enter image description here

//Set Data Source
dgv.DataSource = new RotatedListDataSource<Employee>(list);

//Hide Column Headers
dgv.ColumnHeadersVisible = false;

//Set Row Headers Autosize
dgv.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders;

//Show PropertyName on RowHeader
dgv.RowPrePaint += (o, a) =>
{
    var value = ((RotatedItem)dgv.Rows[a.RowIndex].DataBoundItem).PropertyName;
    if (a.RowIndex > -1 && $"{dgv.Rows[a.RowIndex].HeaderCell.Value}" != value)
        dgv.Rows[a.RowIndex].HeaderCell.Value = value;
};

If you liked the idea and what to give it a try for learning purpose to see how type descriptor works, here is the code for type descriptor and property descriptors that I created:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
public class RotatedListDataSource<T> : List<RotatedItem>
{
    public List<T> List { get; }
    public RotatedListDataSource(List<T> list)
    {
        List = list;
        this.AddRange(typeof(T).GetProperties().Select(p =>
            new RotatedItem(
                p.Name,
                list.Cast<object>().ToArray(),
                p.PropertyType)));
    }
}
public class RotatedItem : CustomTypeDescriptor
{
    public string PropertyName { get; }
    private object[] data;
    public Type Type { get; }
    public RotatedItem(string propertyName, object[] data, Type type)
    {
        this.PropertyName = propertyName;
        this.data = data;
        this.Type = type;
    }
    public override PropertyDescriptorCollection GetProperties()
    {
        return this.GetProperties(new Attribute[] { });
    }
    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        var properties = new List<PropertyDescriptor>();
        properties.Add(new PropertyNameProperty(new Attribute[] { 
            new BrowsableAttribute(false)}));
        for (int i = 0; i < data.Length; i++)
        {
            properties.Add(new IndexProperty(i, typeof(object), new Attribute[] { }));
        }
        return new PropertyDescriptorCollection(properties.ToArray());
    }
    public object this[int i]
    {
        get => data[i].GetType().GetProperty(PropertyName).GetValue(data[i]);
        set => data[i].GetType().GetProperty(PropertyName).SetValue(
        data[i], Convert.ChangeType(value, Type));
    }
}
public class IndexProperty : PropertyDescriptor
{
    int index;
    Type type;
    public IndexProperty(int index, Type type, Attribute[] attributes) 
        : base(index.ToString(), attributes)
    {
        this.index = index;
        this.type = type;
    }
    public override Type ComponentType => typeof(RotatedItem);
    public override bool IsReadOnly => false;
    public override Type PropertyType => type;
    public override bool CanResetValue(object component) => false;
    public override object GetValue(object component) => 
       ((RotatedItem)component)[index];
    public override void ResetValue(object component) { }
    public override void SetValue(object component, object value) => 
       ((RotatedItem)component)[index] = value;
    public override bool ShouldSerializeValue(object component) => true;
}
public class PropertyNameProperty : PropertyDescriptor
{
    public PropertyNameProperty(Attribute[] attributes)
        : base(nameof(RotatedItem.PropertyName), attributes) { }
    public override Type ComponentType => typeof(RotatedItem);
    public override bool IsReadOnly => true;
    public override Type PropertyType => typeof(string);
    public override bool CanResetValue(object component) => false;
    public override object GetValue(object component) => 
        ((RotatedItem)component).PropertyName;
    public override void ResetValue(object component) { }
    public override void SetValue(object component, object value) { }
    public override bool ShouldSerializeValue(object component) => true;
}

Leave a Reply

Your email address will not be published. Required fields are marked *