首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >PropertyGrid可扩展集合

PropertyGrid可扩展集合
EN

Stack Overflow用户
提问于 2015-09-15 09:30:06
回答 3查看 7.1K关注 0票数 10

我想自动显示每个IList在我的PropertyGrid中都是可扩展的(通过“可扩展”,我的意思显然是项目将被显示)。我不想在每个列表上使用属性(再一次,我希望它对每个IList都有效)

我试图通过使用自定义PropertyDescriptorExpandableObjectConverter来实现它。它可以工作,但是在我从列表中删除项目后,PropertyGrid将不会被刷新,仍然会显示已删除的项。

我试图在提高ObservableCollectionRefreshProperties属性的同时使用RefreshProperties,但是没有什么效果。

这是我的密码:

代码语言:javascript
运行
复制
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);
    }
}

代码语言:javascript
运行
复制
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;
    }
}

我将其用于所有IList,并使用以下行:

代码语言:javascript
运行
复制
TypeDescriptor.AddAttributes(typeof (IList), new TypeConverterAttribute(typeof(ExpandableCollectionConverter)));

一些澄清

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

有效的解决方案是这样的解决方案:

  1. 如果您在列表为空时展开该列表,然后添加项,则会用展开的项刷新网格。
  2. 如果将项添加到列表中,展开它,然后删除项(不折叠),则网格将刷新展开的项,而不会抛出ArgumentOutOfRangeException,因为它试图显示已删除的项。
  3. 我要把这整件事作为配置实用程序。只有PropertyGrid应该更改集合

重要编辑:

我成功地用Reflection更新了扩展集合,并在调用PropertyDescriptor GetValue方法时(当引发RefreshRequired事件时)调用了context对象上的PropertyDescriptor方法:

代码语言:javascript
运行
复制
var notifyValueGivenParentMethod = context.GetType().GetMethod("NotifyValueGivenParent", BindingFlags.NonPublic | BindingFlags.Instance);
notifyValueGivenParentMethod.Invoke(context, new object[] {context.Instance, 1});

它工作得很好,但它会导致事件无限次被引发,因为调用NotifyValueGivenParent会导致PropertyDescriptor的重新加载,因此引发事件等等。

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

EN

回答 3

Stack Overflow用户

发布于 2015-09-25 15:36:02

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

代码语言:javascript
运行
复制
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;
    }
}

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

代码语言:javascript
运行
复制
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;
    }
}

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

代码语言:javascript
运行
复制
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(); }
    }
}

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

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

票数 5
EN

Stack Overflow用户

发布于 2015-09-27 13:20:58

当其他属性刷新时,我不希望它刷新。当列表被更改时,我希望它刷新。我将项目添加到列表中,展开它,添加更多的项目,但是这些项目没有更新。

这是对PropertyGrid的典型误用。它用于配置组件,而不是用于反映外部源动态进行的并发更改。即使将IList封装到ObservableCollection中也无助于您,因为它仅由描述符使用,而外部源则直接操作底层IList实例。

您仍然可以做的是,特别是丑陋的黑客

代码语言:javascript
运行
复制
public class ExpandableCollectionPropertyDescriptor : PropertyDescriptor
{
    // Subscribe to this event from the form with the property grid
    public static event EventHandler CollectionChanged;

    // Tuple elements: The owner of the list, the list, the serialized content of the list
    // The reference to the owner is a WeakReference because you cannot tell the
    // PropertyDescriptor that you finished the editing and the collection
    // should be removed from the list.
    // Remark: The references here may survive the property grid's life
    private static List<Tuple<WeakReference, IList, byte[]>> collections;
    private static Timer timer;

    public ExpandableCollectionPropertyDescriptor(ITypeDescriptorContext context, IList collection, ...)
    {
        AddReference(context.Instance, collection);
        // ...
    }

    private static void AddReference(object owner, IList collection)
    {
        // TODO:
        // - serialize the collection into a byte array (BinaryFormatter) and add it to the collections list
        // - if this is the first element, initialize the timer
    }

    private static void Timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        // TODO: Cycle through the collections elements
        // - If WeakReference is not alive, remove the item from the list
        // - Serialize the list again and compare the result to the last serialized content
        // - If there a is difference:
        //   - Update the serialized content
        //   - Invoke the CollectionChanged event. The sender is the owner (WeakReference.Target).
    }
}

现在您可以这样使用它:

代码语言:javascript
运行
复制
public class Form1 : Form
{
    MyObject myObject = new MyObject();

    public MyForm()
    {
        InitializeComponent();
        ExpandableCollectionPropertyDescriptor.CollectionChanged += CollectionChanged();
        propertyGrid.SelectedObject = myObject;
    }

    private void CollectionChanged(object sender, EventArgs e)
    {
        if (sender == myObject)
            propertyGrid.SelectedObject = myObject;
    }
}

但老实说,我根本不会用它。它有严重的缺陷:

  • 如果集合元素被PropertyGrid更改,但计时器尚未更新上一次外部更改,怎么办?
  • IList的实现者必须是可序列化的
  • 荒谬的性能开销
  • 虽然使用弱引用可能会减少内存泄漏,但如果要编辑的对象的生命周期比编辑器窗体长,则没有帮助,因为它们将保留在静态集合中。
票数 3
EN

Stack Overflow用户

发布于 2020-10-20 19:06:16

把所有这些放在一起,这是可行的:

下面是包含列表的类,我们将在属性网格中放置一个实例。此外,为了演示使用复杂对象列表的用法,我使用了NameAgePair类。

代码语言:javascript
运行
复制
public class SettingsStructure
{
    public SettingsStructure()
    {
        //To programmatically add this to properties that implement ILIST for the naming of the edited node and child items:
        //[TypeConverter(typeof(ListConverter))]
        TypeDescriptor.AddAttributes(typeof(IList), new TypeConverterAttribute(typeof(ListConverter)));

        //To programmatically add this to properties that implement ILIST for the refresh and expansion of the edited node
        //[Editor(typeof(CollectionEditorBase), typeof(System.Drawing.Design.UITypeEditor))]
        TypeDescriptor.AddAttributes(typeof(IList), new EditorAttribute(typeof(CollectionEditorBase), typeof(UITypeEditor)));
    }

    public List<string> ListOfStrings { get; set; } = new List<string>();
    public List<string> AnotherListOfStrings { get; set; } = new List<string>();
    public List<int> ListOfInts { get; set; } = new List<int>();
    public List<NameAgePair> ListOfNameAgePairs { get; set; } = new List<NameAgePair>();
}

public class NameAgePair
{
    public string Name { get; set; } = "";
    public int Age { get; set; } = 0;

    public override string ToString()
    {
        return $"{Name} ({Age})";
    }
}

下面是处理生成子节点的ListConverter类。

代码语言:javascript
运行
复制
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;
    }

    public override object ConvertTo(ITypeDescriptorContext pContext, CultureInfo pCulture, object value, Type pDestinationType)
    {
        if (pDestinationType == typeof(string))
        {
            IList v = value as IList;
            int iCount = (v == null) ? 0 : v.Count;
            return $"({iCount} Items)";
        }
        return base.ConvertTo(pContext, pCulture, value, pDestinationType);
    }
}

下面是各个项的ExpandableCollectionPropertyDescriptor类。

代码语言:javascript
运行
复制
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;
    }
}

然后是用于在集合编辑器关闭后刷新属性网格的CollectionEditorBase类。

代码语言:javascript
运行
复制
public class CollectionEditorBase : CollectionEditor
{
    protected PropertyGrid _PropertyGrid;
    private bool _ExpandedBefore;
    private int _CountBefore;

    public CollectionEditorBase(Type type) : base(type) { }

    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
    {
        //Record entry state of property grid item
        GridItem giThis = (GridItem)provider;
        _ExpandedBefore = giThis.Expanded;
        _CountBefore = (giThis.Value as IList).Count;

        //Get the grid so later we can refresh it on close of editor
        PropertyInfo piOwnerGrid = provider.GetType().GetProperty("OwnerGrid", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        _PropertyGrid = (PropertyGrid)piOwnerGrid.GetValue(provider);

        //Edit the collection
        return base.EditValue(context, provider, value);
    }

    protected override CollectionForm CreateCollectionForm()
    {
        CollectionForm cf = base.CreateCollectionForm();
        cf.FormClosing += delegate (object sender, FormClosingEventArgs e)
        {
            _PropertyGrid.Refresh();
            //Because nothing changes which grid item is the selected one, expand as desired
            if (_ExpandedBefore || _CountBefore == 0) _PropertyGrid.SelectedGridItem.Expanded = true; 
        };
        return cf;
    }

    protected override object CreateInstance(Type itemType)
    {
        //Fixes the "Constructor on type 'System.String' not found." when it is an empty list of strings
        if (itemType == typeof(string)) return string.Empty;
        else return Activator.CreateInstance(itemType);
    }
}

现在这种用法产生了:

执行各种操作可产生:

你可以根据你的喜好来调整它。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32582504

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档