Click here to Skip to main content
15,891,864 members
Articles / Desktop Programming / Windows Forms

DataGridView that Saves Column Order, Width and Visibility to user.config

Rate me:
Please Sign up or sign in to vote.
4.64/5 (19 votes)
29 Jul 2009CPOL3 min read 137.6K   6.9K   76   30
Enhanced DataGridView that saves column order, width and visibility to user.config
gfoidl.DataGridView0.jpg

Image 2

Introduction

The DataGridView shipped with the framework allows the user to reorder the columns but there is nothing to persist the new column order.

In this article, I'll show an enhanced DataGridView that saves:

  • column order
  • column width
  • column visibility

to the user.config without the need to code something in the hosting form.

Background 

Usually the application and user-settings are created via the designer in Visual Studio. Behind the scenes, the designer generates code for accessing the settings.

For this control, the code for accessing the settings is generated manually so it's possible to use more advanced datatypes than the few primitive ones provided in the designer.

Because it is possible to have more than one DGV in the client application the settings are stored with the use of a dictionary. The key of the dictionary is the name of the control.

Binary serialization is used because the dictionary implements the IDictionary interface and thus it is not possible to use XML serialization. But this doesn't matter - the result is the same. The only difference is that the information in the user.config is not as readable as with XML serialization.

In .NET assemblies are loaded into an AppDomain and executed from there. Each AppDomain has one configuration-file (not necessarily) and therefore the data of several configuration classes get serialized into this config-file (or read from).

Using the Control 

The usage of the control is not different from the System.Windows.Form.DataGridView.

The Code of the Control

The control is derived from the System.Windows.Form.DataGridView. So everything the DataGridView provides can be provided by this control too.

Attributes of the Class

C#
[Description("Extension of the System.Windows.Forms.DataGridView")]
[ToolboxBitmap(typeof(System.Windows.Forms.DataGridView))]
public class gfDataGridView : System.Windows.Forms.DataGridView
{
    ...
}

The attributes are used for displaying a bitmap and a description of the control in the toolbox - like usual controls do.

Saving the Column Order, Width and Visibility

This is done in the Dispose method. For storing the data, the ColumnOrderItem class is used (see later in this article).

C#
protected override void Dispose(bool disposing)
{
	SaveColumnOrder();
	base.Dispose(disposing);
}

private void SaveColumnOrder()
{
	if (this.AllowUserToOrderColumns)
	{
		List<ColumnOrderItem> columnOrder = new List<ColumnOrderItem>();
		DataGridViewColumnCollection columns = this.Columns;
		for (int i = 0; i < columns.Count; i++)
		{
			columnOrder.Add(new ColumnOrderItem
			{
				ColumnIndex = i,
				DisplayIndex = columns[i].DisplayIndex,
				Visible = columns[i].Visible,
				Width = columns[i].Width
			});
		}

		gfDataGridViewSetting.Default.ColumnOrder[this.Name] = columnOrder;
		gfDataGridViewSetting.Default.Save();
	}
}

At first step, it gets checked whether AllowUserToOrderColumns is enabled or not. If not, there is no necessity to save the column order. This can be omitted if you want to save the width, etc. of the columns.

Then a List is populated with the values that should be saved to user.config and this list gets added or updated to the dictionary where the key is the name of the control. The dictionary gets assigned to the property setting and saved - just like "normal" designer generated settings.

Restoring the Column Order

In the OnCreateControl method the column order gets restored. It's important to start with the first displayed column to get the desired result. If this would be done using the column index, then it's just like dragging a column from one position to another which rearranges the order and the result is wrong.

C#
protected override void OnCreateControl()
{
	base.OnCreateControl();
	SetColumnOrder();
}

private void SetColumnOrder()
{
	if (!gfDataGridViewSetting.Default.ColumnOrder.ContainsKey(this.Name))
		return;

	List<ColumnOrderItem> columnOrder =
		gfDataGridViewSetting.Default.ColumnOrder[this.Name];

	if (columnOrder != null)
	{
		var sorted = columnOrder.OrderBy(i => i.DisplayIndex);
		foreach (var item in sorted)
		{
			this.Columns[item.ColumnIndex].DisplayIndex =
                                item.DisplayIndex;
			this.Columns[item.ColumnIndex].Visible = item.Visible;
			this.Columns[item.ColumnIndex].Width = item.Width;
		}
	}
}

ColumnOrderItem

The items of the list are defined within this class: 

C#
[Serializable]
public sealed class ColumnOrderItem
{
	public int DisplayIndex { get; set; }
	public int Width { get; set; }
	public bool Visible { get; set; }
	public int ColumnIndex { get; set; }
}

Settings

As mentioned earlier in this article, the designer generates usually the code for the settings-class. Here this is done manually and for storing the column order, a Dictionary<string, List<ColumnOrderItem>> is used. This dictionary gets serialized as binary.

C#
internal sealed class gfDataGridViewSetting : ApplicationSettingsBase
{
	private static gfDataGridViewSetting _defaultInstace =
		(gfDataGridViewSetting)ApplicationSettingsBase
		.Synchronized(new gfDataGridViewSetting());
	//---------------------------------------------------------------------
	public static gfDataGridViewSetting Default
	{
		get { return _defaultInstace; }
	}
	//---------------------------------------------------------------------
	// Because there can be more than one DGV in the user-application
	// a dictionary is used to save the settings for this DGV.
	// As key the name of the control is used.
	[UserScopedSetting]
	[SettingsSerializeAs(SettingsSerializeAs.Binary)]
	[DefaultSettingValue("")]
	public Dictionary<string, List<ColumnOrderItem>> ColumnOrder
	{
		get { return this["ColumnOrder"] as Dictionary<string,
                       List<ColumnOrderItem>>; }
		set { this["ColumnOrder"] = value; }
	}
}

The upper part is just the singleton implementation of the settings class. Then the dictionary for storing the column order is defined. The attributes specify that the property:

  • should be stored to the user scope (user.config)
  • serialization mode: binary
  • and the default value is an empty string, i.e. an empty dictionary

That's everything needed.

History

  • 29th July 2009 (Version 1.0.1.1)
    Updated article and code.
    • The problem of having more than one DGV in the client application is solved (due the use of the dictionary).
    • The DGV can be used in several containers (due to setting the columnorder in the OnCreateControl method and due to saving the columnorder in theDispose method.
  • 8th June 2009 (Version 1.0.0.0)
    Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Foidl Günther
Austria Austria
Engineer in combustion engine development.
Programming languages: C#, FORTRAN 95, Matlab

FIS-overall worldcup winner in Speedski (Downhill) 2008/09 and 2009/10.

Comments and Discussions

 
GeneralMy vote of 5 Pin
osmanx18-Mar-23 11:09
osmanx18-Mar-23 11:09 
SuggestionMultiple gfDataGridView's with same name Pin
symmetr122-Dec-20 8:32
symmetr122-Dec-20 8:32 
GeneralMy vote of 5 Pin
ClusterM6-Mar-19 21:17
ClusterM6-Mar-19 21:17 
GeneralMy vote of 4 Pin
J3ssy Pmntra15-Sep-14 8:13
J3ssy Pmntra15-Sep-14 8:13 
SuggestionUnhandled error with runtime populated grid Pin
Member 1056032130-Jan-14 18:16
Member 1056032130-Jan-14 18:16 
QuestionGreat post, exactly what I needed but some fixes needed Pin
Dudi Avrahamov16-Dec-13 1:03
Dudi Avrahamov16-Dec-13 1:03 
PraiseRe: Great post, exactly what I needed but some fixes needed Pin
DominikP19-Jan-16 18:58
DominikP19-Jan-16 18:58 
QuestionAn easier way to order columns Pin
Member 103092311-Oct-13 6:14
Member 103092311-Oct-13 6:14 
Questionthe second Datagrid Pin
C# learning user10-Apr-13 6:28
C# learning user10-Apr-13 6:28 
GeneralThanks, plus a VB version, plus an enhancement to accommodate column changes Pin
miteleda12-Nov-12 18:51
miteleda12-Nov-12 18:51 
Nice work, thank you very much.

Below I include a VB version. It also includes a change which better manages modifcations to the grid's data source (or explicit column set).

1) Whilst new columns appended to the end will happily be displayed as the last columns, inserted columns (or columns whose name is changed) will upset the restoring of columns. The change made saves the column name and restores based on column name, not index. In so doing prior columns restore normally, new and changed columns appear as the last columns.

2) Deleted columns will cause an exception as the restoring of columns tries to affect a column that no longer exists. The exception is now being caught and ignored, on the assumption that the column has been intentionally deleted. The next save of the column settings already excluded the deleted column(s) so the issue does not repeat.


Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Configuration
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Collections
Imports System.Reflection

<Description("DataGridView that Saves Column Order, Width and Visibility to user.config")> _
  <ToolboxBitmap(GetType(System.Windows.Forms.DataGridView))> _
Public Class DataGridView
  Private _key As String

  Private Sub SetColumnOrder()
    If Not gfDataGridViewSetting.[Default].ColumnOrder.ContainsKey(_key) Then
      Return 'Settings never been saved yet
    End If

    Dim columnOrder As List(Of ColumnOrderItem) = gfDataGridViewSetting.[Default].ColumnOrder(_key)

    If columnOrder IsNot Nothing Then
      columnOrder.Sort(New ColumnOrderItemComparer)
      For Each item As ColumnOrderItem In columnOrder
        Try
          Me.Columns(item.ColumnName).DisplayIndex = item.DisplayIndex
          Me.Columns(item.ColumnName).Visible = item.Visible
          Me.Columns(item.ColumnName).Width = item.Width
        Catch ex As Exception
          'An exception will be raised if the grid columns has been change such that
          'a column no longer exists, in this case the column setting will be ignored. 
          'It will be overwritten during SaveColumnOrder so will not repeat.
          'New columns will appear as the last columns.
        End Try
      Next
    End If
  End Sub
  '---------------------------------------------------------------------
  Private Sub SaveColumnOrder()
    If Me.AllowUserToOrderColumns Then
      Dim columnOrder As New List(Of ColumnOrderItem)()
      Dim columns As DataGridViewColumnCollection = Me.Columns
      For i As Integer = 0 To columns.Count - 1
        columnOrder.Add(New ColumnOrderItem(columns(i).Name, i, columns(i).DisplayIndex, columns(i).Visible, columns(i).Width))
      Next

      gfDataGridViewSetting.[Default].ColumnOrder(_key) = columnOrder
      gfDataGridViewSetting.[Default].Save()
    End If
  End Sub
  '---------------------------------------------------------------------
  Protected Overrides Sub OnCreateControl()
    MyBase.OnCreateControl()
    If Columns.Count > 0 Then
      _key = Me.Parent.Name & "." & Me.Name
      SetColumnOrder()
    End If
  End Sub
  '---------------------------------------------------------------------
  Protected Overrides Sub OnDataSourceChanged(ByVal e As EventArgs)
    MyBase.OnDataSourceChanged(e)
    If Columns.Count > 0 Then
      _key = Me.Parent.Name & "." & Me.Name
      SetColumnOrder()
    End If
  End Sub
  '---------------------------------------------------------------------
  <System.Diagnostics.DebuggerNonUserCode()> _
  Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    SaveColumnOrder()
    MyBase.Dispose(disposing)
    Try
      If disposing AndAlso components IsNot Nothing Then
        components.Dispose()
      End If
    Finally
      MyBase.Dispose(disposing)
    End Try
  End Sub
End Class
'-------------------------------------------------------------------------
Friend NotInheritable Class gfDataGridViewSetting
  Inherits ApplicationSettingsBase
  Private Shared _defaultInstace As gfDataGridViewSetting = DirectCast(ApplicationSettingsBase.Synchronized(New gfDataGridViewSetting()), gfDataGridViewSetting)
  '---------------------------------------------------------------------
  Public Shared ReadOnly Property [Default]() As gfDataGridViewSetting
    Get
      Return _defaultInstace
    End Get
  End Property
  '---------------------------------------------------------------------
  ' Because there can be more than one DGV in the user-application
  ' a dictionary is used to save the settings for this DGV.
  ' As key the name of the control's parent and control is used, separated by a .
  <UserScopedSetting()> _
  <SettingsSerializeAs(SettingsSerializeAs.Binary)> _
  <DefaultSettingValue("")> _
  Public Property ColumnOrder() As Dictionary(Of String, List(Of ColumnOrderItem))
    Get
      Return TryCast(Me("ColumnOrder"), Dictionary(Of String, List(Of ColumnOrderItem)))
    End Get
    Set(ByVal value As Dictionary(Of String, List(Of ColumnOrderItem)))
      Me("ColumnOrder") = value
    End Set
  End Property
End Class
'-------------------------------------------------------------------------
<Serializable()> _
Public NotInheritable Class ColumnOrderItem
  Public Sub New(ByVal ColumnName As String, ByVal ColumnIndex As Integer, ByVal DisplayIndex As Integer, ByVal Visible As Boolean, ByVal Width As Integer)
    Me.ColumnName = ColumnName
    Me.ColumnIndex = ColumnIndex
    Me.DisplayIndex = DisplayIndex
    Me.Visible = Visible
    Me.Width = Width
  End Sub
  Public Property ColumnName() As String
    Get
      Return m_ColumnName
    End Get
    Set(ByVal value As String)
      m_ColumnName = value
    End Set
  End Property
  Private m_ColumnName As String
  Public Property DisplayIndex() As Integer
    Get
      Return m_DisplayIndex
    End Get
    Set(ByVal value As Integer)
      m_DisplayIndex = value
    End Set
  End Property
  Private m_DisplayIndex As Integer
  Public Property Width() As Integer
    Get
      Return m_Width
    End Get
    Set(ByVal value As Integer)
      m_Width = value
    End Set
  End Property
  Private m_Width As Integer
  Public Property Visible() As Boolean
    Get
      Return m_Visible
    End Get
    Set(ByVal value As Boolean)
      m_Visible = value
    End Set
  End Property
  Private m_Visible As Boolean
  Public Property ColumnIndex() As Integer
    Get
      Return m_ColumnIndex
    End Get
    Set(ByVal value As Integer)
      m_ColumnIndex = value
    End Set
  End Property
  Private m_ColumnIndex As Integer
End Class


Public Class ColumnOrderItemComparer
  Implements IComparer(Of ColumnOrderItem)

  Public Function Compare(ByVal x As ColumnOrderItem, ByVal y As ColumnOrderItem) As Integer Implements System.Collections.Generic.IComparer(Of ColumnOrderItem).Compare
    x.DisplayIndex.CompareTo(y.DisplayIndex)
  End Function
End Class

GeneralRe: Thanks, plus a VB version, plus an enhancement to accommodate column changes Pin
Rob Sherratt24-Feb-13 1:59
Rob Sherratt24-Feb-13 1:59 
SuggestionSome error in VB Version Pin
Member 103741914-Nov-13 20:07
Member 103741914-Nov-13 20:07 
QuestionOriginal columns' order Pin
esb771-Oct-12 9:26
esb771-Oct-12 9:26 
QuestionThanks Pin
sitajaf28-Aug-12 2:51
sitajaf28-Aug-12 2:51 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey26-Feb-12 21:30
professionalManoj Kumar Choubey26-Feb-12 21:30 
QuestionThank You Pin
DougDeBug23-Nov-11 6:19
DougDeBug23-Nov-11 6:19 
Questionto XML Pin
vlad kg18-Mar-10 4:34
vlad kg18-Mar-10 4:34 
AnswerRe: to XML Pin
Günther M. FOIDL3-May-10 22:20
Günther M. FOIDL3-May-10 22:20 
GeneralRe: to XML Pin
vlad kg11-May-10 6:43
vlad kg11-May-10 6:43 
GeneralThank You Pin
harshamn4-Feb-10 4:47
harshamn4-Feb-10 4:47 
GeneralRe: Thank You Pin
Günther M. FOIDL3-May-10 22:14
Günther M. FOIDL3-May-10 22:14 
GeneralThank You Pin
Richard Beatty10-Nov-09 8:34
Richard Beatty10-Nov-09 8:34 
GeneralRe: Thank You Pin
Günther M. FOIDL3-Dec-09 10:43
Günther M. FOIDL3-Dec-09 10:43 
Generaldatagridview column with problem Pin
ss_hellhound27-Aug-09 15:55
ss_hellhound27-Aug-09 15:55 
GeneralRe: datagridview column with problem Pin
Günther M. FOIDL28-Aug-09 13:19
Günther M. FOIDL28-Aug-09 13:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.