Why Browsable attribute makes property not bindabl

2019-02-10 08:09发布

问题:

I'm trying to use System.Windows.Forms.PropertyGrid.

To make a property not visible in this grid one should use BrowsableAttribute set to false. But adding this attribute makes the property not bindable.

Example: Create a new Windows Forms project, and drop a TextBox and PropertyGrid onto Form1. Using the code below, the width of the TextBox does not get bound to Data.Width:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        Data data = new Data();
        data.Text = "qwe";
        data.Width = 500;

        BindingSource bindingSource = new BindingSource();
        bindingSource.Add(data);

        textBox1.DataBindings.Add("Text", bindingSource, "Text", true,
            DataSourceUpdateMode.OnPropertyChanged);
        textBox1.DataBindings.Add("Width", bindingSource, "Width", true,
            DataSourceUpdateMode.OnPropertyChanged);

        propertyGrid1.SelectedObject = data;
    }
}

The code for the data class is:

public class Data : IBindableComponent
{
    public event EventHandler TextChanged;
    private string _Text;
    [Browsable(true)]
    public string Text
    {
        get
        {
            return _Text;
        }
        set
        {
            _Text = value;
            if (TextChanged != null)
                TextChanged(this, EventArgs.Empty);
        }
    }

    public event EventHandler WidthChanged;
    private int _Width;
    [Browsable(false)]
    public int Width
    {
        get
        {
            return _Width;
        }
        set
        {
            _Width = value;
            if (WidthChanged != null)
                WidthChanged(this, EventArgs.Empty);
        }
    }

    #region IBindableComponent Members

    private BindingContext _BindingContext;
    public BindingContext BindingContext
    {
        get
        {
            if (_BindingContext == null)
                _BindingContext = new BindingContext();

            return _BindingContext;
        }
        set
        {
            _BindingContext = value;
        }
    }

    private ControlBindingsCollection _DataBindings;
    public ControlBindingsCollection DataBindings
    {
        get 
        {
            if (_DataBindings == null)
                _DataBindings = new ControlBindingsCollection(this);

            return _DataBindings;    
        }
    }

    #endregion

    #region IComponent Members

    public event EventHandler Disposed;

    public System.ComponentModel.ISite Site
    {
        get
        {
            return null;
        }
        set
        {

        }
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    {
        throw new NotImplementedException();
    }

    #endregion
}

If you switch the Browsable attribute to true on every property in Data it works. Now it seems like BindingSource searches datasource by Browsable Attribute.

回答1:

Updated answer based on update example:

I've done some digging in Reflector and discovered that the "problem" is actually in ListBindingHelper, which is used by CurrencyManager, which is in turn used by the BindingSource (these are all in the System.Windows.Forms namespace).

To discover the bindable properties, the CurrencyManager invokes ListBindingSource.GetListItemProperties. Under the hood, this calls out to GetListItemPropertiesByInstance (when you pass in a single object). That method has the following line of code at the end:

return TypeDescriptor.GetProperties(target, BrowsableAttributeList);

And the implementation of BrowsableAttributeList is:

private static Attribute[] BrowsableAttributeList
{
    get
    {
        if (browsableAttribute == null)
        {
            browsableAttribute = new Attribute[] { new BrowsableAttribute(true) };
        }
        return browsableAttribute;
    }
}

You can see that it is, in fact, filtering properties by BrowsableAttribute(true). It should probably be using BindableAttribute instead, but my guess is that the designers realized that everybody was already depending on BrowsableAttribute and decided to use that one instead.

So unfortunately you won't be able to get around this issue if you use BrowsableAttribute. Your only options are to either do what Marc suggests and use a custom TypeConverter, or filter the property grid itself using one of the solutions in this question: Programatically Hide Field in PropertyGrid.



回答2:

BrowsableAttribute is used by a lot of the component-model way as a mechanism to avoid it being included. Perhaps the best option is to not add [Browsable(false)].

There are several other ways of filtering PropertyGrid, including (in increasing complexity) TypeConverter, ICustomTypeDescriptor, TypeDescriptionProvider - but the simplest is probably to tell PropertyGrid the attributes that describe things you do want (.BrowsableAttributes), and mark the other properties.

Note that another option is to create a custom tab - but that is hit'n'miss in my experience.

Here's an example using TypeConverter, which is used by PropertyGrid, but not by most other bindings; it works by having a custom type-converter which excludes a specific property by name, but you could also use something like Attribute.IsDefined to do the masking:

using System.Windows.Forms;
using System;
using System.Linq;
using System.ComponentModel;
static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Data data = new Data { Name = "the name", Value = "the value" };
        using (Form form = new Form
        {
            Controls =
            {
                new PropertyGrid {
                    Dock = DockStyle.Fill,
                    SelectedObject = data
                },
                new TextBox {
                    Dock = DockStyle.Bottom,
                    DataBindings = { {"Text", data, "Value"}, }
                },
                new TextBox {
                    Dock = DockStyle.Bottom,
                    DataBindings = { {"Text", data, "Name"}, }
                }
            }
        })
        {
            Application.Run(form);
        }        
    }
}
[TypeConverter(typeof(DataConverter))]
class Data
{
    class DataConverter : ExpandableObjectConverter
    {
        public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
        {
            var props = base.GetProperties(context, value, attributes);
            return new PropertyDescriptorCollection(
                (from PropertyDescriptor prop in props
                 where prop.Name != "Value"
                 select prop).ToArray(), true);
        }
    }
    public string Value { get; set; }
    public string Name { get; set; }
}