PropertyGrid可扩展集合

And*_*ker 10 .net c# propertygrid typeconverter propertydescriptor

我希望自动显示每个IList在我的可扩展PropertyGrid(通过"可扩展",我显然意味着将显示项目).我不想在每个列表上使用属性(再次,我希望它适用于每个列表IList)

我试图通过使用自定义PropertyDescriptor和a来实现它ExpandableObjectConverter.它工作正常,但在我从列表中删除项目后,PropertyGrid没有刷新,仍然显示已删除的项目.

我尝试使用ObservableCollection同时提高OnComponentChanged,也RefreshProperties属性,但没有任何效果.

这是我的代码:

public class ExpandableCollectionPropertyDescriptor : PropertyDescriptor
{
    private IList _collection;

    private readonly int _index = -1;

    internal event EventHandler RefreshRequired;

    public ExpandableCollectionPropertyDescriptor(IList coll, int idx) : base(GetDisplayName(coll, idx), null)
    {
        _collection = coll
        _index = idx;
    }

    public override bool SupportsChangeEvents
    {
        get { return true; }
    }

    private static string GetDisplayName(IList list, int index)
    {

        return "[" + index + "]  " + CSharpName(list[index].GetType());
    }

    private static string CSharpName(Type type)
    {
        var sb = new StringBuilder();
        var name = type.Name;
        if (!type.IsGenericType)
            return name;
        sb.Append(name.Substring(0, name.IndexOf('`')));
        sb.Append("<");
        sb.Append(string.Join(", ", type.GetGenericArguments()
                                        .Select(CSharpName)));
        sb.Append(">");
        return sb.ToString();
    }

    public override AttributeCollection Attributes
    {
        get 
        { 
            return new AttributeCollection(null);
        }
    }

    public override bool CanResetValue(object component)
    {

        return true;
    }

    public override Type ComponentType
    {
        get 
        { 
            return _collection.GetType();
        }
    }

    public override object GetValue(object component)
    {
        OnRefreshRequired();

        return _collection[_index];
    }

    public override bool IsReadOnly
    {
        get { return false;  }
    }

    public override string Name
    {
        get { return _index.ToString(); }
    }

    public override Type PropertyType
    {
        get { return _collection[_index].GetType(); }
    }

    public override void ResetValue(object component)
    {
    }

    public override bool ShouldSerializeValue(object component)
    {
        return true;
    }

    public override void SetValue(object component, object value)
    {
         _collection[_index] = value;
    }

    protected virtual void OnRefreshRequired()
    {
        var handler = RefreshRequired;
        if (handler != null) handler(this, EventArgs.Empty);
    }
}
Run Code Online (Sandbox Code Playgroud)

.

internal class ExpandableCollectionConverter : ExpandableObjectConverter
{
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destType)
    {
        if (destType == typeof(string))
        {
            return "(Collection)";
        }
        return base.ConvertTo(context, culture, value, destType);
    }

    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
    {
        IList collection = value as IList;
        PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null);

        for (int i = 0; i < collection.Count; i++)
        {
            ExpandableCollectionPropertyDescriptor pd = new ExpandableCollectionPropertyDescriptor(collection, i);
            pd.RefreshRequired += (sender, args) =>
            {
                var notifyValueGivenParentMethod = context.GetType().GetMethod("NotifyValueGivenParent", BindingFlags.NonPublic | BindingFlags.Instance);
                notifyValueGivenParentMethod.Invoke(context, new object[] {context.Instance, 1});
            };
            pds.Add(pd);
        }
        // return the property descriptor Collection
        return pds;
    }
}
Run Code Online (Sandbox Code Playgroud)

我用IList以下几行将它用于所有s:

TypeDescriptor.AddAttributes(typeof (IList), new TypeConverterAttribute(typeof(ExpandableCollectionConverter)));
Run Code Online (Sandbox Code Playgroud)

一些澄清

我希望在更改列表时网格自动更新.当另一个属性发生变化时刷新并没有帮助.

一个有效的解决方案是一个解决方案,其中:

  1. 如果在列表为空时展开列表,然后添加项目,则会使用展开的项目刷新网格
  2. 如果您将项目添加到列表,展开它,然后删除项目(不折叠),网格将刷新与扩展的项目,而不是抛出,ArgumentOutOfRangeException因为它试图显示已删除的项目
  3. 我想要一个配置实用程序的全部内容.只有PropertyGrid应该更改集合

重要编辑:

我还是设法使扩大收藏与更新Reflection,并呼吁NotifyValueGivenParenton方法context的对象时PropertyDescriptorGetValue方法被调用(当RefreshRequired事件引发的):

var notifyValueGivenParentMethod = context.GetType().GetMethod("NotifyValueGivenParent", BindingFlags.NonPublic | BindingFlags.Instance);
notifyValueGivenParentMethod.Invoke(context, new object[] {context.Instance, 1});
Run Code Online (Sandbox Code Playgroud)

它完美地工作,除了它导致事件被无限次地提升,因为调用NotifyValueGivenParent导致重新加载,因此PropertyDescriptor,引发事件,等等.

我试图通过添加一个简单的标志来解决它,如果它已经重新加载将阻止重新加载,但由于某种原因,NotifyValueGivenParent行为是异步的,因此重新加载发生在标志关闭后.也许这是另一个探索的方向.唯一的问题是递归

Gyö*_*zeg 5

没有必要使用ObservableCollection. 您可以按如下方式修改描述符类:

public class ExpandableCollectionPropertyDescriptor : PropertyDescriptor
{
    private IList collection;
    private readonly int _index;

    public ExpandableCollectionPropertyDescriptor(IList coll, int idx)
        : base(GetDisplayName(coll, idx), null)
    {
        collection = coll;
        _index = idx;
    }

    private static string GetDisplayName(IList list, int index)
    {
        return "[" + index + "]  " + CSharpName(list[index].GetType());
    }

    private static string CSharpName(Type type)
    {
        var sb = new StringBuilder();
        var name = type.Name;
        if (!type.IsGenericType)
            return name;
        sb.Append(name.Substring(0, name.IndexOf('`')));
        sb.Append("<");
        sb.Append(string.Join(", ", type.GetGenericArguments()
                                        .Select(CSharpName)));
        sb.Append(">");
        return sb.ToString();
    }

    public override bool CanResetValue(object component)
    {
        return true;
    }

    public override Type ComponentType
    {
        get { return this.collection.GetType(); }
    }

    public override object GetValue(object component)
    {
        return collection[_index];
    }

    public override bool IsReadOnly
    {
        get { return false; }
    }

    public override string Name
    {
        get { return _index.ToString(CultureInfo.InvariantCulture); }
    }

    public override Type PropertyType
    {
        get { return collection[_index].GetType(); }
    }

    public override void ResetValue(object component)
    {
    }

    public override bool ShouldSerializeValue(object component)
    {
        return true;
    }

    public override void SetValue(object component, object value)
    {
        collection[_index] = value;
    }
}
Run Code Online (Sandbox Code Playgroud)

ExpandableCollectionConverter我将派生该类,而不是CollectionConverter派生类,因此您仍然可以使用省略号按钮以旧方式编辑集合(因此,如果集合不是只读的,您可以添加/删除项目):

public class ListConverter : CollectionConverter
{
    public override bool GetPropertiesSupported(ITypeDescriptorContext context)
    {
        return true;
    }

    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
    {
        IList list = value as IList;
        if (list == null || list.Count == 0)
        return base.GetProperties(context, value, attributes);

        var items = new PropertyDescriptorCollection(null);
        for (int i = 0; i < list.Count; i++)
        {
            object item = list[i];
            items.Add(new ExpandableCollectionPropertyDescriptor(list, i));
        }
        return items;
    }
}
Run Code Online (Sandbox Code Playgroud)

我会ListConverter在我想要查看可扩展列表的属性上使用它。当然,您通常可以像在示例中那样注册类型转换器,但这会覆盖所有内容,这可能不是总体意图。

public class MyClass 
{
    [TypeConverter(typeof(ListConverter))]
    public List<int> List { get; set; }

    public MyClass()
    {
        List = new List<int>();
    }

    [RefreshProperties(RefreshProperties.All)]
    [Description("Change this property to regenerate the List")]
    public int Count
    {
        get { return List.Count; }
        set { List = Enumerable.Range(1, value).ToList(); }
    }
}
Run Code Online (Sandbox Code Playgroud)

重要提示RefreshProperties应该为更改其他属性的属性定义属性。在此示例中,更改Count将替换整个列表。

使用它会propertyGrid1.SelectedObject = new MyClass();产生以下结果:

在此输入图像描述

  • 这不是我想要的。我不希望它在其他属性刷新时刷新。我希望它在列表更改时刷新。我将项目添加到列表中,展开它,添加更多项目,但项目没有更新 (2认同)