This weekend I've been working on a skin editor for my
CustomBorderForms and I wanted to use XML Serialization to store skin files. However, my skins use several
System.Drawing classes that don't serialize very cleanly or not at all (e.g. Bitmap, Font, Size) . In such situations I often use surrogate properties that are serialized instead of the original ones. Today I've learned another trick from
Load and Save objects to XML using serialization article posted on CodeProject, that shows how to use
TypeConverters to simplify serialization of common objects by using
TypeDescriptors.
So here is how I serialize a Font property:
[XmlIgnore]
public Font TitleFont
{
get { return _titleFont; }
set { _titleFont = value; }
}
[XmlAttribute("titleFont")]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public string TitleFont_XmlSurrogate
{
get
{
if (TitleFont != null)
{
TypeConverter FontConverter = TypeDescriptor.GetConverter(typeof(Font));
return FontConverter.ConvertToInvariantString(TitleFont);
} else
return null;
}
set
{
if (!String.IsNullOrEmpty(value))
{
TypeConverter FontConverter = TypeDescriptor.GetConverter(typeof(Font));
TitleFont = (Font)FontConverter.ConvertFromInvariantString(value);
}
else
TitleFont = null;
}
}
and the resulting XML element looks like this:
<formStyle titleFont="Tahoma, 8pt, style=Bold" ...>
As you can see, I've added second property, TitleFont_XmlSurrogate to control the serialization and the original property is marked with XmlIgnore. The surrogate property obtains the TypeConverter for serialized type (here Font) and uses its ConvertToInvariantString and ConvertFromInvariantString methods to serialize and derserialize the value of original property. You will want to use InvariantCulture in serialization, because otherwise you can find yourself in troubles in non-English systems. For example, in English systems comma (,) is used as list separators, but in Polish it is serves as digit separator.
Other things to note is that I setup the surrogate property with EditorBrowsable and Browsable attributes so it's not visible and thus not confusing for casual programmer that will be using this classes.
I use the same technique to serialize Size, Padding, Color and other types that can be easily stored as attributes. In case of Bitmaps we can serialize them as arrays of bytes and XmlSerializer will use Base64 encoding to store them (similar to how it works in .resx files). In this case I use MemoryStream to quickly reconstruct the Bitmap from byte array, as shown in the before mentioned article:
[XmlIgnore]
public Bitmap Image
{
get { return _image; }
set { _image = value; }
}
[XmlElement("image")]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public byte[] Image_XmlSurrogate
{
get
{
if (Image != null)
{
TypeConverter BitmapConverter =
TypeDescriptor.GetConverter(Image.GetType());
return (byte[])BitmapConverter.ConvertTo(Image, typeof(byte[]));
}
else
return null;
}
set
{
if (value != null)
Image = new Bitmap(new MemoryStream(value));
else
Image = null;
}
}