Introduction
One of the features that is good to have when you work with a DataGrid
control is to make it remember its columns' width. In this article, I will be presenting a way to make your grid control remember its columns' size. Basically, somehow you need to make the grid control �memory� linked.
Today, XML has been widely gaining acceptance in industry. I, therefore, will use it to function as memory to the grid control. In this article, I would be presenting an XML file that can remember a single data grid control columns width. The extension of this article would be to make it remembering more than one state from multiple grid controls.
Using the code
First of all, you will need to add a DataGridTableStyle
to the grid control before you bind the grid control to a DataSource
. This tablestyle will be used to set the columns' width through its Width
property. So, every time the application get loaded, the last column state would be fetched and loaded.
The following example shows you a hard-coded way of setting the columns' width of your grid control. This way you can�t expect the grid control to remember its state.
public void HardcodedInitializeGrid()
{
Hashtable colWidths = LoadLastGridState();
tAddress = new DataTable();
tAddress.TableName = "tAddress";
tAddress.Columns.Add("No", typeof(int));
tAddress.Columns.Add("Firstname", typeof(string));
tAddress.Columns.Add("Lastname", typeof(string));
tAddress.Columns.Add("Email", typeof(string));
DataRow newRow;
newRow = tAddress.NewRow();
newRow["No"] = 0;
newRow["Firstname"] = "Henry";
newRow["Lastname"] = "Tan";
newRow["Email"] = "henryws@it.uts.edu.au";
tAddress.Rows.Add(newRow);
newRow = tAddress.NewRow();
newRow["No"] = 1;
newRow["Firstname"] = "Albert";
newRow["Lastname"] = "Einstein";
newRow["Email"] = "albert.einstein@heaven.au";
tAddress.Rows.Add(newRow);
DataGridTableStyle tablestyle = new DataGridTableStyle();
tablestyle.AlternatingBackColor = Color.WhiteSmoke;
tablestyle.MappingName = "tAddress";
DataGridTextBoxColumn textboxColumn = null;
for(int i=0; i < tAddress.Columns.Count; i++)
{
string colname = tAddress.Columns[i].ColumnName;
if(colname.Equals("No"))
{
textboxColumn = new DataGridTextBoxColumn();
textboxColumn.HeaderText = colname;
textboxColumn.MappingName = colname;
textboxColumn.Width = int.Parse(colWidths["No"].ToString());
tablestyle.GridColumnStyles.Add(textboxColumn);
}
else if(colname.Equals("Firstname"))
{
textboxColumn = new DataGridTextBoxColumn();
textboxColumn.HeaderText = colname;
textboxColumn.MappingName = colname;
textboxColumn.Width = int.Parse(colWidths["Firstname"].ToString());
tablestyle.GridColumnStyles.Add(textboxColumn);
}
else if(colname.Equals("Lastname"))
{
textboxColumn = new DataGridTextBoxColumn();
textboxColumn.HeaderText = colname;
textboxColumn.MappingName = colname;
textboxColumn.Width = int.Parse(colWidths["Lastname"].ToString());
tablestyle.GridColumnStyles.Add(textboxColumn);
}
else if(colname.Equals("Email"))
{
textboxColumn = new DataGridTextBoxColumn();
textboxColumn.HeaderText = colname;
textboxColumn.MappingName = colname;
textboxColumn.Width = int.Parse(colWidths["Email"].ToString());
tablestyle.GridColumnStyles.Add(textboxColumn);
}
else
{
textboxColumn = new DataGridTextBoxColumn();
textboxColumn.HeaderText = "Default";
textboxColumn.MappingName = "Default";
textboxColumn.Width = int.Parse(colWidths["Default"].ToString());
tablestyle.GridColumnStyles.Add(textboxColumn);
}
}
dataGrid.TableStyles.Add(tablestyle);
dataGrid.DataSource = tAddress;
}
Figure 1: Hardcoded way of setting columns' width.
The hard-coded approach is not what we usually want in the real application. What we want is an application that can memorize each of the columns' width. The above code, creates a DataTable
with four columns, �No
�, �Firstname
�, �Lastname
�, and �Email
�. It then adds two records to the table. Next, creates DataGridTableStyle
object, setting the grid�s AlternatingBackColor
with WhiteSmoke
, and map it with the previously created table by setting the MappingName
equal to the table�s name. Next, we need to construct an XML file to store the grid columns' width state. The following is one possible format.
="1.0" ="utf-8"
<GridState>
<Control name="MemoryGrid">
<Column name="No">30</Column>
<Column name="FirstName">200</Column>
<Column name="LastName">200</Column>
<Column name="Email">250</Column>
<Column name="Default">100</Column>
</Control>
</GridState>
Figure 2: XML file to remember columns' width.
As now, the columns' widths are stored in the XML format, consequently, some changes need to be made to the previous code in fig. 1. You need to read the columns' width of each of the column and set it for each of the column. For readability purpose, all changes made to the previous code in fig 1. would be highlighted in red.
public void InitializeGridWithLastState()
{
Hashtable colWidths = LoadLastGridState();
tAddress = new DataTable();
tAddress.TableName = "tAddress";
tAddress.Columns.Add("No", typeof(int));
tAddress.Columns.Add("Firstname", typeof(string));
tAddress.Columns.Add("Lastname", typeof(string));
tAddress.Columns.Add("Email", typeof(string));
DataRow newRow;
newRow = tAddress.NewRow();
newRow["No"] = 0;
newRow["Firstname"] = "Henry";
newRow["Lastname"] = "Tan";
newRow["Email"] = "henryws@it.uts.edu.au";
tAddress.Rows.Add(newRow);
newRow = tAddress.NewRow();
newRow["No"] = 1;
newRow["Firstname"] = "Albert";
newRow["Lastname"] = "Einstein";
newRow["Email"] = "albert.einstein@heaven.au";
tAddress.Rows.Add(newRow);
DataGridTableStyle tablestyle = new DataGridTableStyle();
tablestyle.AlternatingBackColor = Color.WhiteSmoke;
tablestyle.MappingName = "tAddress";
DataGridTextBoxColumn textboxColStyle = null;
for(int i=0; i < table.Column.Count; i++)
{
string colname = table.Column[i].ColumnName;
if(colname.Equals(�No�))
{
textboxColumn = new DataGridTextBoxColumn();
textboxColumn.HeaderText = colname;
textboxColumn.MappingName = colname;
textboxColumn.Width = colWidths[�No�];
}
else if(colname.Equals(�Firstname�))
{
textboxColumn = new DataGridTextBoxColumn();
textboxColumn.HeaderText = colname;
textboxColumn.MappingName = colname;
textboxColumn.Width = colWidths[�Firstname�];
}
else if(colname.Equals(�Lastname�))
{
textboxColumn = new DataGridTextBoxColumn();
textboxColumn.HeaderText = colname;
textboxColumn.MappingName = colname;
textboxColumn.Width = colWidths[�Lastname�];
}
else if(colname.Equals(�Emails�))
{
textboxColumn = new DataGridTextBoxColumn();
textboxColumn.HeaderText = colname;
textboxColumn.MappingName = colname;
textboxColumn.Width = colWidths[�Emails�];
}
else
{
textboxColumn = new DataGridTextBoxColumn();
textboxColumn.HeaderText = colname;
textboxColumn.MappingName = colname;
textboxColumn.Width = colWidths[�Default�];
}
}
dataGrid.TableStyles.Add(tablestyle);
dataGrid.DataSource = tAddress;
}
Figure 3: Initialize with the last grid's state [columns' width].
Few changes easily noticeable from the code in fig. 3. Instead of assigning the columns' width with a constant, it fetches the columns width from the XML file. Using hashtable makes the task becomes handy. We store the pairs of {column-name, column-width} in the hashtable. For example, in fig 3. colWidths[�Firstname�]
will return its column width. Oppositely, to add a pair to the hashtable, colwidths.Add(�Firstname�, 200)
will do the job, storing a pair with �Firstname� as the key and 200 as the value.
public Hashtable LoadLastGridState()
{
Hashtable colWidths = new Hashtable();
XmlTextReader tr = new XmlTextReader("gridstate.xml");
while(tr.Read())
{
if(tr.NodeType == XmlNodeType.Element)
{
if(tr.Name == "Column")
{
string key = tr.GetAttribute("name");
string val = tr.ReadElementString();
colWidths.Add(key, val);
}
}
}
tr.Close();
return colWidths;
}
Figure 4: Loads columns' width from XML file.
Loading the grid�s state from XML file is quite straight-forward. Especially, if you are already familiar with XmlTextReader. For each of the element in XML that started with �Column� as its tag-name, fetch its attribute name (column name) and the column width. Add it to the hashtable object and return the hashtable object after completing reading the XML.
public void SaveCurrentGridState()
{
if(dataGrid.TableStyles.Count == 0)
return;
Hashtable colWidths = new Hashtable();
XmlTextWriter tw =
new XmlTextWriter("gridstate.xml", System.Text.Encoding.ASCII);
tw.Formatting = Formatting.Indented;
GridColumnStylesCollection colstyle =
dataGrid.TableStyles[0].GridColumnStyles;
int test = colstyle["No"].Width;
colWidths.Add("No", colstyle["No"].Width);
colWidths.Add("Firstname", colstyle["Firstname"].Width);
colWidths.Add("Lastname", colstyle["Lastname"].Width);
colWidths.Add("Email", colstyle["Email"].Width);
#region Document
tw.WriteStartDocument();
#region GridState
tw.WriteStartElement("GridState");
#region gridMainOptStrategy
tw.WriteStartElement("Control");
tw.WriteAttributeString("name", "MemoryGrid");
#region No
tw.WriteStartElement("Column");
tw.WriteAttributeString("name", "No");
tw.WriteString(colWidths["No"].ToString());
tw.WriteEndElement();
#endregion
#region Firstname
tw.WriteStartElement("Column");
tw.WriteAttributeString("name", "Firstname");
tw.WriteString(colWidths["Firstname"].ToString());
tw.WriteEndElement();
#endregion
#region Lastname
tw.WriteStartElement("Column");
tw.WriteAttributeString("name", "Lastname");
tw.WriteString(colWidths["Lastname"].ToString());
tw.WriteEndElement();
#endregion
#region Email
tw.WriteStartElement("Column");
tw.WriteAttributeString("name", "Email");
tw.WriteString(colWidths["Email"].ToString());
tw.WriteEndElement();
#endregion
tw.WriteEndElement();
#endregion
tw.WriteEndElement();
#endregion
tw.WriteEndDocument();
#endregion
tw.Close();
}
Figure 5: Saves current grid's columns' width into XML file.
Finally, at some point, you need to call the SaveCurrentGridState().
Otherwise the grid won�t be able to call the current state in a future time. You can save the grid�s state at closing event when the application is closed.
private void MainForm_Closed(object sender, System.EventArgs e)
{
SaveCurrentGridState();
}
Figure 6: Closed event handler.
That�s it! Now, you can have your grid control remembering its columns' width at all time. Once you close the application and load it again, you won�t notice anything difference. Just few notes before I conclude the article, you can extend the XML format so that it can remember more than one grid controls. What you need to do is to structure the grid states as in the following XML file:
="1.0" ="utf-8"
<GridState>
<Control name="Grid1">
<Column name="No">50</Column>
<Column name="FirstName">200</Column>
<Column name="LastName">200</Column>
<Column name="Email">250</Column>
<Column name="Default">100</Column>
</Control>
<Control name="Grid2">
<Column name="Id">30</Column>
<Column name="Item Description">200</Column>
<Column name="Price">200</Column>
<Column name="Default">100</Column>
</Control>
</GridState>
Figure 7: XML remember many controls' state
Points of Interest
You need to make few adjustments to the LoadLastGridState()
and SaveCurrentGridState()
which I will write on my next articles.
History
First release - Memorizing one grid columns' width.