为何单个System.Drawing.Color可序列化,List<Color>在WinForms设置中失效?
这个问题我之前也踩过坑,核心原因是WinForms的Settings系统对单个Color和集合中的Color使用了不同的序列化机制:
问题根源
当你存储单个System.Drawing.Color时,Settings会自动使用ColorConverter将Color转换为可序列化的字符串(比如Argb(255,255,0,0)或者颜色名称Red),这种转换是Settings内置支持的,所以能正常保存和读取。
但当你使用List<Color>这类泛型集合时,Settings会默认切换到XmlSerializer来序列化整个集合。而XmlSerializer不会自动调用ColorConverter,它会尝试直接序列化Color的所有公共属性——但Color的很多属性是动态计算的(比如IsKnownColor、IsSystemColor),而且XmlSerializer对值类型的序列化逻辑会生成大量冗余或空的节点,最终导致配置文件里只留下空的颜色条目,无法正确保存颜色信息。
解决方案
这里提供几种实用的解决方法,按推荐程度排序:
方案1:用StringCollection替代List(最简单)
Settings原生支持System.Collections.Specialized.StringCollection类型,我们可以把Color转换成字符串(比如用ToArgb()的数值表示,避免颜色名称的兼容性问题),存储在StringCollection中,读写时再转换回来。
步骤如下:
- 在Settings设计器中添加一个类型为
StringCollection的设置项,比如命名为MarkerColorsStorage。 - 读写代码示例:
// 保存颜色列表 var targetColors = new List<Color> { Color.Red, Color.LimeGreen, Color.Blue }; var colorStrings = new StringCollection(); colorStrings.AddRange(targetColors.Select(c => c.ToArgb().ToString()).ToArray()); MyApp.Properties.Settings.Default.MarkerColorsStorage = colorStrings; MyApp.Properties.Settings.Default.Save(); // 读取颜色列表 var savedStrings = MyApp.Properties.Settings.Default.MarkerColorsStorage; if (savedStrings != null) { var loadedColors = savedStrings.Cast<string>() .Select(s => Color.FromArgb(int.Parse(s))) .ToList(); // 使用loadedColors }
方案2:自定义可序列化的Color集合类
如果想直接使用List<Color>类似的类型,可以创建一个实现IXmlSerializable的自定义集合类,手动控制序列化逻辑:
using System.Drawing; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; using System.ComponentModel; [Serializable] [TypeConverter(typeof(ColorListConverter))] public class ColorList : List<Color>, IXmlSerializable { public XmlSchema GetSchema() => null; // 反序列化逻辑 public void ReadXml(XmlReader reader) { reader.ReadStartElement(); while (reader.NodeType != XmlNodeType.EndElement) { var colorArgb = int.Parse(reader.ReadElementString("Color")); Add(Color.FromArgb(colorArgb)); reader.MoveToContent(); } reader.ReadEndElement(); } // 序列化逻辑 public void WriteXml(XmlWriter writer) { foreach (var color in this) { writer.WriteElementString("Color", color.ToArgb().ToString()); } } } // 配套TypeConverter,让Settings设计器能正常显示和编辑这个类型 public class ColorListConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); } public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { if (value is string colorStr) { var colors = colorStr.Split(',') .Select(s => Color.FromArgb(int.Parse(s.Trim()))) .ToList(); return new ColorList(colors); } return base.ConvertFrom(context, culture, value); } public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { if (destinationType == typeof(string) && value is ColorList colorList) { return string.Join(", ", colorList.Select(c => c.ToArgb().ToString())); } return base.ConvertTo(context, culture, value, destinationType); } }
然后在Settings设计器中添加类型为ColorList的设置项,就能直接像使用List
方案3:手动重写Settings的序列化逻辑
如果不想修改现有设置类型,可以在Settings的部分类中重写加载和保存的逻辑,手动处理List<Color>的序列化:
namespace MyApp.Properties { internal sealed partial class Settings { protected override void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { base.OnPropertyChanged(sender, e); // 当MarkerColors属性变化时,手动序列化到StringCollection if (e.PropertyName == nameof(MarkerColors)) { var colorStrings = new System.Collections.Specialized.StringCollection(); colorStrings.AddRange(MarkerColors.Select(c => c.ToArgb().ToString()).ToArray()); this["MarkerColorsSerialized"] = colorStrings; } } protected override void OnSettingsLoaded(object sender, System.Configuration.SettingsLoadedEventArgs e) { base.OnSettingsLoaded(sender, e); // 加载时手动反序列化 var savedStrings = this["MarkerColorsSerialized"] as System.Collections.Specialized.StringCollection; if (savedStrings != null) { MarkerColors = savedStrings.Cast<string>() .Select(s => Color.FromArgb(int.Parse(s))) .ToList(); } } } }
注意:这种方法需要额外添加一个名为MarkerColorsSerialized的StringCollection类型设置项,用来存储序列化后的字符串。
内容的提问来源于stack exchange,提问作者greenoldman

