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.
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.
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; }
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With