Introduction
When developing GUIs, say for prototyping new hardware, it is quite common to have a wide selection of check boxes, list boxes, radio buttons, text boxes, etc. These are often added and removed from the form quite regularly, as their role is simply to support testing and development needs. Between runs, the status of the components (checked or not, values, indexes, etc.) must be re-entered. It would be convenient if a generic way of saving the status of the various components could be implemented that does not need altering for each change in the GUI features. This article outlines a means of achieving this, by generically saving set-up information to XML.
Using the code
Open the TestForm solution file in Visual Studio .NET. The downloadable code consists of the FormSetupSaver class project and a simple GUI application project to test the class. The FormSetupSaver
class has a constructor that takes the Form
's Controls
as an argument.
FormSetupSaver setupSaver = new FormSetupSaver(this.Controls);
The class has two methods: Save
and Load
, each taking the name of the XML file where the details will be stored. Don't add the .xml extension to the filename variable.
setupSaver.Save(filename);
setupSaver.Load(filename);
Try adding and removing check boxes, list boxes, radio buttons, and text boxes.
Writing the control status to XML file
The body of the work is done in two private methods of the FormSetupSaver
class (see source file FormSetupSaver.cs). These methods are WriteCycleThroughControls
(used by Save
method) and ReadCycleThroughControls
(used by Load
method). First, here is a stripped down version of WriteCycleThroughControls
:
private void WriteCycleThroughControls(XmlTextWriter xw,
Control.ControlCollection controls)
{
foreach(Control c in controls)
{
if(c is TextBox)
{
xw.WriteStartElement(c.GetType().ToString());
xw.WriteAttributeString("Name", c.Name);
xw.WriteAttributeString("Text", c.Text);
xw.WriteEndElement();
}
else if(c is CheckBox)
{
xw.WriteStartElement(c.GetType().ToString());
xw.WriteAttributeString("Name", c.Name);
xw.WriteAttributeString("Checked",
((CheckBox)c).Checked.ToString());
xw.WriteEndElement();
}
else if(c is GroupBox)
{
WriteCycleThroughControls(xw, c.Controls);
}
}
}
We cycle through all the controls and check the type of each using the 'is
' keyword. Depending on the type and whether we support it (in this example only TextBox
es and CheckBox
es are, but in the source code there are others), the details are written to an XML file. Using an XmlTextWriter
, we write the type as an element, and the name as the first attribute. This the same for all components. However, as can be seen in the example, CheckBox
es and TextBox
es have different properties: boolean Checked
and string Text
respectively. Write an end
element to complete information for the current control.
Of greatest interest is how to handle controls that are within container controls, such as GroupBox
es. These will not be seen within the Form
's own Controls
list. Therefore, we recursively call WriteCycleThroughControls
again when we hit a GroupBox
. This way we can transparently handle GroupBox
es nested within GroupBox
es or other containers (if they are present).
Reading the XML file and updating the controls
This is slightly more complex. We set up an XmlTextReader
and use a while
loop to go through the XML file. If an element is found then we check whether it is a valid control. A simple check is to see if the name of the control type begins in an expected manner ("System.Windows.Forms
"). If so then get the attributes. Another private method called GetControl
does the work to get the actual control by cycling through all the controls including controls within controls (recursive calling of GetControl
). If the control name found in the XML file matches the name of the control in the controls list then we've found the right control and return it. If it can't be found (say, we deleted a now unnecessary TextBox
) then a null pointer is returned and we harmlessly skip to the next element in the XML file.
Finally, depending on the type of control, we set the correct property (.Text
for TextBox
es, .Checked
for CheckBox
es).
private void ReadCycleThroughControls(XmlTextReader xr,
Control.ControlCollection controls)
{
string controltype;
string controlname;
string controlvalue;
int index=0;
Control c = null;
while(xr.Read())
{
if(xr.NodeType == XmlNodeType.Element || xr.IsEmptyElement == false)
{
controltype = xr.Name;
if(controltype.StartsWith("System.Windows.Forms"))
{
xr.MoveToNextAttribute();
controlname = xr.Value;
xr.MoveToNextAttribute();
controlvalue = xr.Value;
}
else
continue;
c = null;
if(controltype.Length > 0)
c = GetControl(controlname, controls);
xr.MoveToElement();
if(c == null)
continue;
if(c is TextBox)
((TextBox)c).Text = controlvalue;
else if(c is CheckBox)
((CheckBox)c).Checked = Convert.ToBoolean(controlvalue);
}
}
}
private Control GetControl(string controlname, Control.ControlCollection controls)
{
Control controlInGroupBox = null;
foreach(Control c in controls)
{
if(c is GroupBox)
{
controlInGroupBox = GetControl(controlname, c.Controls);
if(controlInGroupBox != null)
return controlInGroupBox;
}
else if(c.Name == controlname)
{
return c;
}
}
return null;
}