如何在WinForms中实现List<string>在PropertyGrid与ini文件间的存读?
问题解决:Windows Forms配置中List类型的INI存读异常
问题场景
在Visual Studio开发Windows Forms应用时,通过自定义PropsClass管理应用配置,用户可通过PropertyGrid控件修改配置。字符串、整数类型的配置存读功能正常,但添加List<string>类型的关键词列表后,虽已通过TypeConverter使其在PropertyGrid中以逗号分隔形式显示并支持编辑,但调用SaveToIniFile方法时,该列表在INI文件中仍显示为(Collection),无法正确完成保存与加载操作。
解决方案
1. 修复转换器获取逻辑
原代码直接获取属性类型的默认转换器,未读取属性上标注的自定义TypeConverter。需改为优先读取属性的TypeConverterAttribute指定的转换器,确保自定义逻辑生效。
2. 完善自定义TypeConverter实现
原StringListConverter仅实现了ConvertTo方法,缺少从字符串还原为List<string>的ConvertFromString方法,同时需明确声明支持的转换类型,形成完整的存读闭环。
3. 移除冲突属性
DesignerSerializationVisibility(DesignerSerializationVisibility.Content)会导致序列化系统跳过自定义转换器,直接处理集合内容,需移除该属性。
修改后的完整代码
PropsClass.cs
public class PropsClass { [Description("Maximum year value."), Category("DataConvert"), DefaultValue(2050)] public int YearMaximum { get; set; } [Description("Null keyword, for example NaN or NULL, case sensitive."), Category("DataConvert"), DefaultValue("NULL")] public string KeyWordNull { get; set; } private List<string> _replaceItems = new List<string>(); [Description("List of items to replace."), Category("DataConvert"), DefaultValue("enter keywords here")] [Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] [TypeConverter(typeof(StringListConverter))] public List<string> ReplaceItemsList { get { return _replaceItems; } set { _replaceItems = value; } } [DllImport("kernel32.dll")] public static extern int GetPrivateProfileSection(string lpAppName, byte[] lpszReturnBuffer, int nSize, string lpFileName); public void SaveToIniFile(string filename) { using (var fp = new StreamWriter(filename, false, Encoding.UTF8)) { foreach (var section in GetType() .GetProperties() .GroupBy(x => ((CategoryAttribute)x.GetCustomAttributes(typeof(CategoryAttribute), false) .FirstOrDefault())?.Category ?? "General")) { fp.WriteLine(Environment.NewLine + "[{0}]", section.Key); foreach (var propertyInfo in section.OrderBy(x => x.Name)) { // 优先获取属性上的自定义TypeConverter TypeConverter converter = null; var typeConverterAttr = propertyInfo.GetCustomAttributes(typeof(TypeConverterAttribute), false) .FirstOrDefault() as TypeConverterAttribute; if (typeConverterAttr != null) { var converterType = Type.GetType(typeConverterAttr.ConverterTypeName); if (converterType != null) { converter = Activator.CreateInstance(converterType) as TypeConverter; } } // 无自定义转换器时使用类型默认转换器 converter ??= TypeDescriptor.GetConverter(propertyInfo.PropertyType); fp.WriteLine("{0}={1}", propertyInfo.Name, converter.ConvertToInvariantString(propertyInfo.GetValue(this, null))); } } } } public void ReadFromIniFile(string filename) { var loaded = GetType().GetProperties() .Select(x => ((CategoryAttribute)x.GetCustomAttributes(typeof(CategoryAttribute), false).FirstOrDefault())?.Category ?? "General") .Distinct() .ToDictionary(section => section, section => GetKeys(filename, section)); foreach (var propertyInfo in GetType().GetProperties()) { var category = ((CategoryAttribute)propertyInfo.GetCustomAttributes(typeof(CategoryAttribute), false).FirstOrDefault())?.Category ?? "General"; var name = propertyInfo.Name; if (loaded.ContainsKey(category) && loaded[category].ContainsKey(name) && !string.IsNullOrEmpty(loaded[category][name])) { var rawString = loaded[category][name]; // 同样优先获取自定义转换器 TypeConverter converter = null; var typeConverterAttr = propertyInfo.GetCustomAttributes(typeof(TypeConverterAttribute), false) .FirstOrDefault() as TypeConverterAttribute; if (typeConverterAttr != null) { var converterType = Type.GetType(typeConverterAttr.ConverterTypeName); if (converterType != null) { converter = Activator.CreateInstance(converterType) as TypeConverter; } } converter ??= TypeDescriptor.GetConverter(propertyInfo.PropertyType); if (converter.IsValid(rawString)) { propertyInfo.SetValue(this, converter.ConvertFromString(rawString), null); } } } } private Dictionary<string, string> GetKeys(string iniFile, string category) { var buffer = new byte[8 * 1024]; GetPrivateProfileSection(category, buffer, buffer.Length, iniFile); var tmp = Encoding.UTF8.GetString(buffer).Trim('\0').Split('\0'); return tmp.Select(x => x.Split(new[] { '=' }, 2)) .Where(x => x.Length == 2) .ToDictionary(x => x[0], x => x[1]); } }
StringListConverter.cs
public class StringListConverter : TypeConverter { // 声明支持从字符串转换为List<string> public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) return true; return base.CanConvertFrom(context, sourceType); } // 声明支持从List<string>转换为字符串 public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(string)) return true; return base.CanConvertTo(context, destinationType); } // 实现从逗号分隔字符串转换为List<string> public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string strValue) { if (string.IsNullOrEmpty(strValue)) return new List<string>(); return strValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()) .ToList(); } return base.ConvertFrom(context, culture, value); } // 实现从List<string>转换为逗号分隔字符串 public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == typeof(string) && value is List<string> list) { return string.Join(", ", list); } return base.ConvertTo(context, culture, value, destinationType); } // 验证字符串格式有效性 public override bool IsValid(ITypeDescriptorContext context, object value) { // 逗号分隔字符串格式默认有效,可按需添加自定义验证逻辑 if (value is string) return true; return base.IsValid(context, value); } }
说明
- 移除
DesignerSerializationVisibility(DesignerSerializationVisibility.Content)后,PropertyGrid依然可通过指定的StringCollectionEditor编辑列表,同时自定义转换器能正常工作。 - 修改后的存读方法会优先使用属性上标注的自定义转换器,确保
List<string>以逗号分隔形式写入INI文件,并能正确读取还原。
内容的提问来源于stack exchange,提问作者BdR
相关产品推荐
相关产品推荐

