我希望在我的IList
中自动将每个PropertyGrid
显示为可扩展的(通过"可扩展",我显然意味着将显示这些项目)。
我不想在每个列表中使用属性(再次,我希望它适用于每个IList
)
我尝试使用自定义PropertyDescriptor
和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);
}
}
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
:
TypeDescriptor.AddAttributes(typeof (IList), new TypeConverterAttribute(typeof(ExpandableCollectionConverter)));
一些澄清
我希望在更改列表时网格自动更新。当另一个属性发生变化时刷新也无济于事。
有效的解决方案是:
ArgumentOutOfRangeException
,因为它正在尝试显示已删除的项目已经PropertyGrid
才能更改集合重要编辑:
我确实设法使用Reflection
更新扩展集合,并在调用context
GetValue方法时调用PropertyDescriptor
对象上的NotifyValueGivenParent
方法(当{{ 1}}事件被提出):
RefreshRequired
它完美地工作,除非它导致事件被无限次提升,因为调用var notifyValueGivenParentMethod = context.GetType().GetMethod("NotifyValueGivenParent", BindingFlags.NonPublic | BindingFlags.Instance);
notifyValueGivenParentMethod.Invoke(context, new object[] {context.Instance, 1});
会导致重新加载NotifyValueGivenParent
,因此,引发事件,等等。
我尝试通过添加一个简单的标志来解决它,如果它已经重新加载将阻止重新加载,但由于某种原因,PropertyDescriptor
表现异步,因此重新加载在标志关闭后发生。
也许这是另一个探索的方向。唯一的问题是递归
答案 0 :(得分:3)
无需使用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;
}
}
而不是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;
}
}
我会在我希望看到可扩展列表的属性上使用此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(); }
}
}
重要:应为更改其他属性的属性定义RefreshProperties
属性。在此示例中,更改Count
将替换整个列表。
将其用作propertyGrid1.SelectedObject = new MyClass();
会产生以下结果:
答案 1 :(得分:3)
当其他属性刷新时,我不希望它刷新。我希望它在列表更改时刷新。我将项目添加到列表,展开,添加更多项目,但项目未更新
这是PropertyGrid
的典型误用。它用于配置组件,而不是用于反映外部源即时更改的并发更改。即使将IList
包装到ObservableCollection
也无济于事,因为它仅由您的描述符使用,而外部源直接操作基础IList
实例。
你仍然可以做的是特别难看的黑客:
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).
}
}
现在您可以像这样使用它:
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
的实施者必须是可序列化的答案 2 :(得分:0)
将所有内容放在一起,可以起作用:
这是带有列表的类,我们将在其属性网格中放置一个实例。为了演示复杂对象列表的用法,我还有NameAgePair类。
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类,用于处理子节点的创建。
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类。
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类,用于在关闭集合编辑器后刷新属性网格。
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);
}
}
现在用法产生:
执行各种操作会产生:
您可以调整它的操作使其自如。