43

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 it 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?

Grokify
  • 15,092
  • 6
  • 60
  • 81
Dialecticus
  • 16,400
  • 7
  • 43
  • 103
  • I think you have reached the limits of XML serialization. You might want to look into DataContract serialization instead, – leppie Jul 19 '10 at 10:50

7 Answers7

71

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; }
}
bvj
  • 3,294
  • 31
  • 30
  • 3
    I like this! If it works, that is. I use VS2008 with .NET 3.5, so I would need to check this out some time in the future. Is there a reason to use ToColor/FromColor instead of some Color property? Can this be recoded to have just one xml string as serialized value? I would like this to be the result: `#aarrggbb` – Dialecticus Dec 01 '10 at 10:44
  • 4
    You could strip the class down to the implicit operators and a single publishable property. How that property gets serialized is totally up to you. I noticed calling on the Argb methods results in hex strings regardless of the alpha value. I chose XmlAttribute to avoid child elements. You might consider an "ARGB" XmlAttribute and coerce the 4 component hex strings by utilizing the Argb methods. No doubt you could squeeze in a Color property if you find it helpful. – bvj Dec 03 '10 at 09:42
  • 3
    @bvj, Too bad I can't give you a bounty for this one :) – Andrey Rubshtein Dec 26 '11 at 12:45
  • 2
    This is genius. Now I only have to add a `[XmlElement(Type=typeof(XmlColor))]` attribute on my code. Really the most elegant solution. – Otiel Oct 13 '14 at 14:50
  • 2
    If you are working with WPF colors (or convert your WinForms colors over to WPF), the auto-serialization will work, but it's kind of ugly (giving you one XML element of A, R, G, B, ScA, ScR, ScG and ScB each). But with a slightly modified version of bvj's solution, you can get a nicer output by simply replacing `ColorTranslator.ToHtml()` with `color_.ToString()` and `ColorTranslator.FromHtml()` with `ColorConverter.FromString()`. The result will be stored in the form "#FF000000", so you don't even need to deal with a separate Alpha value. – Crusha K. Rool Nov 18 '16 at 18:29
  • Thanks for the recommendation, @CrushaK.Rool – bvj Mar 05 '17 at 06:47
  • I get the following error: error CS0029: Cannot implicitly convert type 'XmlColor' to 'System.Drawing.Color' –  Jan 31 '19 at 14:21
  • @EricSchneider Just tested in a VS2017 console app (.Net) only adding the `System.Drawing` assembly. The results ``. Are you testing with .Net Core? – bvj Jan 31 '19 at 21:43
  • no, but I'm using vb.net for this project. Not sure what was wrong, but was eager to move ahead so just did the "surrogate property" –  Feb 01 '19 at 05:37
  • Does this still work with SystemColors ? could that be the issue? –  Feb 01 '19 at 05:38
  • @EricSchneider for VB.Net, I created C# lib `XmlColorLib` to keep it simple, and then on a VB Class `System.Drawing.Color` Property, I added the attribute `` which worked without complications. HTH. – bvj Feb 04 '19 at 04:20
  • yea, i don't want an extra lib for just one class. No time for me to resolve it, i'm busy with other stuff. –  Feb 04 '19 at 17:23
  • I get this exception: System.PlatformNotSupportedException: "Compiling JScript/CSharp scripts is not supported". dotNET 6 MAUI application – Mike Tsayper Jan 19 '23 at 06:32
  • @MikeTsayper I created a Maui project using the latest VS2022 (netcore 7.0) with `using Color = System.Drawing.Color;` and the project compiled without errors. I didn't go so far as making use of the class, but I'm guessing it will work. – bvj Jan 20 '23 at 07:38
22

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); }
}
Niall C.
  • 10,878
  • 7
  • 69
  • 61
5

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;}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
4

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); }
    }
}
Dialecticus
  • 16,400
  • 7
  • 43
  • 103
2

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>
Jonathan Lidbeck
  • 1,555
  • 1
  • 14
  • 15
2

I have found another solution,

It is possible to use System.Windows.Media.Color instead of System.Drawing.Color.
It is serializable to XML.

Andrey Rubshtein
  • 20,795
  • 11
  • 69
  • 104
  • I tried it and for one color value I got several tags over which I have no control. That's no serialization the way I want it. – Dialecticus Dec 26 '11 at 18:04
  • @Dialecticus, please explain. – Andrey Rubshtein Dec 26 '11 at 21:46
  • 1
    for color (A=123, R=45, G=67, B=89) this is the result of serialization: `1234567890.4823529420.02624122240.05612849440.09989873`. What I want as result is `#7B2D4359`. And besides, I really need GDI color, not media color, because that's the structure I'm working with. – Dialecticus Dec 27 '11 at 09:49
  • @Dialecticus, Ok, I understand what you mean. But for some purposes it can be fine. – Andrey Rubshtein Dec 27 '11 at 10:05
0

The approach to use the helper class "XmlColor" and define it on the property like this

[XmlElement(Type=typeof(XmlColor))]
public Color MyColor { get; set; }

will fail in .NET6 and .NET7 with a meaningless error "Compiling JScript/CSharp scripts is not supported" (I created a dotnet github issue to improve this error message: https://github.com/dotnet/runtime/issues/81613)

The resolution that worked for me is to implement the interface System.Xml.Serialization.IXmlSerializable:

public class XmlColor6 : IXmlSerializable
{
  private Color m_value = Color.Empty;

  public XmlColor6() { }
  public XmlColor6(Color source) { m_value = source; }

  public static implicit operator Color?(XmlColor6 o)
  {
    return o == null ? default(Color?) : o.m_value;
  }

  public static implicit operator XmlColor6(Color? o)
  {
    return o == null ? null : new XmlColor6(o.Value);
  }

  public static implicit operator Color(XmlColor6 o)
  {
    return o == null ? default(Color) : o.m_value;
  }

  public static implicit operator XmlColor6(Color o)
  {
    return o == default(Color) ? null : new XmlColor6(o);
  }

  public void WriteXml(XmlWriter writer)
  {
    string colorAsString = // convert Color to string
    writer.WriteString(colorAsString );
  }

  public void ReadXml(XmlReader reader)
  {
    string colorAsString = reader.ReadElementContentAsString();

    this.m_value = // convert "colorAsString" to Color
  }

  public XmlSchema GetSchema()
  {
    return null;
  }
}

The attribute to serialize has no longer the type "Color", but "XmlColor6":

public XmlColor6 Color
{
  get; set;
}

As there are some implicit operators, you can assign it to "Color" variables without problems.

I prefer using a "struct" instead of a "class", so that you can replace all "Color" variables by "XmlColor6" without having to care for null values:

public struct XmlColor6 : IXmlSerializable
{
  private Color m_value;

  public XmlColor6(Color source) { m_value = source; }

  public static implicit operator Color(XmlColor6 o)
  {
    return o.m_value;
  }

  public static implicit operator XmlColor6(Color o)
  {
    return new XmlColor6(o);
  }

  public void WriteXml(XmlWriter writer)
  {
    string colorAsString = // convert Color to string
    writer.WriteString(colorAsString );
  }

  public void ReadXml(XmlReader reader)
  {
    string colorAsString = reader.ReadElementContentAsString();

    this.m_value = // convert "colorAsString" to Color
  }

  public XmlSchema GetSchema()
  {
    return null;
  }
}
wknauf
  • 160
  • 1
  • 7