Need to sort columns in DataGridView

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?
Euless_TechAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

anarki_jimbelSenior DeveloperCommented:
0
it_saigeDeveloperCommented:
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-
1

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Euless_TechAuthor 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.
0
Euless_TechAuthor Commented:
Thanks Saige! Great solution
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
DataGridView

From novice to tech pro — start learning today.