Note: I'm using MVVM Light Toolkit and MahApps.Metro.
I've checked the answers but it doesn't seem like any of them relate to my question.
I have a Grid whose columns and header should be dynamically created. The number and value of columns is unknown to view, and the number of rows is unknown to view.
Columns, rows and data in the rows represent a Database Table. All data is present in the ViewModel.
I have an ObservableCollection<ServerRow> ServerRows; in my ViewModel.
Server Row object is a Model that looks like this:
public class ServerRow : ObservableObject
{
private ObservableCollection<ServerColumn> _columns;
public ObservableCollection<ServerColumn> Columns
{
get { return _columns; }
set { Set(() => Columns, ref _columns, value); }
}
}
This is a ServerColumn class :
public class ServerColumn : ObservableObject
{
private string _name;
private string _type;
private string _value;
public string Name
{
get { return _name; }
set { Set(() => Name, ref _name, value); }
}
public string Type
{
get { return _type; }
set { Set(() => Type, ref _type, value); }
}
public string Value
{
get { return _value; }
set { Set(() => Value, ref _value, value); }
}
}
The Idea was to Bind DataGrid to ObservableCollection<ServerRow> ServerRows;, and then generate the Columns depending on the ServerRow object which has ServerColumns which in turn have Name (should be a header of the column), Type as the datatype of column data, and Value as the value which should be represented in every row/column.
My XAML is pretty simple (because it's not complete, and of course- not working)
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding ServerRows}"/>
How do I write the XAML properly to achieve what I'm trying to do?
This is the result, which makes sense because Grid is trying to show a collection of objects inside a single Column and calling its ToString() method.
I've had this problem before too.
If you look at what is done here:
https://github.com/taori/WMPR/blob/0a81bc6a6a4c6fc36edc4cbc99f0cfa8a2b8871c/src/WMPR/WMPR.Client/ViewModels/Sections/ReportEvaluationViewModel.cs#L503
You provide the iteratable collection as ObservableCollection<object> when the underlying structure is actually of type DynamicGridCell, which uses a DynamicGridCellDescriptor which can be found at
DynamicGridCell:
public class DynamicGridCell : DynamicObject, ICustomTypeDescriptor, IDictionary<string, object>
{
private readonly Dictionary<string, object> _values = new Dictionary<string, object>();
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return new AttributeCollection();
}
string ICustomTypeDescriptor.GetClassName()
{
return nameof(DynamicGridCell);
}
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 null;
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return null;
}
private PropertyDescriptor[] CreatePropertyDescriptors()
{
var result = new List<PropertyDescriptor>();
foreach (var pair in _values)
{
result.Add(new DynamicGridCellDescriptor(pair.Key));
}
return result.ToArray();
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
var result = new PropertyDescriptorCollection(CreatePropertyDescriptors());
return result;
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
var result = new PropertyDescriptorCollection(CreatePropertyDescriptors());
return result;
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
public IEnumerator GetEnumerator()
{
return _values.GetEnumerator();
}
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
{
return _values.GetEnumerator();
}
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
_values.Add(item.Key, item.Value);
}
void ICollection<KeyValuePair<string, object>>.Clear()
{
_values.Clear();
}
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
return _values.Contains(item);
}
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
}
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
if (_values.ContainsKey(item.Key))
{
_values.Remove(item.Key);
return true;
}
return false;
}
public int Count => _values.Count;
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;
public bool ContainsKey(string key)
{
return _values.ContainsKey(key);
}
public void Add(string key, object value)
{
_values.Add(key, value);
}
bool IDictionary<string, object>.Remove(string key)
{
return _values.Remove(key);
}
public bool TryGetValue(string key, out object value)
{
return _values.TryGetValue(key, out value);
}
public object this[string key]
{
get { return _values[key]; }
set
{
if (_values.ContainsKey(key))
{
_values[key] = value;
}
else
{
_values.Add(key, value);
}
}
}
public ICollection<string> Keys => _values.Keys;
public ICollection<object> Values => _values.Values;
}
DynamicGridCellDescriptor
public class DynamicGridCellDescriptor : PropertyDescriptor
{
public DynamicGridCellDescriptor(string name) : base(name, null)
{
}
public override bool CanResetValue(object component)
{
return true;
}
public override object GetValue(object component)
{
return ((DynamicGridCell) component)[Name];
}
public override void ResetValue(object component)
{
((DynamicGridCell) component)[Name] = null;
}
public override void SetValue(object component, object value)
{
((DynamicGridCell) component)[Name] = value;
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override Type ComponentType => typeof(DynamicGridCell);
public override bool IsReadOnly => false;
public override Type PropertyType => typeof(object);
}
Just make sure that the property you bind to is of type ObservableCollection<object> anyways - otherwise for me automatic grid column generation did not work.
You have some logical issues.
When you set the ItemsSource of a DataGrid the bound collection will be used to create rows and if you don't change it the property AutoGenerateColumns is set to true. In this case the DataGrid will generate a column for each property in the bound collection and this is exactly what is happening in your sample.
You bound an instance with a property 'Columns' and get a DataGrid column which is named 'Columns'. And you get as much rows as you have entries in this property displayed as '(Collection)' because ServerColumn inherits from ObservableObject.
You can set AutoGenerateColumns to false and have to create the columns by your own; normally in xaml => hard coded.
If you really want to have dynamically generate the columns you have to write your own logic to create and bind the columns. I've done that once and it's pain in the ass if you want to have it generic.
If you want a DataGrid with dynamic columns where the user can change values it's more tricky then a read only one.
One approach could be having a ObservableCollection<string> for the column names and another one ObservableCollection which stores your ViewModels for each row.
If both rows and columns really need to be dynamic, your best choice is to use two nested ItemControls, the outer one representing rows, the inner one columns:
<ItemsControl ItemsSource="{Binding Rows}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Columns}" ItemTemplateSelector="{StaticResource ColumnTemplateSelector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This allows you to display different type of columns, by defining a template selector that might look somewhat similar to the following:
public class ColumnTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var column = item as ServerColumn;
switch (column.Type)
{
case "Text":
return TextTemplate;
case "Number":
return NumberTemplate;
case "Image":
return ImageTemplate;
}
// Fallback
return TextTemplate;
}
public DataTemplate TextTemplate { get; set; }
public DataTemplate NumberTemplate { get; set; }
public DataTemplate ImageTemplate { get; set; }
}
...based on each column's type, a different template would be referenced (all of these template obviously need to be defined somewhere and referenced as StaticResource. (This even allows easy creation of changeable (not read-only) grids.)
Note that, instead of the outer ItemsControl, you can of course use ListView or any other control that is derived from ItemsControl. Using a ListView might be useful if you need automatic scrolling, for example.
Related
My View Model class:
class Student : INotifyPropertyChanged
{
private string name;
private bool isVisible;
public event PropertyChangedEventHandler PropertyChanged;
public string PersonName
{
get { return name; }
set
{
name = value;
OnPropertyChanged("PersonName");
}
}
public bool IsVisible
{
get { return isVisible; }
set
{
isVisible = value;
OnPropertyChanged("IsVisible");
}
}
}
My Students collection that store all my objects:
public ObservableCollection<Student> Students { get; set; }
XAML:
<ComboBox x:Name="cbStudents"
ItemsSource="{Binding Students}"
SelectionChanged="cbInterfaces_SelectionChanged"/>
So in some point i want to disappear several Students from my ComboBox so i just change IsVisible value to False.
Any idea how to do that using XAML ?
You can have your Students collection return only visible students.
//All students (visible and invisible)
ObservableCollection<Students> _AllStudents = GetAllStudentsFromDataSource();
//only visible students
ObservableCollection<Students> _VisibleStudents = new ObservableCollection<Students>();
foreach(var _s in _AllStudents.Where(x => x.IsVisible)){
_VisibleStudents.Add(_s);
}
//your property
public ObservableCollection<Student> Students { get{ return _VisibleStudents; } }
In the case of your check box toggling the visibility of students, your checkbox can be bound to a command like this:
<Checkbox IsChecked="{Binding IsCheckboxChecked}" Command={Binding ToggleStudents}" />
And your view model has an extra control for the checkbox toggle and the command:
bool _IsCheckboxChecked = false;
public bool IsCheckboxChecked {
get { return _IsCheckboxChecked;}
set {
if(_IsCheckboxChecked != value)
{
_IsCheckboxChecked = value;
}
}
}
public ICommand ToggleStudents
{
get;
internal set;
}
private void ToggleStudentsCommand()
{
ToggleStudents = new RelayCommand(ToggleStudentsExecute);
}
public void ToggleStudentsExecute()
{
_VisibleStudents.Clear();
if(_IsCheckboxChecked){
foreach(var _s in _AllStudents.Where(x => x.IsVisible)){
_VisibleStudents.Add(_s);
}
}
else
{
foreach(var _s in _AllStudents.Where(x => x.IsVisible == false)){
_VisibleStudents.Add(_s);
}
}
OnPropertyChanged("Students");
}
Your xaml doesn't need to change.
I have a WPF ComboBox bound to a list of a class which contains an enum.
This all works fine, my question is at the end of this post, first the code:
Here is the class:
public class FILTER_TEST
{
public FilterType Filter { get; private set; }
public string Description { get; private set; }
public static List<FILTER_TEST> CreateFilters()
{
var list = new List<FILTER_TEST>();
list.Add(new FILTER_TEST() { Filter = FilterType.CheckNone, Description = "Uncheck all" });
list.Add(new FILTER_TEST() { Filter = FilterType.CheckAll, Description = "Check all" });
list.Add(new FILTER_TEST() { Filter = FilterType.CheckCustom, Description = "Custom check" });
return list;
}
}
Here is the enum FilterType:
public enum FilterType
{
CheckNone,
CheckAll,
CheckCustom
}
In my view model I have the following:
public List<FILTER_TEST> FilterNames { get { return FILTER_TEST.CreateFilters(); } }
public FILTER_TEST SelectedFilter
{
get { return selectedFilter; }
set
{
if (value != selectedFilter)
{
selectedFilter = value;
OnPropertyChanged("SelectedFilter");
}
}
}
Also in the view model, I set the SelectedItem of the ComboBox as follows:
SelectedFilter = FilterNames.Where(x => x.Filter == FilterType.CheckNone).FirstOrDefault();
Here is the xaml putting it all together:
<ComboBox DisplayMemberPath="Description" ItemsSource="{Binding FilterNames}"
SelectedItem="{Binding SelectedFilter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="True"/>
My problem is that although the changing of the SelectionItem works, the actual value displayed in the ComboBox doesn’t change.
The initial SelectedItem is “Uncheck all” as, when the window has been loaded, none of the corresponding CheckBox controls (bound to another class which contains a Boolean property) have been checked. What I would like is that when a CheckBox has been checked, then the SelectedItem changes to “Custom check”.
This does indeed change the value of the SelectedItem:
SelectedFilter = FilterNames.Where(x => x.Filter == FilterType.CheckCustom).FirstOrDefault();
But the text shown in the ComboBox is still “Uncheck all”.
Does anyone have an idea as to what I am missing? I am forced to use the 4.0 framework, I don’t know if this is relevant.
I've seen the hint to overwrite Equals() of the type in use as this:
public override bool Equals(object o)
{
if (o is FILTER_TEST)
{
var other = o as FILTER_TEST;
return this.Description == other.Description && this.Filter == other.Filter;
}
else
return false;
}
Now that makes your sample work. Let me come back for a reference on the why.
I'm looking for a PropertyGrid for my WPF project that allows me to customize the ordering the properties / categories are listed. Right now I'm using Extended WPF Toolkits (Community Edition) PropertyGrid with CustomPropertyDescriptors. My researches showed, that it's not possible to have custom sorting with that PropertyGrid.
Is there a (preferably free) solution?
Ordering of properties in the Extended WPF Toolkit can be achieved by decorating the property with the PropertyOrderAttribute attribute.
If you don't want to pollute POCO's by decorating them with attributes at design time, or the order is dynamic in some way, then it's possible to add the attribute at run time by creating a type converter and overriding the GetProperties method. For example, if you wish to maintain the index order of a generic IList type:
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using System.ComponentModel;
public class MyExpandableIListConverter<T> : ExpandableObjectConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
if (value is IList<T>)
{
IList<T> list = value as IList<T>;
PropertyDescriptorCollection propDescriptions = new PropertyDescriptorCollection(null);
IEnumerator enumerator = list.GetEnumerator();
int counter = -1;
while (enumerator.MoveNext())
{
counter++;
propDescriptions.Add(new ListItemPropertyDescriptor<T>(list, counter));
}
return propDescriptions;
}
else
{
return base.GetProperties(context, value, attributes);
}
}
}
With the ListItemPropertyDescriptor being defined as follows:
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using System.ComponentModel;
public class ListItemPropertyDescriptor<T> : PropertyDescriptor
{
private readonly IList<T> owner;
private readonly int index;
public ListItemPropertyDescriptor(IList<T> owner, int index) : base("["+ index+"]", null)
{
this.owner = owner;
this.index = index;
}
public override AttributeCollection Attributes
{
get
{
var attributes = TypeDescriptor.GetAttributes(GetValue(null), false);
//If the Xceed expandable object attribute is not applied then apply it
if (!attributes.OfType<ExpandableObjectAttribute>().Any())
{
attributes = AddAttribute(new ExpandableObjectAttribute(), attributes);
}
//set the xceed order attribute
attributes = AddAttribute(new PropertyOrderAttribute(index), attributes);
return attributes;
}
}
private AttributeCollection AddAttribute(Attribute newAttribute, AttributeCollection oldAttributes)
{
Attribute[] newAttributes = new Attribute[oldAttributes.Count + 1];
oldAttributes.CopyTo(newAttributes, 1);
newAttributes[0] = newAttribute;
return new AttributeCollection(newAttributes);
}
public override bool CanResetValue(object component)
{
return false;
}
public override object GetValue(object component)
{
return Value;
}
private T Value
=> owner[index];
public override void ResetValue(object component)
{
throw new NotImplementedException();
}
public override void SetValue(object component, object value)
{
owner[index] = (T)value;
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override Type ComponentType
=> owner.GetType();
public override bool IsReadOnly
=> false;
public override Type PropertyType
=> Value?.GetType();
}
Portions of this code were adapted from the following SO answer
I'm trying to use the [TypeDescriptionProviderAttribute] in order to give my class a custom type descriptor. This works, but when I implement INotifyPropertyChanged WPF seems to ignore the custom type descriptor and go straight for the CLR property (if it exists). Here's a snippet, I'll paste the full example later on:
//[TypeDescriptionProvider(typeof(MyProvider))]
class MyModel : Object
//, INotifyPropertyChanged
//, ICustomTypeDescriptor
{
public string TheProperty { get { return "CLR - TheProperty"; } }
I bind a TextBlock to TheProperty. When I...
Leave everything commented
I see "CLR - TheProperty" as expected.
Use [TypeDescriptionProvider]
I see "MyPropertyDescriptor - TheProperty" as expected.
Use ICustomTypeDescriptor
I see "MyPropertyDescriptor - TheProperty" as expected.
Use ICustomTypeDescriptor and INotifyPropertyChanged
I see "MyPropertyDescriptor - TheProperty" as expected.
Use [TypeDescriptionProvider] and INotifyPropertyChanged
I see "CLR - TheProperty". Why is this? The weird thing is that custom properties without a CLR property are shown normally. My custom type descriptor also returns a "MyPropertyDescriptor - AnotherProperty" which works in all cases because there is no CLR AnotherProperty defined.
In summary, given this XAML
<StackPanel>
<TextBlock Text="{Binding TheProperty}" />
<TextBlock Text="{Binding AnotherProperty}" />
</StackPanel>
AnotherProperty always works as expected because the model does not have a CLR property named "AnotherProperty". TheProperty works as expected except when [TypeDescriptionProvider] and INotifyPropertyChanged are both used.
Here's the full code. It's a bit long but most of it is irrelevant, it's just required by System.ComponentModel
public partial class TestWindow : Window
{
public TestWindow()
{
InitializeComponent();
DataContext = new MyModel();
}
}
//[TypeDescriptionProvider(typeof(MyProvider))]
class MyModel : Object
//, INotifyPropertyChanged
//, ICustomTypeDescriptor
{
public string TheProperty { get { return "CLR - TheProperty"; } }
public event PropertyChangedEventHandler PropertyChanged;
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this);
}
public string GetClassName()
{
return TypeDescriptor.GetClassName(this);
}
public string GetComponentName()
{
return TypeDescriptor.GetComponentName(this);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return TypeDescriptor.GetProperties(this, attributes);
}
public PropertyDescriptorCollection GetProperties()
{
return MyTypeDescriptor.GetCustomProperties();
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
}
class MyProvider : TypeDescriptionProvider
{
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
return new MyTypeDescriptor();
}
}
class MyTypeDescriptor : CustomTypeDescriptor
{
public override PropertyDescriptorCollection GetProperties()
{
return GetCustomProperties();
}
public static PropertyDescriptorCollection GetCustomProperties()
{
return new PropertyDescriptorCollection(
new[] {
new MyPropertyDescriptor("TheProperty"),
new MyPropertyDescriptor("AnotherProperty")
});
}
}
class MyPropertyDescriptor : PropertyDescriptor
{
public MyPropertyDescriptor(string propName)
: base(propName, null)
{
}
public override bool CanResetValue(object component)
{
return false;
}
public override Type ComponentType
{
get { return typeof(MyModel); }
}
public override object GetValue(object component)
{
return "MyPropertyDescriptor - " + Name;
}
public override bool IsReadOnly
{
get { return true; }
}
public override Type PropertyType
{
get { return typeof(string); }
}
public override void ResetValue(object component)
{
throw new InvalidOperationException("cannot reset value");
}
public override void SetValue(object component, object value)
{
throw new InvalidOperationException("property is readonly");
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
}
Old question, but for people looking for an answer..
Problem is in System.Windows.PropertyPath.ResolvePropertyName(String, Object, Type, Object, Boolean) private method. I have found it in PresentationFramework.dll in .NET 4.0.
Extracted from .NET Reflector:
object propertyHelper = DependencyProperty.FromName(str, ownerType);
if ((propertyHelper == null) && (item is ICustomTypeDescriptor))
{
propertyHelper = TypeDescriptor.GetProperties(item)[str];
}
if ((propertyHelper == null) && ((item is INotifyPropertyChanged) || (item is DependencyObject)))
{
propertyHelper = this.GetPropertyHelper(ownerType, str);
}
if (propertyHelper == null)
{
propertyHelper = TypeDescriptor.GetProperties(item)[str];
}
if (propertyHelper == null)
{
propertyHelper = this.GetPropertyHelper(ownerType, str);
}
if ((propertyHelper == null) && throwOnError)
{
throw new InvalidOperationException(SR.Get("PropertyPathNoProperty", new object[] { ownerType.Name, str }));
}
return propertyHelper;
As you can see, retrieving property identifier (DependencyProperty / PropertyDescriptor / PropertyInfo) goes like this:
Try get DependencyProperty,
If item implements ICustomTypeDescriptor, use TypeDescriptor to get PropertyDescriptor,
If item implements INotifyPropertyChanged or is DependencyObject, use System.Reflection to get PropertyInfo,
Else use TypeDescriptor to get PropertyDescriptor,
Else use System.Reflection to get PropertyInfo,
Else throw exception or return null.
So System.Reflection/PropertyInfo gets precedence over TypeDescriptor/PropertyDescriptor if item implements INotifyPropertyChanged interface. I believe they choose this strategy for performance reasons because PropertyInfo is much more lighter than PropertyDescriptor.
Solution to your problem would be to implement ICustomTypeDescriptor (preferably explicitly) so that it transfers ICustomTypeDescriptor method calls to appropriate TypeDescriptor method calls but not with object parameter, but Type parameter (this.GetType()). This way your TypeDescriptionProvider will be used.
I have a canvas that I can create and drop elements onto. I can move them and size them with a thumb or through binding to a property grid.
Now, I need to implement a feature to "lock" position and size. I started with try and set min and max height\width to the actual height\width. The initial effort wasn't so great. And I still don't have a strategy for fixing the location.
So before I go off and wade through 25 approaches that don't work, perhaps someone has a suggestion. I'd much appreciate it.
Thanks,
jeff
You could just disable the Thumb and the PropertyGrid...
Of course, for the PropertyGrid it is not ideal... it would be better to keep it enabled, but read-only, unfortunately the PropertyGrid doesn't have a ReadOnly property... A possible solution would be to wrap your object in a custom type descriptor which would present the properties as read-only. Here are two classes to achieve that (sorry for the long code...) :
ReadOnlyTypeDescriptor :
public class ReadOnlyTypeDescriptor : ICustomTypeDescriptor
{
public ReadOnlyTypeDescriptor(object target)
{
TypeDescriptionProvider provider = TypeDescriptor.GetProvider(target);
_originalDescriptor = provider.GetTypeDescriptor(target);
}
public ReadOnlyTypeDescriptor(ICustomTypeDescriptor descriptor)
{
_originalDescriptor = descriptor;
}
private ICustomTypeDescriptor _originalDescriptor;
private PropertyDescriptor MakeReadOnly(PropertyDescriptor propertyDescriptor)
{
return new ReadOnlyPropertyDescriptor(propertyDescriptor);
}
private PropertyDescriptorCollection MakeReadOnly(PropertyDescriptorCollection propertyDescriptors)
{
var descriptors = propertyDescriptors
.Cast<PropertyDescriptor>()
.Select(pd => new ReadOnlyPropertyDescriptor(pd))
.ToArray();
return new PropertyDescriptorCollection(descriptors, true);
}
#region ICustomTypeDescriptor Members
public AttributeCollection GetAttributes()
{
return _originalDescriptor.GetAttributes();
}
public string GetClassName()
{
return _originalDescriptor.GetClassName();
}
public string GetComponentName()
{
return _originalDescriptor.GetComponentName();
}
public TypeConverter GetConverter()
{
return _originalDescriptor.GetConverter();
}
public EventDescriptor GetDefaultEvent()
{
return _originalDescriptor.GetDefaultEvent();
}
public PropertyDescriptor GetDefaultProperty()
{
return MakeReadOnly(_originalDescriptor.GetDefaultProperty());
}
public object GetEditor(Type editorBaseType)
{
return _originalDescriptor.GetEditor(editorBaseType);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return _originalDescriptor.GetEvents(attributes);
}
public EventDescriptorCollection GetEvents()
{
return _originalDescriptor.GetEvents();
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return MakeReadOnly(_originalDescriptor.GetProperties(attributes));
}
public PropertyDescriptorCollection GetProperties()
{
return MakeReadOnly(_originalDescriptor.GetProperties());
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return _originalDescriptor.GetPropertyOwner(pd);
}
#endregion
}
ReadOnlyPropertyDescriptor :
public class ReadOnlyPropertyDescriptor : PropertyDescriptor
{
public ReadOnlyPropertyDescriptor(PropertyDescriptor descriptor)
: base(
descriptor.Name,
descriptor.Attributes.Cast<Attribute>().ToArray())
{
_originalDescriptor = descriptor;
}
private PropertyDescriptor _originalDescriptor;
public override bool CanResetValue(object component)
{
return false;
}
public override Type ComponentType
{
get { return _originalDescriptor.ComponentType; }
}
public override object GetValue(object component)
{
return _originalDescriptor.GetValue(component);
}
public override bool IsReadOnly
{
get { return true; }
}
public override Type PropertyType
{
get { return _originalDescriptor.PropertyType; }
}
public override void ResetValue(object component)
{
throw new NotSupportedException();
}
public override void SetValue(object component, object value)
{
throw new NotSupportedException();
}
public override bool ShouldSerializeValue(object component)
{
return _originalDescriptor.ShouldSerializeValue(component);
}
}
To show the object target as read-only in the PropertyGrid, just do that :
propertyGrid.SelectedObject = new ReadOnlyTypeDescriptor(target);
It will show the same properties, but they won't be editable...
OK, this solution is probably a little overkill for your needs... but I think it can be handy in some cases ;)