Most elegant XML serialization of Color structure
One problem bugged me enough to register on Stack Overflow. Currently if I want to serialize Color to XML string as named color, or #rrggbb
, or #aarrggbb
, I do i开发者_开发问答t like this:
[XmlIgnore()]
public Color color;
[XmlElement(ElementName = "Color")]
public String color_XmlSurrogate
{
get { return MyColorConverter.SetColor(color); }
set { color = MyColorConverter.GetColor(value); }
}
Here MyColorConverter
does serialization just the way I like it. But all this feels like a kludge, with additional field and all. Is there a way to make it work in less lines, maybe connecting TypeDescriptor with C# attributes related to XML?
Here's something I'm using for serializing the Color
struct in XML. It's better than shadowing the primary Color
property in my opinion. Any suggestions welcome.
The XmlColor
class relies primarily on the implicit operator
language feature to provide the key data tranformations. Without this, the class is basically useless. Other bits of functionality were added to round out the class.
The XmlColor
helper also provides a convenient way to separate color components. I added the Alpha
property to show this. Notice the Alpha
component won't be serialized if it's cranked all the way up to 255.
Deserializing the Web
color value combines the Alpha
value currently stored in the instance. The order in which the attributes are parsed shouldn't matter. If the Alpha
attribute is missing in the XML source, the instance component value will be used to set the Alpha
level. This is arguably faulty; however, in the case of XML serialization, the XmlColor
class will initialized with Color.Black
setting the Alpha
to 255.
I'm working out of the VS2010 environment and building against .Net 4. I have no idea how compatible the code is with previous versions.
Here's an example property that should be serialized to XML:
[XmlElement(Type=typeof(XmlColor))]
public Color MyColor { get; set; }
Here's the XmlColor
helper class:
public class XmlColor
{
private Color color_ = Color.Black;
public XmlColor() {}
public XmlColor(Color c) { color_ = c; }
public Color ToColor()
{
return color_;
}
public void FromColor(Color c)
{
color_ = c;
}
public static implicit operator Color(XmlColor x)
{
return x.ToColor();
}
public static implicit operator XmlColor(Color c)
{
return new XmlColor(c);
}
[XmlAttribute]
public string Web
{
get { return ColorTranslator.ToHtml(color_); }
set {
try
{
if (Alpha == 0xFF) // preserve named color value if possible
color_ = ColorTranslator.FromHtml(value);
else
color_ = Color.FromArgb(Alpha, ColorTranslator.FromHtml(value));
}
catch(Exception)
{
color_ = Color.Black;
}
}
}
[XmlAttribute]
public byte Alpha
{
get { return color_.A; }
set {
if (value != color_.A) // avoid hammering named color if no alpha change
color_ = Color.FromArgb(value, color_);
}
}
public bool ShouldSerializeAlpha() { return Alpha < 0xFF; }
}
I believe below I have an easier solution to that. Color serialization is ignored and color is saved and loaded as simple 32-bit ARGB data.
[XmlIgnore]
public Color BackColor { get; set; }
[XmlElement("BackColor")]
public int BackColorAsArgb
{
get { return BackColor.ToArgb(); }
set { BackColor = Color.FromArgb(value); }
}
A pain, isn't it? That is all you can do with XmlSerializer
, unless you implement IXmlSerializable
(which I do not recommend). Options:
- stick with that, but also mark
color_XmlSurrogate
as[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
- that will stop it appearing in most data-binding views, and in the code-editor when referencing your assembly as a dll - use
DataContractSerializer
, which supports private properties (but which doesn't support xml attributes; you can't win...)
btw, I'd have color
as a property, not a field:
[XmlIgnore]
public Color Color {get;set;}
Since this is my first question here I decided to investigate it more. @bvj gave an excellent answer. I tweaked his code, so here it is. XmlColor
class is changed so that the serialized string goes in text of the tag, and there are public static functions if string should be serialized as XML attribute instead of tag. Attributes unfortunately still must be serialized with surrogate fields, but please correct me if I'm wrong. Using the class:
// Color as tag
[XmlElement(Type = typeof(XmlColor))]
public Color ColorAsTag { get; set; }
// Color as attribute
[XmlIgnore]
public Color ColorAsAttribute { get; set; }
[XmlAttribute("ColorAsAttribute")]
public string ColorAsAttribute_XmlSurrogate
{
get { return XmlColor.FromColor(ColorAsAttribute); }
set { ColorAsAttribute = XmlColor.ToColor(value); }
}
The class that makes it happen:
public class XmlColor
{
private Color color_ = Color.Black;
public XmlColor() { }
public XmlColor(Color c) { color_ = c; }
public static implicit operator Color(XmlColor x)
{
return x.color_;
}
public static implicit operator XmlColor(Color c)
{
return new XmlColor(c);
}
public static string FromColor(Color color)
{
if (color.IsNamedColor)
return color.Name;
int colorValue = color.ToArgb();
if (((uint)colorValue >> 24) == 0xFF)
return String.Format("#{0:X6}", colorValue & 0x00FFFFFF);
else
return String.Format("#{0:X8}", colorValue);
}
public static Color ToColor(string value)
{
try
{
if (value[0] == '#')
{
return Color.FromArgb((value.Length <= 7 ? unchecked((int)0xFF000000) : 0) +
Int32.Parse(value.Substring(1), System.Globalization.NumberStyles.HexNumber));
}
else
{
return Color.FromName(value);
}
}
catch (Exception)
{
}
return Color.Black;
}
[XmlText]
public string Default
{
get { return FromColor(color_); }
set { color_ = ToColor(value); }
}
}
I have found another solution,
It is possible to use System.Windows.Media.Color instead of System.Drawing.Color.
It is serializable to XML.
For those using System.Windows.Media.Color
, @bvj's solution can be simplified, leveraging the class's ToString
method:
using System.Windows.Media;
public class XmlColor
{
private Color m_color;
public XmlColor() { }
public XmlColor(Color c) { m_color = c; }
public static implicit operator Color(XmlColor x)
{
return x.m_color;
}
public static implicit operator XmlColor(Color c)
{
return new XmlColor(c);
}
[XmlText]
public string Default
{
get { return m_color.ToString(); }
set { m_color = (Color)ColorConverter.ConvertFromString(value); }
}
}
}
As before, now you can just add this before each serializable Color property:
[XmlElement(Type = typeof(XmlColor))]
By default, System.Media.Color
serializes to this XML:
<DisplayColor>
<A>255</A>
<R>123</R>
<G>0</G>
<B>0</B>
<ScA>1</ScA>
<ScR>0.482352942</ScR>
<ScG>0</ScG>
<ScB>0</ScB>
</DisplayColor>
With the above conversion, it serializes to this:
<DisplayColor>#FF7B0000</DisplayColor>
精彩评论