Desidero mostrare automaticamente ogni IList
come espandibile nel mio PropertyGrid
(Per "espandibile", ovviamente intendo che gli elementi verranno visualizzati). Non voglio utilizzare gli attributi di ciascuna lista (ancora una volta, voglio che funzionare per ogni IList
)Raccolta espandibile PropertyGrid
ho cercato di achive esso utilizzando una consuetudine PropertyDescriptor
ed un ExpandableObjectConverter
. Funziona, ma dopo aver eliminato gli elementi dall'elenco, lo PropertyGrid
non viene aggiornato, continuando a visualizzare gli elementi eliminati.
Ho provato a utilizzare ObservableCollection
insieme ad aumentare OnComponentChanged
e anche attributo RefreshProperties
, ma non ha funzionato.
Questo è il mio codice:
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;
}
}
e lo uso per tutti IList
s con la seguente riga:
TypeDescriptor.AddAttributes(typeof (IList), new TypeConverterAttribute(typeof(ExpandableCollectionConverter)));
alcuni chiarimenti
voglio griglia per aggiornare automaticamente quando cambio la lista. L'aggiornamento quando un'altra proprietà cambia, non aiuta.
una soluzione che funziona, è una soluzione in cui:
- Se si espande la lista mentre è vuota, e quindi aggiungere gli elementi, la griglia viene aggiornata con gli articoli espansi
- Se si aggiungono elementi alla lista, espanderla e quindi rimuovere gli elementi (senza collassare), la griglia viene aggiornata con gli elementi espansi e non lanciando
ArgumentOutOfRangeException
perché sta tentando di mostrare elementi che sono stati già eliminati - Voglio tutto questo per un utilità di configurazione. Solo il
PropertyGrid
dovrebbe cambiare le collezioni
EDIT IMPORTANTE:
sono riuscito a rendere le collezioni espansi aggiornano con Reflection
, e chiamando NotifyValueGivenParent
metodo sull'oggetto context
quando il metodo GetValue PropertyDescriptor
viene chiamato (quando RefreshRequired
evento viene generato):
var notifyValueGivenParentMethod = context.GetType().GetMethod("NotifyValueGivenParent", BindingFlags.NonPublic | BindingFlags.Instance);
notifyValueGivenParentMethod.Invoke(context, new object[] {context.Instance, 1});
funziona perfettamente, tranne che provoca l'evento essere innalzato infinite volte, perché chiamare NotifyValueGivenParent
causa un ricaricamento di PropertyDescriptor
e, quindi, innalzare l'evento e così via.
ho cercato di risolverlo aggiungendo una semplice bandiera che impedirà la ricarica se è già ricaricando, ma per qualche ragione NotifyValueGivenParent
si comporta in modo asincrono, e quindi la ricarica avviene dopo la bandiera è spento. Forse è un'altra direzione da esplorare.L'unico problema è la ricorsione
Perché non si chiama 'TypeDescriptor.AddAttributes (typeof (IList), nuovo TypeConverterAttribute (typeof (ExpandableObjectConverter)))?' invece della classe personalizzata? –
@SimonMourier perché non riesco a vedere gli elementi nella raccolta, ma le proprietà 'Capacity' e' Count' –
Questo requisito non viene visualizzato nella domanda. A proposito, funziona per me con una proprietà di tipo ArrayList. Suppongo che dipenda dalla classe in SelectedObject. dovresti rispondere alla tua domanda con tutto il codice pertinente e l'intera domanda. –