Здраствуйте!!
Я использую PropertyGrid для отображения свойств объекта, но у меня возникла проблема. Как изменить состояние составного свойства с ReadOnly(false) на ReadOnly(true) при изменении значения другого свойства.
Пробывал использовать PropertyDescriptor, переписал его так:
using System;
using System.ComponentModel;
using System.Resources;
using Kernel.Enums;
namespace Kernel.PropertySettings.PropertyBase
{
/// <summary>
/// GlobalizedPropertyDescriptor enhances the base class bay obtaining the display name for a property
/// from the resource.
/// </summary>public class GlobalizedPropertyDescriptor : PropertyDescriptor
{
private readonly PropertyDescriptor _basePropertyDescriptor;
private readonly PropertyDescriptorCollection _basePropertyDescriptorCollection;
private readonly object _baseObject;
#region -Private Section-
/// <summary>
/// First lookup the property if there are GlobalizedPropertyAttribute
/// instances are available.
/// </summary>
/// <param name="tableName">Name of the Table</param>
/// <param name="tempName">Name of the AttributeName</param>
/// <param name="typeAttribute">Type of the Attribute</param>private void Find(ref string tableName, ref string tempName, TypePropertyGridAttribute typeAttribute)
{
foreach (Attribute oAttrib in _basePropertyDescriptor.Attributes)
{
if (oAttrib.GetType().Equals(typeof(GlobalizedPropertyAttribute)))
{
switch (typeAttribute)
{
case TypePropertyGridAttribute .DisplayName:
tempName = ((GlobalizedPropertyAttribute)oAttrib).Name;
break;
case TypePropertyGridAttribute .Description:
tempName = ((GlobalizedPropertyAttribute)oAttrib).Description;
break;
case TypePropertyGridAttribute .Category:
tempName = ((GlobalizedPropertyAttribute)oAttrib).Category;
break;
}
tableName = ((GlobalizedPropertyAttribute)oAttrib).Table;
}
}
}
private void Validate(ref string tableName, ref string tempName, TypePropertyGridAttribute typeAttribute)
{
// If yes, try to get resource table name and display name id from that attribute.
// If no resource table specified by attribute, then build it itself by using namespace and class name.if (tableName.Length == 0)
tableName = _basePropertyDescriptor.ComponentType.Namespace + "." +
_basePropertyDescriptor.ComponentType.Name;
// If no display name id is specified by attribute, then construct it by using default display name (usually the property name) if (tempName.Length == 0)
switch (typeAttribute)
{
case TypePropertyGridAttribute .Description:
tempName = _basePropertyDescriptor.DisplayName + "Description";
break;
case TypePropertyGridAttribute .DisplayName:
tempName = _basePropertyDescriptor.DisplayName;
break;
case TypePropertyGridAttribute .Category:
tempName = _basePropertyDescriptor.DisplayName + "Category";
break;
}
}
private string Localized(string tableName, string tempName)
{
// Now use table name and display name id to access the resources. var rm = new ResourceManager(tableName, _basePropertyDescriptor.ComponentType.Assembly);
// Get the string from the resources.
// If this fails, then use default empty string indictating 'no description' string s = rm.GetString(tempName);
string localized = s ?? "";
return localized;
}
#endregion
#region -Public Section-
public GlobalizedPropertyDescriptor(PropertyDescriptor basePropertyDescriptor,
PropertyDescriptorCollection propertyDescriptorCollection,object component) : base(basePropertyDescriptor)
{
_basePropertyDescriptor = basePropertyDescriptor;
_basePropertyDescriptorCollection = propertyDescriptorCollection;
_baseObject = component;
}
public override bool CanResetValue(object component)
{
return _basePropertyDescriptor.CanResetValue(component);
}
public override Type ComponentType
{
get { return _basePropertyDescriptor.ComponentType; }
}
public override string DisplayName
{
get
{
// First lookup the property if GlobalizedPropertyAttribute instances are available.
// If yes, then try to get resource table name and display name id from that attribute.string tableName = "";
string displayName = "";
Find(ref tableName, ref displayName, TypePropertyGridAttribute .DisplayName);
Validate(ref tableName, ref displayName, TypePropertyGridAttribute .DisplayName);
return Localized(tableName, displayName);
}
}
public override string Description
{
get
{
string tableName = "";
string description = "";
Find(ref tableName,ref description,TypePropertyGridAttribute .Description);
Validate(ref tableName,ref description,TypePropertyGridAttribute .Description);
return Localized(tableName,description);
}
}
public override string Category
{
get
{
// First lookup the property if GlobalizedPropertyAttribute instances are available.
// If yes, then try to get resource table name and display name id from that attribute.string tableName = "";
string categoruName = "";
Find(ref tableName, ref categoruName, TypePropertyGridAttribute .Category);
Validate(ref tableName, ref categoruName, TypePropertyGridAttribute .Category);
return Localized(tableName, categoruName);
}
}
public override object GetValue(object component)
{
return _basePropertyDescriptor.GetValue(component);
}
public override bool IsReadOnly
{
get
{
foreach (Attribute attribute in _basePropertyDescriptor.Attributes)
{
if (attribute is DynamicReadOnlyAttribute)
{
var dpf =(DynamicReadOnlyAttribute)attribute;
PropertyDescriptor temp = _basePropertyDescriptorCollection[dpf.DependPropertyName];
if (dpf.DependShowOn.IndexOf(temp.GetValue(_baseObject).ToString()) > -1)
return true;
}
}
return _basePropertyDescriptor.IsReadOnly;
}
}
public override string Name
{
get { return _basePropertyDescriptor.Name; }
}
public override Type PropertyType
{
get { return _basePropertyDescriptor.PropertyType; }
}
public override void ResetValue(object component)
{
_basePropertyDescriptor.ResetValue(component);
}
public override bool ShouldSerializeValue(object component)
{
return _basePropertyDescriptor.ShouldSerializeValue(component);
}
public override void SetValue(object component, object value)
{
_basePropertyDescriptor.SetValue(component, value);
}
#endregion
}
}
в частности
public override bool IsReadOnly ...
отвечает за определение состояния ReadOnly
в зависимости от того установлен ли и скакими параметрами аттребут DynamicReadOnlyAttribute
Дело в том что данная реализация работает если ее применять к простым свойствам, но при попытки применить ее к составному свойству блокируется только заголовок свойства. А мне нужно заблокировать все свойство включая подсвойства.
Подскажите пожалуйста что мне нужно зделать?
Во-вторых, Здравствуйте, Аноним, Вы писали:
А>Я использую PropertyGrid для отображения свойств объекта, но у меня возникла проблема. Как изменить состояние составного свойства с ReadOnly(false) на ReadOnly(true) при изменении значения другого свойства. А>Пробывал использовать PropertyDescriptor, переписал его так:
Очень всё грусно :о(
Но, по-порядку:
А> foreach (Attribute oAttrib in _basePropertyDescriptor.Attributes)
А> {
А> if (oAttrib.GetType().Equals(typeof(GlobalizedPropertyAttribute)))
А> {
Что бы узнать, установлен ли некоторый атрибут на свойство, требуется поступать так:
var attribute = (GlobalizedPropertyAttribute)_basePropertyDescriptor.Attributes[typeof(GlobalizedPropertyAttribute)];
if(attribute != null) {
//…
}///if
Во-первых, это короче (например, у вас есть ошибка, так цикл будет продолжаться и после того, как аттрибут необходимого типа будет найден, а это не понятно для каких целей
нужно).
Во-вторых, индексатор по типу в AttributeCollection предоставляет (и другие стандартные компоненты этим пользуются, а вы — нет) более унифицированный способ определения того, что некоторый аттрибут применён к свойству.
Сначала, как и у вас, происходит поиск атрибута, тип которого равен аргументу индексатора. Как только найден атрибут необходимого типа поиск завершается и возвращается результат.
Если же индексатор не найдёт среди PropertyDescriptor.Attributes атрибута нужного типа, он проверит, нету ли среди атрибутов наследника указанного типа и вернёт первого встретившегося.
Если и наследник не будет найден, индексатор вернёт "значение по-умолчанию для атрибута". (Что это такое? Сначала, с помощью reflection, ищется открытое статическое поле (не свойство) Default) и если оно найдено, возвращается его значение. Если поле не найдено, ищется "конструктор по-умолчанию" (открытый, без параметров) в типе атрибута. Если он найден, создаётся экземпляр, у которого спрашивается Attribute.IsDefaultAttribute() и если значение true (а если в классе атрибута этот метод не переопределить, то будет false) то возвращается этот экземпляр. Или возвращается null. Вот так всё не просто, но код, работающий с атрибутами, должен об этом знать.
Далее. DisplayName, Description, Category имеет смысл кешировать, вычислив их только один раз. Возможно, уже в конструкторе. Для этого понадобится только один проход по списку атрибутов.
Теперь о главном. Если у вас атрибуты одного свойства зависят от другого, то не нужно эту логику зависимости вставлять в свойство. Её нужно держать в том месте, которое управляет свойствами. каким образом вы подставляете свои PropertyDescriptorы? Через TypeConverter? ICustomTypeDescriptor? TypeDescriptionProvider? В методе GetProperties нужно проанализировать объект и решить, какие свойства ему нужны и вернуть их, а не внутри самого свойства решать, каким оно является. То есть, в вашем случае нужно передавать в вам PropertyDescriptor булевый флаг readOnly, значение которого будет возвращено из переопределения IsReadOnly. Тогда отпадёт необходимость в передаче всей коллекции свойств и самого объекта в каждое свойство.
— нужно не забыть добавить [RefreshProperties(RefreshProperties.All)] ко всем свойствам, изменение значения которых может повлиять на атрибуты других свойств.
Help will always be given at Hogwarts to those who ask for it.
Re[2]: Динамическая блокировка свойст в PropertyGrid
Здравствуйте, _FRED_, Вы писали:
_FR>Во-вторых, Здравствуйте, Аноним, Вы писали:
А>>Я использую PropertyGrid для отображения свойств объекта, но у меня возникла проблема. Как изменить состояние составного свойства с ReadOnly(false) на ReadOnly(true) при изменении значения другого свойства. А>>Пробывал использовать PropertyDescriptor, переписал его так:
..... _FR>Далее. DisplayName, Description, Category имеет смысл кешировать, вычислив их только один раз. Возможно, уже в конструкторе. Для этого понадобится только один проход по списку атрибутов.
.....
Спасибо за помощь в оптимизации!!!
Но проблема после этого осталась. Делло в том что я могу заблокировать одиночное свойство, но если это свойство является классом помеченным атрибутом [TypeConverter(typeof(ExpandableObjectConverter))] то я не могу заблокировать все внутренние свойства данного класса.
Вот исходник класса которым я проверяю на существование аттрибута DynamicReadOnlyAttribute с помощью которго можно заблокировать одиночное свойство в зависимости от значения другого свойства:
using System;
using System.ComponentModel;
using Kernel.PropertySettings.Attributes;
namespace Kernel.PropertySettings.Base
{
public class GlobalizedObject : ICustomTypeDescriptor
{
...
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetFilteredProperties(attributes);
}
public PropertyDescriptorCollection GetProperties()
{
return GetFilteredProperties(new Attribute[0]);
}
protected PropertyDescriptorCollection GetFilteredProperties(Attribute[] attributes)
{
PropertyDescriptorCollection propertyDescriptorCollection = TypeDescriptor.GetProperties(this,attributes, true);
var finalProps =new PropertyDescriptorCollection(new PropertyDescriptor[0]);
foreach (PropertyDescriptor propertyDescriptor in propertyDescriptorCollection)
{
bool include = false;
bool dynamic = false;
bool readOnly = false;
var dynamicPropertyFilter = (DynamicPropertyFilterAttribute)
propertyDescriptor.Attributes[typeof (DynamicPropertyFilterAttribute)];
if (dynamicPropertyFilter != null)
{
dynamic = true;
PropertyDescriptor temp =
propertyDescriptorCollection[dynamicPropertyFilter.PropertyName];
if (dynamicPropertyFilter.ShowOn.IndexOf(temp.GetValue(this).ToString()) > -1)
include = true;
}
var dynamicReadOnlyAttribute = (DynamicReadOnlyAttribute)
propertyDescriptor.Attributes[typeof (DynamicReadOnlyAttribute)];
if (dynamicReadOnlyAttribute != null)
{
PropertyDescriptor temp =
propertyDescriptorCollection[dynamicReadOnlyAttribute.DependPropertyName];
if (dynamicReadOnlyAttribute.DependShowOn.IndexOf(temp.GetValue(this).ToString()) > -1)
readOnly = true;
}
if (!dynamic || include)
finalProps.Add(new GlobalizedPropertyDescriptor(propertyDescriptor,readOnly));
}
return finalProps;
}
}
}
Как мне быть?
И еще по поводу _FR>Далее. DisplayName, Description, Category имеет смысл кешировать, вычислив их только один раз. Возможно, уже в конструкторе. Для этого понадобится только один проход по списку атрибутов.
Можно пожалуйста по подробние?
Здравствуйте, Аноним, Вы писали:
А>Пробывал использовать PropertyDescriptor, переписал его так:
Направление верное. Но проще всего, думаю, вообще скрыть объект за специальным презентером, который предназначен только для PropertyGrid. Он, с одной стороны, реализует ICustomTypeDescriptor и аггрегирует объект, с другой стороны, отслеживает изменения значений свойств аггрегируемого/делегируемого объекта и подпихивает гриду соответствующим образом заполненную PropertyDescriptorCollection, в которую не включаются "невидимые" свойства.
Re[3]: Динамическая блокировка свойст в PropertyGrid
Здравствуйте, Ant-Xp, Вы писали:
AX>Но проблема после этого осталась. Делло в том что я могу заблокировать одиночное свойство, но если это свойство является классом помеченным атрибутом [TypeConverter(typeof(ExpandableObjectConverter))] то я не могу заблокировать все внутренние свойства данного класса.
Правильно. Ты не можешь перезаписать объект, но можешь менять его свойства. Если требуется изменить эту процедуру, то нужно вмешаться в процесс получения свойств объекта, ссылка на который хранится в свойстве. Для этого можно переопределить PropertyDescriptor.GetChildProperties и вернуть оттуда такие PropertyDescriptorы, которые будут возвращать IsReadOnly = false.
AX>И еще по поводу _FR>>Далее. DisplayName, Description, Category имеет смысл кешировать, вычислив их только один раз. Возможно, уже в конструкторе. Для этого понадобится только один проход по списку атрибутов. AX>Можно пожалуйста по подробние?
Они, во-первых, все похожи. Во-вторых, каждый раз бегут по одной и той же коллекции. Я бы завёл в классе поля displayName, description, category и в конструкторе, за один раз, вытащил бы из значения и сохранил.
Но тебе и этого не нужно, судя по всему. Атрибуты DisplayNameAttribute, DescriptionAttribute и CategoryAttribute уже поддердивают локализацию: Re: Локализация значений аттрибутов