Need to sort columns in DataGridView

Euless_Tech
Euless_Tech used Ask the Experts™
on
I am using a List<> to populate a DataGridView like this:

List<object> myList = GetObjects();
DataGridView.DataSource = myList;

I would like to be able to sort the DataGridView columns without using a BindingList by clicking on the column heading. Is that possible? I've got to believe in this day and age, there are better ways to do this.

Ideas?
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Commented:
Probably the easiest way to accomplish your requirements is to convert your list into a Datatable since the DataTable type supports basic datatype sorting out of the box.

Form1.cs -
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Windows.Forms;

namespace EE_Q29012407
{
    public partial class Form1 : Form
    {
        readonly List<Person> people = new List<Person>()
        {
            new Person() { ID = 1, FirstName = "John", MiddleName = "Paul", LastName = "Jones", Birthdate = new DateTime(1972, 1, 23) },
            new Person() { ID = 2, FirstName = "Craig", MiddleName = "The", LastName = "List", Birthdate = new DateTime(1994, 3, 18) },
            new Person() { ID = 3, FirstName = "Peter", MiddleName = "James", LastName = "Tyler", Birthdate = new DateTime(1988, 12, 6) },
            new Person() { ID = 4, FirstName = "Mike", MiddleName = "Douglas", LastName = "Beals", Birthdate = new DateTime(1962, 2, 12) },
            new Person() { ID = 5, FirstName = "Seymour", MiddleName = "Anthony", LastName = "Niles", Birthdate = new DateTime(1917, 7, 4) }
        };

        public Form1()
        {
            InitializeComponent();
        }

        private void OnColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            if (sender is DataGridView)
            {
                var grid = sender as DataGridView;
                var newColumn = grid.Columns[e.ColumnIndex];
                var oldColumn = grid.SortedColumn;
                var direction = default(ListSortDirection);

                if (oldColumn != null)
                {
                    if (oldColumn.Equals(newColumn) && grid.SortOrder == SortOrder.Descending)
                        direction = ListSortDirection.Ascending;
                    else
                    {
                        direction = ListSortDirection.Descending;
                        oldColumn.HeaderCell.SortGlyphDirection = SortOrder.None;
                    }
                }
                else
                    direction = ListSortDirection.Descending;

                grid.Sort(newColumn, direction);
                newColumn.HeaderCell.SortGlyphDirection = (direction == ListSortDirection.Descending) ? SortOrder.Descending : SortOrder.Ascending;
            }
        }

        private void OnDataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
        {
            if (sender is DataGridView)
            {
                var grid = sender as DataGridView;
                if (grid.Rows.Count > 0 && grid.CurrentCell != null)
                    grid.BeginEdit(true);
                foreach (DataGridViewColumn column in grid.Columns)
                    column.SortMode = DataGridViewColumnSortMode.Programmatic;
                foreach (DataGridViewRow row in grid.SelectedRows)
                    row.Selected = false;
                grid.EndEdit();
            }
        }

        private void OnLoad(object sender, EventArgs e)
        {
            dgvData.DataSource = people.ConvertToDataTable();
        }
    }

    class Person
    {
        public int ID { get; set; }
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public DateTime Birthdate { get; set; }

        public string Age { get { return string.Format("{0} Years Old", DateTime.Now.Year - Birthdate.Year); } }

        public override string ToString()
        {
            return string.Format("{0} {1}. {2} [Age: {3}]", FirstName, MiddleName, LastName, Age);
        }
    }

    static class Extensions
    {
        public static DataTable ConvertToDataTable<T>(this IEnumerable<T> source)
        {
            var properties = TypeDescriptor.GetProperties(typeof(T));
            var table = new DataTable();
            var property = default(PropertyDescriptor);

            for (int i = 0; i < properties.Count - 1; i++)
            {
                property = properties[i];
                if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable)))
                    table.Columns.Add(property.Name, property.PropertyType.GetGenericArguments()[0]);
                else
                    table.Columns.Add(property.Name, property.PropertyType);
            }

            object[] values = new object[properties.Count - 1];
            foreach (var item in source)
            {
                for (int i = 0; i < properties.Count - 1; i++)
                    values[i] = properties[i].GetValue(item);
                table.Rows.Add(values);
            }

            return table;
        }
    }
}

Open in new window

Form1.Designer.cs -
namespace EE_Q29012407
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.dgvData = new System.Windows.Forms.DataGridView();
            ((System.ComponentModel.ISupportInitialize)(this.dgvData)).BeginInit();
            this.SuspendLayout();
            // 
            // dgvData
            // 
            this.dgvData.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.dgvData.Dock = System.Windows.Forms.DockStyle.Fill;
            this.dgvData.Location = new System.Drawing.Point(0, 0);
            this.dgvData.Name = "dgvData";
            this.dgvData.Size = new System.Drawing.Size(544, 261);
            this.dgvData.TabIndex = 0;
            this.dgvData.ColumnHeaderMouseClick += new System.Windows.Forms.DataGridViewCellMouseEventHandler(this.OnColumnHeaderMouseClick);
            this.dgvData.DataBindingComplete += new System.Windows.Forms.DataGridViewBindingCompleteEventHandler(this.OnDataBindingComplete);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.AutoSize = true;
            this.ClientSize = new System.Drawing.Size(544, 261);
            this.Controls.Add(this.dgvData);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.OnLoad);
            ((System.ComponentModel.ISupportInitialize)(this.dgvData)).EndInit();
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.DataGridView dgvData;
    }
}

Open in new window

Produces the following output -
Initial load -
Sorting by birthdate -

That being said, the problem with this is that you get no direct binding to the original datasource -Capture.PNG
This means that in order to write back changes to the database, you must traverse the datatable of the gridview and determine if you have any dirty rows, for those dirty rows, you now must update the data in the list if it is used as a datasource elsewhere or update the data in the, presumably, database; e.g. -

Form1.cs -
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Windows.Forms;

namespace EE_Q29012407
{
    public partial class Form1 : Form
    {
        readonly List<Person> people = new List<Person>()
        {
            new Person() { ID = 1, FirstName = "John", MiddleName = "Paul", LastName = "Jones", Birthdate = new DateTime(1972, 1, 23) },
            new Person() { ID = 2, FirstName = "Craig", MiddleName = "The", LastName = "List", Birthdate = new DateTime(1994, 3, 18) },
            new Person() { ID = 3, FirstName = "Peter", MiddleName = "James", LastName = "Tyler", Birthdate = new DateTime(1988, 12, 6) },
            new Person() { ID = 4, FirstName = "Mike", MiddleName = "Douglas", LastName = "Beals", Birthdate = new DateTime(1962, 2, 12) },
            new Person() { ID = 5, FirstName = "Seymour", MiddleName = "Anthony", LastName = "Niles", Birthdate = new DateTime(1917, 7, 4) }
        };

        public Form1()
        {
            InitializeComponent();
        }

        private void OnColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            if (sender is DataGridView)
            {
                var grid = sender as DataGridView;
                var newColumn = grid.Columns[e.ColumnIndex];
                var oldColumn = grid.SortedColumn;
                var direction = default(ListSortDirection);

                if (oldColumn != null)
                {
                    if (oldColumn.Equals(newColumn) && grid.SortOrder == SortOrder.Descending)
                        direction = ListSortDirection.Ascending;
                    else
                    {
                        direction = ListSortDirection.Descending;
                        oldColumn.HeaderCell.SortGlyphDirection = SortOrder.None;
                    }
                }
                else
                    direction = ListSortDirection.Descending;

                grid.Sort(newColumn, direction);
                newColumn.HeaderCell.SortGlyphDirection = (direction == ListSortDirection.Descending) ? SortOrder.Descending : SortOrder.Ascending;
            }
        }

        private void OnDataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
        {
            if (sender is DataGridView)
            {
                var grid = sender as DataGridView;
                if (grid.Rows.Count > 0 && grid.CurrentCell != null)
                    grid.BeginEdit(true);
                foreach (DataGridViewColumn column in grid.Columns)
                    column.SortMode = DataGridViewColumnSortMode.Programmatic;
                foreach (DataGridViewRow row in grid.SelectedRows)
                    row.Selected = false;
                grid.EndEdit();
            }
        }

        private void OnLoad(object sender, EventArgs e)
        {
            dgvData.DataSource = people.ConvertToDataTable();
        }

        private void OnClick(object sender, EventArgs e)
        {
            if (dgvData.Rows.Count > 0)
            {
                foreach (var row in dgvData.Rows.Cast<DataGridViewRow>().Where(r => r.DataBoundItem != null && (r.DataBoundItem as DataRowView).Row.RowState == DataRowState.Modified))
                {
                    row.DefaultCellStyle.BackColor = System.Drawing.Color.Red;
                    row.DefaultCellStyle.ForeColor = System.Drawing.Color.White;
                }
                foreach (var row in dgvData.Rows.Cast<DataGridViewRow>().Where(r => r.DataBoundItem != null && (r.DataBoundItem as DataRowView).Row.RowState == DataRowState.Added))
                {
                    row.DefaultCellStyle.BackColor = System.Drawing.Color.Green;
                    row.DefaultCellStyle.ForeColor = System.Drawing.Color.Black;
                }
                foreach (var row in dgvData.Rows.Cast<DataGridViewRow>().Where(r => r.DataBoundItem != null && (r.DataBoundItem as DataRowView).Row.RowState == DataRowState.Unchanged))
                {
                    row.DefaultCellStyle.BackColor = System.Drawing.Color.Blue;
                    row.DefaultCellStyle.ForeColor = System.Drawing.Color.White;
                }
            }
        }
    }

    class Person
    {
        public int ID { get; set; }
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public DateTime Birthdate { get; set; }

        public string Age { get { return string.Format("{0} Years Old", DateTime.Now.Year - Birthdate.Year); } }

        public override string ToString()
        {
            return string.Format("{0} {1}. {2} [Age: {3}]", FirstName, MiddleName, LastName, Age);
        }
    }

    static class Extensions
    {
        public static DataTable ConvertToDataTable<T>(this IEnumerable<T> source)
        {
            var properties = TypeDescriptor.GetProperties(typeof(T));
            var table = new DataTable();
            var property = default(PropertyDescriptor);

            for (int i = 0; i < properties.Count - 1; i++)
            {
                property = properties[i];
                if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable)))
                    table.Columns.Add(property.Name, property.PropertyType.GetGenericArguments()[0]);
                else
                    table.Columns.Add(property.Name, property.PropertyType);
            }

            object[] values = new object[properties.Count - 1];
            foreach (var item in source)
            {
                for (int i = 0; i < properties.Count - 1; i++)
                    values[i] = properties[i].GetValue(item);
                table.Rows.Add(values);
            }
            table.AcceptChanges();

            return table;
        }
    }
}

Open in new window

Form1.Designer.cs -
namespace EE_Q29012407
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.dgvData = new System.Windows.Forms.DataGridView();
            this.button1 = new System.Windows.Forms.Button();
            ((System.ComponentModel.ISupportInitialize)(this.dgvData)).BeginInit();
            this.SuspendLayout();
            // 
            // dgvData
            // 
            this.dgvData.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.dgvData.Dock = System.Windows.Forms.DockStyle.Top;
            this.dgvData.Location = new System.Drawing.Point(0, 0);
            this.dgvData.Name = "dgvData";
            this.dgvData.Size = new System.Drawing.Size(544, 261);
            this.dgvData.TabIndex = 0;
            this.dgvData.ColumnHeaderMouseClick += new System.Windows.Forms.DataGridViewCellMouseEventHandler(this.OnColumnHeaderMouseClick);
            this.dgvData.DataBindingComplete += new System.Windows.Forms.DataGridViewBindingCompleteEventHandler(this.OnDataBindingComplete);
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(407, 271);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(125, 23);
            this.button1.TabIndex = 1;
            this.button1.Text = "Identify Dirty Rows";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.OnClick);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.AutoSize = true;
            this.ClientSize = new System.Drawing.Size(544, 306);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.dgvData);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.OnLoad);
            ((System.ComponentModel.ISupportInitialize)(this.dgvData)).EndInit();
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.DataGridView dgvData;
        private System.Windows.Forms.Button button1;
    }
}

Open in new window


Produces the following output -Capture.PNG
Then you must reupdate your list(s) with the data reread from the database.  Ultimately, the better solution *is* to use either a sortablebindinglist (as recommended by anarki) or use a data structure that is directly derived from your datasource, entity framework comes to mind.

-saige-

Author

Commented:
Saige,
I do not have to write anything back to the database - this datagridview is only for viewing purposes. I will give your solution a try and let you know the outcome.

Author

Commented:
Thanks Saige! Great solution

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial