Conditional "Browsable" Attribute

12,707

Solution 1

There is no easy way.

You can possibly work this out by implementing ICustomTypeDescriptor. Here is a good article about implementing ICustomTypeDescriptor.

Or you can associate your own ControlDesigner with your class and override the PreFilterProperties method to add or remove properties viewed in the property grid.

Removing certain properties from property grid.

Solution 2

I'm not sure this applies to your situation, but you can adjust the "Browsable" decoration at run-time by calling the function below.

/// <summary>
/// Set the Browsable property.
/// NOTE: Be sure to decorate the property with [Browsable(true)]
/// </summary>
/// <param name="PropertyName">Name of the variable</param>
/// <param name="bIsBrowsable">Browsable Value</param>
private void setBrowsableProperty(string strPropertyName, bool bIsBrowsable)
{
    // Get the Descriptor's Properties
    PropertyDescriptor theDescriptor = TypeDescriptor.GetProperties(this.GetType())[strPropertyName];

    // Get the Descriptor's "Browsable" Attribute
    BrowsableAttribute theDescriptorBrowsableAttribute = (BrowsableAttribute)theDescriptor.Attributes[typeof(BrowsableAttribute)];
    FieldInfo isBrowsable = theDescriptorBrowsableAttribute.GetType().GetField("Browsable", BindingFlags.IgnoreCase | BindingFlags.NonPublic | BindingFlags.Instance);

    // Set the Descriptor's "Browsable" Attribute
    isBrowsable.SetValue(theDescriptorBrowsableAttribute, bIsBrowsable);
}

Solution 3

You can do this by providing a custom type-model; at the simplest level, you can provide a custom TypeDescriptor for your type derived from ExpandableObjectConverter, and simply include/exclude the given property at whim - but this only works with PropertyGrid - used by the properties page. A more complex approach is to use ICustomTypeDescriptor / TypeDescriptionProvider - this can then work inside things like DataGridView

Solution 4

John Cummings's solution basically worked for me but had the following two problems due to his introduction of the Generics (which was quite smart though):

1- the version SetBrowsableProperty<T>(T obj, string strPropertyName, bool bIsBrowsable) will fail when a collection is passed as the parameter obj because T, in that case, will be an implementation of IEnumerable (e.g. List, Array etc.) and not the type of the collection that was actually intended.

2- It allows passing in the primitive types as well, which is pointless in this case and will nearly always fail.

Complete revised solution:

So here is the revised solution that tackles these problems and has worked for me: (I've slightly renamed the methods and the variables)

First of all, the actual method that does the main job of changing the Browsable attribute value:

/// <summary>
/// Sets the Browsable attribute value of a property of a non premitive type.
/// NOTE: The class property must be decorated with [Browsable(...)] attribute.
/// </summary>
/// <param name="type">The type that contains the property, of which the Browsable attribute value needs to be changed</param>
/// <param name="propertyName">Name of the type property, of which the Browsable attribute value needs to be changed</param>
/// <param name="isBrowsable">The new Browsable value</param>
public static void SetBrowsableAttributeOfAProperty(Type type, string propertyName, bool isBrowsable)
{
    //Validate type - disallow primitive types (this will eliminate problem-2 as mentioned above)
    if (type.IsEnum || BuiltInTypes.Contains(type))
        throw new Exception($"The type '{type.Name}' is not supported");

    var objPropertyInfo = TypeDescriptor.GetProperties(type);

    // Get the Descriptor's Properties
    PropertyDescriptor theDescriptor = objPropertyInfo[propertyName];

    if (theDescriptor == null)
        throw new Exception($"The property '{propertyName}' is not found in the Type '{type}'");

    // Get the Descriptor's "Browsable" Attribute
    BrowsableAttribute theDescriptorBrowsableAttribute = (BrowsableAttribute)theDescriptor.Attributes[typeof(BrowsableAttribute)];
    FieldInfo browsablility = theDescriptorBrowsableAttribute.GetType().GetField("Browsable", BindingFlags.IgnoreCase | BindingFlags.NonPublic | BindingFlags.Instance);

    // Set the Descriptor's "Browsable" Attribute
    browsablility.SetValue(theDescriptorBrowsableAttribute, isBrowsable);
}

Now the variant proposed in John Cummings's solution with <T>:

public static void SetBrowsableAttributeOfAProperty<T>(string propertyName, bool isBrowsable)
{
    SetBrowsableAttributeOfAProperty(typeof(T), propertyName, isBrowsable);
}

Now the overload that had the problem no. 1, but the following modification handles it now:

/// <summary>
/// Sets the Browsable attribute value of a property of a non premitive type.
/// NOTE: The class property must be decorated with [Browsable(...)] attribute.
/// </summary>
/// <param name="obj">An instance of the type that contains the property, of which the Browsable attribute value needs to be changed.</param>
/// <param name="propertyName">Name of the type property, of which the Browsable attribute value needs to be changed</param>
/// <param name="isBrowsable">Browsable Value</param>
public static void SetBrowsableAttributeOfAProperty<T>(T obj, string propertyName, bool isBrowsable)
{
    if (typeof(T).GetInterface("IEnumerable") != null && typeof(T) != typeof(string))   //String type needs to be filtered out as String also implements IEnumerable<char> but its not a normal collection rather a primitive type
    {
        //Get the element type of the IEnumerable collection
        Type objType = obj.GetType().GetGenericArguments()?.FirstOrDefault();       //when T is a collection that implements IEnumerable except Array
        if (objType == null) objType = obj.GetType().GetElementType();              //when T is an Array

        SetBrowsableAttributeOfAProperty(objType, propertyName, isBrowsable);
    }
    else
        SetBrowsableAttributeOfAProperty(typeof(T), propertyName, isBrowsable);

and here is a utility function to get all C# system built-in (primitive) types:

    public static List<Type> BuiltInTypes
        {
            get
            {
                if (builtInTypes == null)                
                    builtInTypes = Enum.GetValues(typeof(TypeCode)).Cast<TypeCode>().Select(t => Type.GetType("System." + Enum.GetName(typeof(TypeCode), t)))
                                   .ToList();
    
                return builtInTypes;
            }
        }

Usage:

 class Foo
{
    [Browsable(false)]
    public string Bar { get; set; }
}
void Example()
{
    SetBrowsableAttributeOfAProperty<Foo>("Bar", true);     //works
    
    Foo foo = new Foo();
    SetBrowsableAttributeOfAProperty(foo, "Bar", false);    //works

    List<Foo> foos = new List<Foo> { foo, new Foo { Bar = "item2" } };
    SetBrowsableAttributeOfAProperty(foos, "Bar", true);    //works now, whereas it would crash with an exception in John Cummings's solution
}

Solution 5

As an improvement on @neoikon's answer above and to avoid the exception Ganesh mentioned in the comments, here is a version that uses generics to get the type:

    /// <summary>
    /// Set the Browsable property.
    /// NOTE: Be sure to decorate the property with [Browsable(true)]
    /// </summary>
    /// <param name="PropertyName">Name of the variable</param>
    /// <param name="bIsBrowsable">Browsable Value</param>
    private void SetBrowsableProperty<T>(string strPropertyName, bool bIsBrowsable)
    {
        // Get the Descriptor's Properties
        PropertyDescriptor theDescriptor = TypeDescriptor.GetProperties(typeof(T))[strPropertyName];

        // Get the Descriptor's "Browsable" Attribute
        BrowsableAttribute theDescriptorBrowsableAttribute = (BrowsableAttribute)theDescriptor.Attributes[typeof(BrowsableAttribute)];
        FieldInfo isBrowsable = theDescriptorBrowsableAttribute.GetType().GetField("Browsable", BindingFlags.IgnoreCase | BindingFlags.NonPublic | BindingFlags.Instance);

        // Set the Descriptor's "Browsable" Attribute
        isBrowsable.SetValue(theDescriptorBrowsableAttribute, bIsBrowsable);
    }

You can then also add a version that takes an instance:

    /// <summary>
    /// Set the Browsable property.
    /// NOTE: Be sure to decorate the property with [Browsable(true)]
    /// </summary>
    /// <param name="obj">An instance of the object whose property should be modified.</param>
    /// <param name="PropertyName">Name of the variable</param>
    /// <param name="bIsBrowsable">Browsable Value</param>
    private void SetBrowsableProperty<T>(T obj, string strPropertyName, bool bIsBrowsable)
    {
        SetBrowsableProperty<T>(strPropertyName, bIsBrowsable);
    }

Usage:

    class Foo
    {
        [Browsable(false)]
        public string Bar { get; set; }
    }
    void Example()
    {
        SetBrowsableProperty<Foo>("Bar", true);
        Foo foo = new Foo();
        SetBrowsableProperty(foo, "Bar", false);
    }
Share:
12,707

Related videos on Youtube

Idov
Author by

Idov

Updated on February 19, 2022

Comments

  • Idov
    Idov over 2 years


    Is there a way to make a "Browsable" attribute conditional, so the property that applies it will sometimes appear in the properties page and sometimes not?
    thanks :)

  • gg89
    gg89 over 7 years
    the link for implementing ICustomTypeDescriptor.got pointed to msdn.microsoft.com/magazine/msdn-magazine-issues I think they have removed the original page
  • Ganesh Kamath - 'Code Frenzy'
    Ganesh Kamath - 'Code Frenzy' about 7 years
    Hi. I am looking for something like this. When I use your code, I am getting a nullValue Exception in the first line PropertyDescriptor theDescriptor = TypeDescriptor.GetProperties(this.GetType())[strPropertyName‌​];. I want to know where to place this functions and how it reflects on the Property grid (did not see a propertyGrid object in the function).
  • Ganesh Kamath - 'Code Frenzy'
    Ganesh Kamath - 'Code Frenzy' about 7 years
    Thank you so much for your solution, i changed the line to PropertyDescriptor theDescriptor = TypeDescriptor.GetProperties(vObject.GetType())[strPropertyN‌​ame‌​];, where vObject is the object I am altering.
  • Nastaran Hakimi
    Nastaran Hakimi about 5 years
    I should say that your solution causes all properties browsable enable or disable which is not the aim of this question.
  • Umar T.
    Umar T. almost 5 years
    Besides giving this solution an up vote as it basically worked for me, I must mention that it has two problems due to the introduction of generics, that would cause it to fail. Please see the problems and a complete improved solution here: stackoverflow.com/a/56376788/1300390
  • skavan
    skavan about 2 years
    This is very clever - but it turns off/on Browsable on all instances of a class. Is there way to apply it to a specific instance?