Подтвердить что ты не робот

Самая элегантная XML-сериализация структуры Color

Одна проблема достала меня достаточно, чтобы зарегистрироваться в Stack Overflow. В настоящее время, если я хочу сериализовать цвет в строку XML как именованный цвет, или #rrggbb, или #aarrggbb, я делаю это так:

[XmlIgnore()]
public Color color;

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

Здесь MyColorConverter выполняет сериализацию так, как мне нравится. Но все это похоже на кучу, с дополнительным полем и все. Есть ли способ заставить его работать в несколько строк, возможно, подключив TypeDescriptor с атрибутами С#, связанными с XML?

4b9b3361

Ответ 1

Здесь я использую для сериализации структуры Color в XML. Это лучше, чем теневое первичное свойство Color, на мой взгляд. Любые предложения приветствуются.

Класс XmlColor полагается в первую очередь на функцию языка implicit operator для предоставления основных преобразований данных. Без этого класс в основном бесполезен. Были добавлены другие бит функциональности для округления класса.

Помощник XmlColor также обеспечивает удобный способ разделения цветовых компонентов. Я добавил свойство Alpha, чтобы показать это. Обратите внимание, что компонент Alpha не будет сериализован, если он полностью закручивается до 255.

Дезамеризация значения цвета Web объединяет значение Alpha, которое в настоящее время хранится в экземпляре. Порядок, в котором анализируются атрибуты, не имеет значения. Если атрибут Alpha отсутствует в источнике XML, значение компонента экземпляра будет использоваться для установки уровня Alpha. Это, возможно, ошибочно; однако в случае сериализации XML класс XmlColor инициализируется с помощью Color.Black, устанавливая значение Alpha в значение 255.

Я работаю над средой VS2010 и строю против .Net 4. Я понятия не имею, насколько совместим код с предыдущими версиями.

Здесь примерное свойство, которое должно быть сериализовано в XML:

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

Здесь вспомогательный класс 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; }
}

Ответ 2

Полагаю, что у меня есть более легкое решение. Сериализация цвета игнорируется, а цвет сохраняется и загружается как простые 32-разрядные данные ARGB.

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

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

Ответ 3

Боль, не так ли? Это все, что вы можете сделать с XmlSerializer, если вы не реализуете IXmlSerializable (который я рекомендую не). Параметры:

  • придерживайтесь этого, но также отмечайте color_XmlSurrogate как [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] - это остановит его появление в большинстве представлений привязки данных и в редакторе кода при ссылке на вашу сборку в виде dll
  • используйте DataContractSerializer, который поддерживает частные свойства (но не поддерживает атрибуты xml, вы не можете выиграть...)

btw, я бы имел color как свойство, а не поле:

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

Ответ 4

Поскольку это мой первый вопрос, я решил исследовать его больше. @bvj дал отличный ответ. Я изменил его код, так что вот оно. XmlColor класс изменяется так, что сериализованная строка идет в тексте тега, и существуют публичные статические функции, если строка должна быть сериализована как XML-атрибут вместо тега. Атрибуты, к сожалению, все же должны быть сериализованы с суррогатными полями, но, пожалуйста, исправьте меня, если я ошибаюсь. Использование класса:

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

Класс, который делает это:

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

Ответ 5

Я нашел другое решение,

Можно использовать System.Windows.Media.Color вместо System.Drawing.Color.
Это сериализуемо для XML.

Ответ 6

Для тех, кто использует System.Windows.Media.Color, решение @bvj можно упростить, используя метод класса ToString:

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

Как и раньше, теперь вы можете просто добавить это перед каждым сериализуемым свойством Color:

[XmlElement(Type = typeof(XmlColor))]

По умолчанию System.Media.Color сериализуется в этот 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>

С вышеупомянутым преобразованием это сериализует к этому:

<DisplayColor>#FF7B0000</DisplayColor>