Динамическая блокировка свойст в PropertyGrid
От: Аноним  
Дата: 15.06.09 11:37
Оценка:
Здраствуйте!!
Я использую 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
Дело в том что данная реализация работает если ее применять к простым свойствам, но при попытки применить ее к составному свойству блокируется только заголовок свойства. А мне нужно заблокировать все свойство включая подсвойства.
Подскажите пожалуйста что мне нужно зделать?
Re: Динамическая блокировка свойст в PropertyGrid
От: _FRED_ Черногория
Дата: 16.06.09 05:28
Оценка:
Во-вторых, Здравствуйте, Аноним, Вы писали:

А>Я использую 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. Тогда отпадёт необходимость в передаче всей коллекции свойств и самого объекта в каждое свойство.

    Как примерно это делается, можно посмотреть здесь
    Автор: _FRED_
    Дата: 28.03.07
    — нужно не забыть добавить [RefreshProperties(RefreshProperties.All)] ко всем свойствам, изменение значения которых может повлиять на атрибуты других свойств.
  • Help will always be given at Hogwarts to those who ask for it.
    Re[2]: Динамическая блокировка свойст в PropertyGrid
    От: Ant-Xp Россия  
    Дата: 16.06.09 20:28
    Оценка:
    Здравствуйте, _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 имеет смысл кешировать, вычислив их только один раз. Возможно, уже в конструкторе. Для этого понадобится только один проход по списку атрибутов.
    Можно пожалуйста по подробние?
    Re: Динамическая блокировка свойст в PropertyGrid
    От: baranovda Российская Империя  
    Дата: 16.06.09 20:42
    Оценка:
    Здравствуйте, Аноним, Вы писали:

    А>Пробывал использовать PropertyDescriptor, переписал его так:


    Направление верное. Но проще всего, думаю, вообще скрыть объект за специальным презентером, который предназначен только для PropertyGrid. Он, с одной стороны, реализует ICustomTypeDescriptor и аггрегирует объект, с другой стороны, отслеживает изменения значений свойств аггрегируемого/делегируемого объекта и подпихивает гриду соответствующим образом заполненную PropertyDescriptorCollection, в которую не включаются "невидимые" свойства.
    Re[3]: Динамическая блокировка свойст в PropertyGrid
    От: _FRED_ Черногория
    Дата: 17.06.09 06:02
    Оценка:
    Здравствуйте, Ant-Xp, Вы писали:

    AX>Но проблема после этого осталась. Делло в том что я могу заблокировать одиночное свойство, но если это свойство является классом помеченным атрибутом [TypeConverter(typeof(ExpandableObjectConverter))] то я не могу заблокировать все внутренние свойства данного класса.


    Правильно. Ты не можешь перезаписать объект, но можешь менять его свойства. Если требуется изменить эту процедуру, то нужно вмешаться в процесс получения свойств объекта, ссылка на который хранится в свойстве. Для этого можно переопределить PropertyDescriptor.GetChildProperties и вернуть оттуда такие PropertyDescriptorы, которые будут возвращать IsReadOnly = false.


    AX>И еще по поводу

    _FR>>Далее. DisplayName, Description, Category имеет смысл кешировать, вычислив их только один раз. Возможно, уже в конструкторе. Для этого понадобится только один проход по списку атрибутов.
    AX>Можно пожалуйста по подробние?

    Посмотри код этих свойств:
    string tableName = "";
    string categoruName = "";
    Find(ref tableName, ref categoruName, TypePropertyGridAttribute .Category);
    Validate(ref tableName, ref categoruName, TypePropertyGridAttribute .Category);
    return Localized(tableName, categoruName);

    Они, во-первых, все похожи. Во-вторых, каждый раз бегут по одной и той же коллекции. Я бы завёл в классе поля displayName, description, category и в конструкторе, за один раз, вытащил бы из значения и сохранил.

    Но тебе и этого не нужно, судя по всему. Атрибуты DisplayNameAttribute, DescriptionAttribute и CategoryAttribute уже поддердивают локализацию: Re: Локализация значений аттрибутов
    Автор: _FRED_
    Дата: 23.04.08
    Help will always be given at Hogwarts to those who ask for it.
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.