czwartek, 28 stycznia 2010

C#: zmiana wartości atrybutu podczas działania aplikacji [PL]

Promuj
Jakiś czas temu natknąłem się na problem podczas pracy z PropertyGrid'em. Aby móc edytować obiekty przy pomocy PrpopertyGrid'a należy nadawać odpowiednim właściwością klasy (której obiekty chcemy edytować) atrybuty. Jednak ja dodatkowo potrzebowałem, by edytor danego obiektu zmieniał się w czasie działania aplikacji, pojawiło się więc pytanie:
"Jak zmienić (w c#, .NET) wartość atrybutu klasy, funkcji, właściwości podczas działania aplikacji z poziomu kodu programu?"

Zobaczmy przykład: mamy klasę:
public class User
{
 [BrowsableAttribute( true )]
  public string Name {get; set;}
 [BrowsableAttribute( false )]
  public string SecondName {get; set;}
}

Chcemy umożliwić użytkownikowi edytowanie obiektów tej klasy poprzez PropertyGrid i zmieniać wartość atrybutu BrowsableAttribute dla SecondName czasem na true a czasem na false w zależności od potrzeb. Jak to zrobić? Jak zmienić wartość atrybutu?

Do rozwiązania tego problemu postanowiłem wykorzystać nowy polski serwis Q&A i zadałem pytanie. Szybko dowiedziałem się, że "Nie jest to możliwe dlatego... gdyż.... atrybuty są częścią kodu. Kiedy kompilujesz kod atrybuty lądują sobie w pliku IL i koniec.". W sumie racja, ale ktoś inny podał linka, gdzie znajdowała się wskazówka, jak to obejść i zaprowadziło mnie to do artykułu: "ICustomTypeDescriptor, Part 1", autorstwa: Stephen Toub.

Teraz już się stało dla mnie jasne: otóż Wartości atrybutów nie da się zmienić, ale za to można stworzyć klasę typu proxy, która będzie zwracała trochę odmienionych informacji odnośnie klasy którą przesłania. Wykorzystując ICustomTypeDescriptor można stworzyć tak klasę proxy, która będzie zwracała inne wartości atrybutów w zależności od jej ustawienia.

Zobaczmy przykład: powyższą klasę User będziemy zmieniać przez PropertyGrid , ale jako element wybrany w PropertyGrid możemy użyć klasy proxy, o której mówiłem. Mamy więc:
public partial class Form1 : Form
{
    User usr;
    AttributesProxyTypeDescriptor proxy;
    public Form1()
    {
        InitializeComponent();
        usr = new User();
        proxy = new AttributesProxyTypeDescriptor(usr); 
        propertyGrid1.SelectedObject = proxy;
    }

    private void checkBox1_CheckedChanged(object sender, EventArgs e)
    {
        proxy.BrowsableAttributeAlwaysTrue = checkBox1.Checked;
        propertyGrid1.Refresh();
    }
}

Natomiast klasa proxy implementuje interfejs ICustomTypeDescriptor :
public class AttributesProxyTypeDescriptor : ICustomTypeDescriptor
{
    private object _target;
    public bool BrowsableAttributeAlwaysTrue { get; set; }
    public AttributesProxyTypeDescriptor(object target)
    {
        if (target == null) throw new ArgumentNullException("target");
        _target = target;
        BrowsableAttributeAlwaysTrue = false;
    }
    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
    {
        return _target;
    }
    AttributeCollection ICustomTypeDescriptor.GetAttributes()
    {
        return TypeDescriptor.GetAttributes(_target, true);
    }
    string ICustomTypeDescriptor.GetClassName()
    {
        return TypeDescriptor.GetClassName(_target, true);
    }
    string ICustomTypeDescriptor.GetComponentName()
    {
        return TypeDescriptor.GetComponentName(_target, true);
    }
    TypeConverter ICustomTypeDescriptor.GetConverter()
    {
        return TypeDescriptor.GetConverter(_target, true);
    }
    EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
    {
        return TypeDescriptor.GetDefaultEvent(_target, true);
    }
    PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
    {
        return TypeDescriptor.GetDefaultProperty(_target, true);
    }
    object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
    {
        return TypeDescriptor.GetEditor(_target, editorBaseType, true);
    }
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
    {
        return TypeDescriptor.GetEvents(_target, attributes, true);
    }
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
    {
        return TypeDescriptor.GetEvents(_target, true);
    }
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
    {
        return ((ICustomTypeDescriptor)this).GetProperties(null);
    }
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
    {
        PropertyDescriptorCollection props_tobereturned = new PropertyDescriptorCollection(null);
        if (BrowsableAttributeAlwaysTrue)
        {
            foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(_target, true))
            {

                props_tobereturned.Add(new MyPropertyDescriptor(prop, attributes));
            }
        }
        else
        {
            props_tobereturned = TypeDescriptor.GetProperties(_target, attributes, true);
        }
        return props_tobereturned;
    }

}
gdzie MyProperyDescriptor to:
public class MyPropertyDescriptor : PropertyDescriptor
{
    private PropertyDescriptor _propertyDescriptorOrg;
    public MyPropertyDescriptor(PropertyDescriptor field, Attribute[] attributes) :
        base(field.Name, attributes)
    {
        _propertyDescriptorOrg = field;
    }
    public override bool Equals(object obj)
    {
        MyPropertyDescriptor other = obj as MyPropertyDescriptor;
        return other != null && other._propertyDescriptorOrg.Equals(_propertyDescriptorOrg);
    }
    public override int GetHashCode() { return _propertyDescriptorOrg.GetHashCode(); }
    public override bool IsReadOnly { get { return _propertyDescriptorOrg.IsReadOnly; } }
    public override void ResetValue(object component) { _propertyDescriptorOrg.ResetValue(component); }
    public override bool CanResetValue(object component) { return _propertyDescriptorOrg.CanResetValue(component); }
    public override bool ShouldSerializeValue(object component) { return _propertyDescriptorOrg.ShouldSerializeValue(component); }
    public override Type ComponentType { get { return _propertyDescriptorOrg.ComponentType; } }
    public override Type PropertyType { get { return _propertyDescriptorOrg.PropertyType; } }
    public override object GetValue(object component) { return _propertyDescriptorOrg.GetValue(component); }
    public override void SetValue(object component, object value)
    {
        _propertyDescriptorOrg.SetValue(component, value);
        OnValueChanged(component, EventArgs.Empty);
    }
} 
Na końcu chciałbym dodać, że omawiany kod dostępny jest do pobrania z tej lokalizacji.
Promuj

4 komentarze:

  1. Można to zrobić dużo łatwiej przy użyciu klasy TypeConverter, przyciązając tylko dwie metody: GetProperties() i GetPropertiesSupported() i dodając do klasy User atrybut TypeConverterAttribute z odpowiednim typem.

    OdpowiedzUsuń
  2. a mógłbyś podać linka lub fragment kodu to realizujący?

    OdpowiedzUsuń
  3. Proszę bardzo:

    [TypeConverter(typeof(MyTypeConverter))]
    public class AttributesProxyTypeDescriptor
    {
    private class MyTypeConverter : TypeConverter
    {
    public override bool GetPropertiesSupported(ITypeDescriptorContext context)
    {
    return true;
    }

    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
    {
    AttributesProxyTypeDescriptor proxy = ((AttributesProxyTypeDescriptor)value);

    if (proxy.BrowsableAttributeAlwaysTrue) attributes = null;

    PropertyDescriptorCollection collection = new PropertyDescriptorCollection(null);

    foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(proxy._target, attributes, true))
    collection.Add(new MyPropertyDescriptor(pd));

    return collection;
    }

    private class MyPropertyDescriptor : SimplePropertyDescriptor
    {
    private readonly PropertyDescriptor _mypd;

    public MyPropertyDescriptor(PropertyDescriptor a_pd)
    : base(a_pd.ComponentType, a_pd.Name, a_pd.PropertyType)
    {
    _mypd = a_pd;
    }

    public override object GetValue(object component)
    {
    return _mypd.GetValue(((AttributesProxyTypeDescriptor)component)._target);
    }

    public override void SetValue(object component, object value)
    {
    _mypd.SetValue(((AttributesProxyTypeDescriptor)component)._target, value);
    }
    }
    }

    private readonly object _target;

    public bool BrowsableAttributeAlwaysTrue { get; set; }

    public AttributesProxyTypeDescriptor(object target)
    {
    if (target == null) throw new ArgumentNullException("target");
    _target = target;
    }
    }

    OdpowiedzUsuń

Posty powiązane / Related posts