Is it possible to hide properties of a class by default, e.g. in a DataGridView?

2.4k Views Asked by At

I'm well aware of the <System.ComponentModel.Browsable("False")> attribute that I can apply to every property of a class. Is it possible to set the default value of the Browsable attribute for all properties to False?

The following code compiles and illustrates what I would like to achieve, but sadly it doesn't work as desired:

<Browsable(False)>
Public Class SomeClass

    Public Property HiddenByDefaultProperty1 As Object
    Public Property HiddenByDefaultProperty2 As Object

    ...

    <Browsable(True)> Public Property BrowsableProperty As Object

End Class

To achieve what I want I would have to apply <Browsable(False)> to all the properties I do not want to show in my DataGridView, which is a lot of code mess.

It would be great, if I would only have to specify <Browsable(True)> for the properties I want to show. But: is it possible?

2

There are 2 best solutions below

1
On

No, I don't think this is possible using the browsable attribute. However, you can control which properties the DataGridView will bind to by implementing the ICustomTypeDescriptor interface.

Model

C#:

public class Foo : ICustomTypeDescriptor
{

    public string P1 { get; set; }
    public string P2 { get; set; }
    public string P3 { get; set; }
    public string P4 { get; set; }
    public string P5 { get; set; }
    public string P6 { get; set; }
    public string P7 { get; set; }
    public string P8 { get; set; }
    public string P9 { get; set; }

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
    {

        var properties = new[] { "P1", "P3", "P7" };

        var descriptors = TypeDescriptor
            .GetProperties(typeof(Foo))
            .Cast<PropertyDescriptor>()
            .Where(p => properties.Any(s => s == p.Name))
            .ToArray();

        return new PropertyDescriptorCollection(descriptors);

    }

    AttributeCollection ICustomTypeDescriptor.GetAttributes()
    {
        return new AttributeCollection(null);
    }

    string ICustomTypeDescriptor.GetClassName()
    {
        return null;
    }

    string ICustomTypeDescriptor.GetComponentName()
    {
        return null;
    }

    TypeConverter ICustomTypeDescriptor.GetConverter()
    {
        return null;
    }

    EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
    {
        return null;
    }

    PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
    {
        return null;
    }

    object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
    {
        return null;
    }

    EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
    {
        return new EventDescriptorCollection(null);
    }

    EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
    {
        return new EventDescriptorCollection(null);
    }

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
    {
        return ((ICustomTypeDescriptor)this).GetProperties(null);
    }

    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
    {
        return this;
    }
}

VB.Net:

Public Class Foo
    Implements ICustomTypeDescriptor

    Public Property P1 As String
    Public Property P2 As String
    Public Property P3 As String
    Public Property P4 As String
    Public Property P5 As String
    Public Property P6 As String
    Public Property P7 As String
    Public Property P8 As String
    Public Property P9 As String

    Private Function GetProperties(attributes() As Attribute) As PropertyDescriptorCollection Implements ICustomTypeDescriptor.GetProperties

        Dim properties = {"P1", "P3", "P7"}

        Dim descriptors = TypeDescriptor _
            .GetProperties(GetType(Foo)) _
            .Cast(Of PropertyDescriptor) _
            .Where(Function(p) properties.Any(Function(s) s = p.Name)) _
            .ToArray()

        Return New PropertyDescriptorCollection(descriptors)

    End Function

    Private Function GetAttributes() As AttributeCollection Implements ICustomTypeDescriptor.GetAttributes
        Return New AttributeCollection(Nothing)
    End Function

    Private Function GetClassName() As String Implements ICustomTypeDescriptor.GetClassName
        Return Nothing
    End Function

    Private Function GetComponentName() As String Implements ICustomTypeDescriptor.GetComponentName
        Return Nothing
    End Function

    Private Function GetConverter() As TypeConverter Implements ICustomTypeDescriptor.GetConverter
        Return Nothing
    End Function

    Private Function GetDefaultEvent() As EventDescriptor Implements ICustomTypeDescriptor.GetDefaultEvent
        Return Nothing
    End Function

    Private Function GetDefaultProperty() As PropertyDescriptor Implements ICustomTypeDescriptor.GetDefaultProperty
        Return Nothing
    End Function

    Private Function GetEditor(editorBaseType As Type) As Object Implements ICustomTypeDescriptor.GetEditor
        Return Nothing
    End Function

    Private Function GetEvents() As EventDescriptorCollection Implements ICustomTypeDescriptor.GetEvents
        Return New EventDescriptorCollection(Nothing)
    End Function

    Private Function GetEvents(attributes() As Attribute) As EventDescriptorCollection Implements ICustomTypeDescriptor.GetEvents
        Return New EventDescriptorCollection(Nothing)
    End Function

    Private Function GetProperties() As PropertyDescriptorCollection Implements ICustomTypeDescriptor.GetProperties
        Return DirectCast(Me, ICustomTypeDescriptor).GetProperties(Nothing)
    End Function

    Private Function GetPropertyOwner(pd As PropertyDescriptor) As Object Implements ICustomTypeDescriptor.GetPropertyOwner
        Return Me
    End Function

End Class

DataGridView:

C#:

var list = new List<Foo>();

list.Add(new Foo());
list.Add(new Foo());
list.Add(new Foo());

this.dataGridView1.DataSource = list;

VB.Net:

Dim list As New List(Of Foo)

list.Add(New Foo())
list.Add(New Foo())
list.Add(New Foo())

Me.DataGridView1.DataSource = list

DataGridView

PropertyGrid:

C#:

this.propertyGrid1.SelectedObject = new Foo();

VB.Net:

Me.PropertyGrid1.SelectedObject = New Foo()

PropertyGrid

3
On

As mentioned in another answer, ICustomTypeDescriptor gives you the full control over the metadata for the object, but it requires a lot of boilerplate code. For list binding controls (like DataGridView, ListView etc.) a ITypedList interface could be implemented by the list source class, which is simpler, but still requires some coding. In both cases, if you can't create some base classes and inherit all your classes from them, it would be much easier to just mark your "non browsable" members.
Anyway, there is another way, which is a little bit hackish, but does (almost) exactly what you asked for. The behavior is provided by a custom class which I called NoBrowsableAttribute. The usage is simple:

// To turn it on
NoBrowsableAttribute.Enabled = true;
// To turn it off
NoBrowsableAttribute.Enabled = false;

Just note that when turned on, it will change the BrowsableAttribute default behavior for ALL your classes inside the application. Here it is with a sample test:

using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

namespace Samples
{
    public sealed class NoBrowsableAttribute : TypeDescriptionProvider
    {
        public static bool Enabled
        {
            get { return instance.enabled; }
            set { instance.enabled = value; }
        }
        private static readonly NoBrowsableAttribute instance = new NoBrowsableAttribute();
        private bool enabled;
        private NoBrowsableAttribute() : base(TypeDescriptor.GetProvider(typeof(BrowsableAttribute)))
        {
            TypeDescriptor.AddProvider(this, typeof(BrowsableAttribute));
        }
        public override Type GetReflectionType(Type objectType, object instance)
        {
            if (enabled && objectType == typeof(BrowsableAttribute)) return typeof(NoBrowsableAttribute);
            return base.GetReflectionType(objectType, instance);
        }
        public static readonly BrowsableAttribute Default = BrowsableAttribute.No;
    }

    static class Test
    {
        class Person
        {
            public int Id { get; set; }
            [Browsable(true)]
            public string Name { get; set; }
            [Browsable(true)]
            public string Description { get; set; }
            public int Age { get; set; }
        }
        [STAThread]
        static void Main()
        {
            NoBrowsableAttribute.Enabled = true;

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var data = Enumerable.Range(1, 10).Select(i => new Person { Id = i, Name = "Name" + i, Description = "Description" + i }).ToList();
            var form = new Form { StartPosition = FormStartPosition.CenterScreen, ClientSize = new Size(500, 300) };
            var split = new SplitContainer { Dock = DockStyle.Fill, Parent = form, FixedPanel = FixedPanel.Panel2, SplitterDistance = 300 };
            var dg = new DataGridView { Dock = DockStyle.Fill, Parent = split.Panel1 };
            var pg = new PropertyGrid { Dock = DockStyle.Fill, Parent = split.Panel2, ToolbarVisible = false, PropertySort = PropertySort.NoSort };
            dg.BindingContextChanged += (sender, e) => {
                var bm = dg.BindingContext[data];
                pg.SelectedObject = bm.Current;
                bm.CurrentChanged += (_sender, _e) => pg.SelectedObject = bm.Current;
            };
            dg.DataSource = data;
            Application.Run(form);
        }
    }
}