web-dev-qa-db-fra.com

La sérialisation XML la plus élégante de la structure Color

Un problème m'a suffisamment perturbé pour m'inscrire sur Stack Overflow. Actuellement, si je veux sérialiser la chaîne Color to XML en tant que couleur nommée, ou #rrggbb, ou #aarrggbb, je le fais comme ceci:

[XmlIgnore()]
public Color color;

[XmlElement(ElementName = "Color")]
public String color_XmlSurrogate
{
  get { return MyColorConverter.SetColor(color); }
  set { color = MyColorConverter.GetColor(value); }
}

Ici MyColorConverter fait la sérialisation exactement comme je l’aime. Mais tout cela ressemble à un kludge, avec un champ supplémentaire et tout. Y a-t-il un moyen de le faire fonctionner en moins de lignes, en connectant TypeDescriptor avec les attributs C # associés à XML?

35
Dialecticus

Voici quelque chose que j'utilise pour sérialiser la structure Color en XML. C’est mieux que d’observer la propriété primaire Color à mon avis. Toutes les suggestions sont les bienvenues.

La classe XmlColor repose principalement sur la fonctionnalité de langage implicit operator pour fournir les transformations de données de clé. Sans cela, la classe est pratiquement inutile. D'autres fonctionnalités ont été ajoutées pour compléter la classe.

L'assistant XmlColor fournit également un moyen pratique de séparer les composants de couleur. J'ai ajouté la propriété Alpha pour montrer ceci. Notez que le composant Alpha ne sera pas sérialisé s'il est lancé à fond jusqu'à 255. 

La désérialisation de la valeur de couleur Web combine la valeur Alpha actuellement stockée dans l'instance. L'ordre dans lequel les attributs sont analysés ne devrait pas avoir d'importance. Si l'attribut Alpha est manquant dans la source XML, la valeur du composant d'instance sera utilisée pour définir le niveau Alpha. C'est sans doute fautif; cependant, dans le cas de la sérialisation XML, la classe XmlColor sera initialisée avec Color.Black, en définissant Alpha à 255.

Je travaille à partir de l'environnement VS2010 et je construis contre .Net 4. Je n'ai aucune idée de la compatibilité du code avec les versions précédentes.

Voici un exemple de propriété à sérialiser en XML:

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

Voici la classe d'assistance XmlColor:

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; }
}
59
bvj

Je crois ci-dessous que j'ai une solution plus facile à cela. La sérialisation des couleurs est ignorée et les couleurs sont enregistrées et chargées en tant que simples données ARGB 32 bits.

[XmlIgnore]
public Color BackColor { get; set; }

[XmlElement("BackColor")]
public int BackColorAsArgb
{
    get { return BackColor.ToArgb();  }
    set { BackColor = Color.FromArgb(value); }
}
17
darklordofsoftware

Une douleur, n'est-ce pas? C’est tout ce que vous pouvez faire avec XmlSerializer, sauf si vous implémentez IXmlSerializable (que je fais pas recommande). Options:

  • s'en tenir à cela, mais aussi marquer color_XmlSurrogate comme [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] - cela l'empêchera d'apparaître dans la plupart des vues de liaison de données et dans l'éditeur de code lors de la référence à votre Assembly en tant que dll
  • utilisez DataContractSerializer, qui supporte les propriétés privées (mais qui n'a pas supporte les attributs xml; vous ne pouvez pas gagner ...)

au fait, j'aurais color comme propriété, pas comme champ:

[XmlIgnore]
public Color Color {get;set;}
5
Marc Gravell

Comme c’est ma première question ici, j’ai décidé d’enquêter davantage. @bvj a donné une excellente réponse. J'ai modifié son code, alors le voici. La classe XmlColor est modifiée pour que la chaîne sérialisée soit insérée dans le texte de la balise. Il existe des fonctions statiques publiques si chaîne doit être sérialisé en tant qu'attribut XML au lieu de balise. Les attributs doivent malheureusement toujours être sérialisés avec des champs de substitution, mais corrigez-moi si je me trompe. En utilisant la classe:

// 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); }
}

La classe qui fait que ça se passe:

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); }
    }
}
3
Dialecticus

J'ai trouvé une autre solution,

Il est possible d'utiliser System.Windows.Media.Color au lieu de System.Drawing.Color .
Il est sérialisable en XML. 

2
Andrey Rubshtein

Pour ceux qui utilisent System.Windows.Media.Color, la solution de @ bvj peut être simplifiée en utilisant la méthode ToString de la classe:

    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); }
        }
    }
}

Comme auparavant, vous pouvez maintenant ajouter ceci avant chaque propriété Color sérialisable:

[XmlElement(Type = typeof(XmlColor))]

Par défaut, System.Media.Color se sérialise vers ce 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>

Avec la conversion ci-dessus, il se sérialise à ceci:

<DisplayColor>#FF7B0000</DisplayColor>
0
Jonathan Lidbeck