An Editable GridView Control in C# and .NET - Part II Persisting in XML


Figure 1 - The Editable GridView with Persistence.



If you've played with .NET a bit, you may have noticed Microsoft's hard push to utilize XML throughout the architecture.  In this article we will take advantage of the XML classes available to us to persist the GridView control that we talked about in our first article in this series.  The two classes we utilize in our code are XmlTextWriter and XmlTextReader. These classes enable us to save the information in a grid into an XML file and read an XML file into the grid.

The XmlTextWriter class has several useful functions that make creating an XML file less tedious.  Below is a table of the methods we take advantage of for writing out the GridView.

XmlTextWriter methods

Description

WriteStartDocument(bool) Writes the XML starting declaration. The bool tells whether the document includes the standalone property.
<?xml version="1.0" standalone="no"?>
WriteStartElement(string)  Creates a node in the XML document with the starting element equal to the string passed
WriteAttributeString(string, string) Writes an attribute in the node with a name and a value
WriteEndElement()  Matches the last node started with an ending element.

Table 1 - XmlTextWriter Methods used in the GridView.

The XmlReader class enables us to read the XML files into our code.  It also has many convenient methods and properties  for walking through the XML nodes and extracting data from the XML document.  Below are the properties and methods we utilize for reading our GridView:

XmlTextReader methods and properties Description
bool Read() Reads the next XML node from the file  into an instance of the class for processing.  Returns false when it has read all the nodes
Name  Name of the current node or attribute
Value  current  node or attribute value
AttributeCount  Number of attributes in the current node
MoveToAttribute(int index) Moves to the attribute in the current node indicated by the index.  The index is 0 based.

Table 2 - XmlTextReader Properties and Methods used in the GridView.

In order to make our grid persistent,  we've created a class called GridPersister that implements the functionality from the XmlTextReader and XmlTextWriter classes on our GridView.  Below is the UML design of our persistent GridView:

Figure 2 - UML Design of the Persistent GridView reverse engineered using the
WithClass 2000 .

In our example, we save all of our GridView information in an XML file including column names, column widths and cell information.  The way we store grid cells and their contents is similar to how HTML stores table information.  We nest the collection of column nodes inside a row node.  The column node contains the text, background color, and foreground color of the cell.  Below is a partial snapshot of the XML file we are going to create from the GridView shown in Figure 1.

Figure 3 - Snapshot of XML File created by the GridView Save Method

Note that the XML file stores color information inside of attributes as aRGB numbers in integer format.  This is why you see these strange negative numbers after all of the color attributes.

Now let's see how we create this file using the GridPersister class.  Listing 1 below is the Write method inside of this class that produces the XML file shown in Figure 3.  The code first loops through the grids column headers and writes them out to the xml file.  Then it loops through rows and columns in the grid and writes out the nodes representing the contents of each cell of the grid.

Listing 1 - Write method to write the XML file

public void Write()
{
// Create instance of XmlTextWriter as a tool for writing our XML GridView information
XmlTextWriter writer = new XmlTextWriter(FilePath, null);
// Indent the XML file to look nice
writer.Formatting = Formatting.Indented;
writer.Namespaces =
true;
writer.Indentation = 4;
// write header
// Write the beginning header of our XML file along with a GridView tag
writer.WriteStartDocument(false);
writer.WriteStartElement("GridView");
// write grid attributes column names and widths for our column headers
writer.WriteStartElement("rowheader", null);
for (int j = 1; j <= m_GridView.NumberOfColumns; j++)
{
writer.WriteStartElement("colheader",
null);
writer.WriteAttributeString("text", m_GridView.GetColumnName(j));
writer.WriteAttributeString("width", m_GridView.GetColumnWidth(j).ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
// write internal grid
for (int i = 0; i < m_GridView.NumberOfRows - 1; i++)
{
// write next row of grid as a node
writer.WriteStartElement("row", null);
// write each column node of grid including text, background color and foreground C
olor
for (int j = 0; j < m_GridView.NumberOfColumns; j++)
{
writer.WriteStartElement("col",
null);
writer.WriteAttributeString("text", m_GridView.GetCell(i + 1, j + 1));
writer.WriteAttributeString("backcolor", m_GridView.GetCellColor(i+1, j+1).ToArgb().ToString());
writer.WriteAttributeString("forecolor", m_GridView.GetCellTextColor(i+1, j+1).ToArgb().ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.Flush();
writer.Close();
}
 

To read the file created by the Write method of the GridPersister we have created a Read method.  This is a bit more complex than the Write method because it needs to determine which node we are reading and extract the information.  The code for Reading the XML looks like a series of nested switch statements because as we read each  node, we need to parse the nested set of nodes inside.  Below is the code for reading the GridView XML information.

Listing 2  - Reading the XML GridView File

public void Read()
{
XmlTextReader reader =
new XmlTextReader(FilePath);
int rowCount = 0;
int colCount = 0;
int colHeaderCount = 0;
try
{
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element: // The node is the start of an Element
switch (reader.Name)
{
case "rowheader":
break;
case "colheader": // Found the next column header, read the name and width
colHeaderCount++;
for (int i = 0; i < reader.AttributeCount; i++)
{
// get the next attribute in the node and deterimine if its the column name or width
reader.MoveToAttribute(i);
switch(reader.Name)
{
case "text": // set the next columnheader name to the attribute read
m_GridView.SetColumnName(colHeaderCount, reader.Value);
break;
case "width": // set the next columnheader width to the attribute red
m_GridView.SetColumnWidthAndIgnoreFont(colHeaderCount,
XmlConvert.ToInt32(reader.Value));
break;
}
}
break;
case "row":
// current node is a row, increment the row count and reset the column count
rowCount++;
colCount = 0;
break;
case "col":
// current node is a column, increment the column count
colCount++;
// Now set the cell for the current row and column, and read in the attributes for the cell
for (int i = 0; i < reader.AttributeCount; i++)
{
reader.MoveToAttribute(i);
switch(reader.Name)
{
case "text":
// this attribute contains the text for the current cell, populate the cell in the gridview
m_GridView.SetCell(rowCount, colCount, reader.Value);
break;
case "forecolor":
// this attribute contains the text color for the current cell, set the text color in the cell
m_GridView.SetCellTextColor(rowCount, colCount,
Color.FromArgb(Convert.ToInt32(reader.Value)));
break;
case "backcolor":
// this attribute contains the cell color for the current cell, set the color inside the cell
m_GridView.SetCellColor(rowCount, colCount,
Color.FromArgb(Convert.ToInt32(reader.Value)));
break;
}
}
break;
}
break;
}
}
}
catch (Exception e)
{
MessageBox.Show(e.Message.ToString());
}
}

The GridPersister Read and Write functionality is propagated through the GridView through the GridView's Open and Save methods.  Below is the code for Opening and Saving the GridView from a form:

Listing 3 - Opening the GridView from a Windows Form

private void OpenMenu_Click(object sender, System.EventArgs e)
{
gridView1.Open();
}

Listing 4 - Saving the GridView from a Windows Form

private void menuItem2_Click(object sender, System.EventArgs e)
{
gridView1.Save();
}

Summary

This article demonstrated how to read and write the GridView to an XML file. The XML reading and writing functionality was accomplished using the XmlTextReader and XmlTextWriter classes. In the next article we will continue our journey with the GridView by describing how to send the GridView to the printer.  The final article in this series will show you how to extract a GridView into an Excel spreadsheet.