You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 23:35:44