Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > .NET FAQ > Вся правда о PropertyGrid


Автор: andrey 30.03.06, 19:21
По заявкам посетителей - описание работы с PropertyGrid в примерах

Примечание: все описываемые классы лежат в нэймспейсах System.Windows.Forms, System.ComponentModel и System.Drawing.Design

Что такое PropertyGrid и что с ним можно сделать
PropertyGrid это одн из стандартных компонентов wondows forms который есть как в первой, так и во второй версии .NET Framework. Этот компонент позволяет показывать и редактировать свойства практически любых объектов, не требуя написания дополнительного кода.
В простейшем случае вы пишете свои классы с использовнием свойств (properties), помещаете на форму PropertyGrid и присваеваете его свойству SelectedObject ссылку на обект, после чего с помощью PropertyGrid можно увидеть и изменить любые свойтсва объекта.
Свойства объекта, если они имеют простой тип (такой как int, string, enum, bool, Size, Color и некоторых других) редактируются средствами PropertyGrid. Если же необходимо редактировать более сложные свойства, к примеру массивы или воженные объекты существует два метода - применение TypeConverter и использование UITypeEditor. Кстати оба эти метода можно сочетать.
Применение TypeConverter позволяет реализовать преобразование типа данных в string и обратно (как это например реализовано для редактирования System.Drawing.Size), а так же управлять набором совйств объекта (это очень мощное по своей функциональности средство).
Применение UITypeEditor позволяет добавить редактор к свойтсву, либо в виде выпадающего окошка (обычно на котором отображается список вариантов), либо в виде кнопки с многоточием, нажатие на которую обрабатывается любым способом.

Атрибуты управляющие PropertyGrid
Существует несколько атрибутов, управляющих видом свойств в PropertyGrid:
[Browsable(bool)] - показывать свойтсво или нет
[ReadOnly(bool)] - возможность редактирования свойства
[Category(string)] - группа свойства
[Description(string)] - описание свойства

использование UITypeEditor
Для того чтобы свойство вашего типа можно бало легко редактировать необходимо написть свой класс редактора, унаследованный от UITypeEditor, в котором реализовать методы:
UITypeEditorStyle GetEditStyle(ITypeDescriptorContext)
object EditValue(ITypeDescriptorContext,IServiceProvider,object)
Первый метод задаёт тип редактора - DropDown или Modal и в реализации абсолютно тривиален, второй обеспечивает собственно редактирование. При этом есть несколько ньюансов.
Прежде всего при редактировании значения следует получить IWindowsFormsEditorService который будет управлять процессом редактирования и обязательно запомнить его во внешней переменной - авось пригодится:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    private IWindowsFormsEditorService edSvc=null;
    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
        edSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
        if(edSvc==null)return value;
    .....

В случае если применяется DorpDown то после этого применяя метод DropDownControl мы показываем выпадающее окошко с элементом управления на нём. Это может быть как ListBox, тогда наш редактор будет напоминать ComboBox, либо что-то другое, к примеру Panel с кучей элементов управления на нём.
Не забывайте задавать событие по которому будет происходить закрытие вашео редактора:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    .... в методе EditValue
        ListBox lb=new ListBox(); //То что будет в выпадающем окне
        lb.SelectedValueChanged+=new EventHandler(lb_SelectedValueChanged); //Событие для закрытия окна
        lb.BorderStyle=BorderStyle.None; //Для красоты
    .... //Здесь заполняем ListBox
        edSvc.DropDownControl(lb);//Выпадаем окно
        //Этот метод блокирует поток пока не закрыто окно
    ....
        return myNewValue;//Возвращаем новое значение свойства

Кроме того нам надо закрывать выпадающее окно при выборе элемента в списке:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    private void lb_SelectedValueChanged(object sender, EventArgs e) {
        if(edSvc!=null) //Вот и пригодился edSvc
            edSvc.CloseDropDown();
    }

Если же используется стиль Modal то в методе EditValue можно делать всё что угодно - создавать окна, и т.п., необхоимо лишь вернуть новое значение свойства

После того как UITypeEditor написан, необходимо назначить его свойству. Это можно сделать с помощью атрибута Editor применённого к классу свойтсва (тогда все свойства данного типа будут редактироваться им), или тем же атрибутом на конкретном свойтсве (редактор будет только для него). Также можно это сделать реализовав в TypeConverter метод GetEditor.


Использование TypeConverter
TypeConverter, наряду с его обычным использованием для преобразования типов данных может применяться совместно с PropertyGrid для задания вида свойств в редакторе.
Для возможности редактирования сложных свойств (таких как, к примеру, Color) в виде строки необхожимо реализовать в классе унаследованом от TypeConverter методы:
bool CanConvertFrom(ITypeDescriptorContext,Type)
object ConvertFrom(ITypeDescriptorContext,System.Globalization.CultureInfo,object)
bool CanConvertTo(ITypeDescriptorContext,Type)
object ConvertTo(ITypeDescriptorContext,System.Globalization.CultureInfo,object,Type)

Реализовать эти методы надо для Type=string. К примеру вот так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
        if(sourceType==typeof(string))return true;
        return base.CanConvertFrom (context, sourceType);
    }
    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) {
        if(value is string){
            string s=value as String;
            int ld=s.LastIndexOfAny("0123456789-Ee.,".ToCharArray());
            if(ld==-1){
                return double.Parse(s.Trim(),System.Globalization.NumberStyles.Any);
            }
            string mul=s.Substring(ld+1);
            s=s.Substring(0,ld+1);
            double v=double.Parse(s.Trim(),System.Globalization.NumberStyles.Any);
            switch(mul.ToLower().Trim()){
                case "кгц":case "к":case "k":case "khz":
                    v*=1000;
                    break;
                case "мгц":case "м":case "m":case "mhz":
                    v*=1000000;
                    break;
                case "ггц":case "г":case "g":case "ghz":
                    v*=1000000000;
                    break;
            }
            return new Frequency(v);
        }
        return base.ConvertFrom (context, culture, value);
    }
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
        if(destinationType==typeof(string))return true;
        return base.CanConvertTo (context, destinationType);
    }
    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) {
        if(destinationType==typeof(string)&&value!=null){
            return value.ToString();
        }
        return base.ConvertTo (context, culture, value, destinationType);
    }


Также с помощью TypeConverter можно реализовавыть собственный набор свойисв для любых классов. Делается это с помощью перегрузки метода GetProperties и метода GetPropertiesSupported. Однако здесь всплывает неслабый подводный камень. Возвращать из этого надо коллекцию PropertyDescriptor, а все стандартные его наследники НЕ облада.т открытыми конструкторами, так что придётся писать свой.
Кроме того собственный PropertyDescriptor позволяет получить полный контроль над PropertyGrid - динамичесикй список свойств, изменение типа свойств - всё это возможно. В PropertyDescriptor как минимум необходимо реализовать GetValue и SetValue (кстати лучше наследоваться от SimplePropertyDescriptor - проблем меньше будет). Делается это так (в примере набор свойтсв формируется из БД):
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    internal class VCol{
        internal BasicInsuranceCompanyImplementation ic;
        internal XtensionInterface.DSAutoIns.InsuranceSessionRow profile;
        public VCol(BasicInsuranceCompanyImplementation ic,XtensionInterface.DSAutoIns.InsuranceSessionRow profile){
            this.ic=ic;
            this.profile=profile;
        }
    }
    private class VColTC:TypeConverter{
        protected class MyPD:SimplePropertyDescriptor{
            private DSAutoIns.ParamsRow pr;
            public MyPD(DSAutoIns.ParamsRow pr,Type componentType,Type propertyType):base(componentType,pr.IsStockParamNull()?pr.Name:pr.StockParamsRow.Name,propertyType){
                this.pr=pr;
            }
            public override object GetValue(object o){
    //Не сильно инетересно - чтение значения из БД
    //-------------8<------------
                VCol vc=o as VCol;
                VValue rv=new VValue();
                rv.SetEmpty();
                if(vc==null)return rv;
                if(pr.IsStockParamNull()){
                    DSAutoIns.ValuesDataTable dt=(DSAutoIns.ValuesDataTable)SBAS.XtensionInterface.GlobalDS.Instance.Tables["Values"];
                    DSAutoIns.ValuesRow r;
                    if((r=dt.FindBySessionCompanyParam(vc.profile.id,vc.ic.id,pr.id))!=null){
                        rv.dname=(string)r.ParamValuesRow.Name;
                        rv.v=(int)r["Value"];
                    }
                }else{
                    DSAutoIns.StockValuesDataTable dt=(DSAutoIns.StockValuesDataTable)SBAS.XtensionInterface.GlobalDS.Instance.Tables["StockValues"];
                    DSAutoIns.StockValuesRow r=dt.FindBySessionParam(vc.profile.id,pr.StockParamsRow.id);
                    if(r!=null){
                        rv.dname=(string)r.StockParamValuesRow.Name;
                        rv.v=(int)r["Value"];
                    }
                }
                return rv;
    //-------------8<------------
            }
            public override void SetValue(object o,object vx){
    //Не сильно инетересно - запись значения в БД
    //-------------8<------------
                VCol vc=o as VCol;
                VValue vv=(VValue)vx;
                int v=vv.v;
                if(vc==null)return;
                if(pr.IsStockParamNull()){
                    DSAutoIns.ValuesDataTable dt=(DSAutoIns.ValuesDataTable)SBAS.XtensionInterface.GlobalDS.Instance.Tables["Values"];
                    DataRow r=dt.FindBySessionCompanyParam(vc.profile.id,vc.ic.id,pr.id);
                    if(r==null){
                        DSAutoIns.ParamValuesRow vr=((DSAutoIns.ParamValuesDataTable)SBAS.XtensionInterface.GlobalDS.Instance.Tables["ParamValues"]).FindByid((int)v);
                        dt.AddValuesRow(vc.profile,vc.ic.row,pr,vr);
                    }else{
                        r["Value"]=v;
                    }
                }else{
                    DSAutoIns.StockValuesDataTable dt=(DSAutoIns.StockValuesDataTable)SBAS.XtensionInterface.GlobalDS.Instance.Tables["StockValues"];
                    DataRow r=dt.FindBySessionParam(vc.profile.id,pr.StockParamsRow.id);
                    if(r==null){
                        DSAutoIns.StockParamValuesRow vr=((DSAutoIns.StockParamValuesDataTable)SBAS.XtensionInterface.GlobalDS.Instance.Tables["StockParamValues"]).FindByid((int)v);
                        dt.AddStockValuesRow(vc.profile,pr.StockParamsRow,vr);
                    }else{
                        r["Value"]=v;
                    }
                }
    //-------------8<------------
            }
        }
    //Гвоздь программы - формирование списка свойств
        public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) {
            PropertyDescriptorCollection pdc=new PropertyDescriptorCollection(null);
            Type t=this.GetType();
            VCol vc=(VCol)value;
            DSAutoIns.CompaniesRow cr=((DSAutoIns.CompaniesDataTable)SBAS.XtensionInterface.GlobalDS.Instance.Tables["Companies"]).FindByid(vc.ic.id);
            MyPD sc;
            DSAutoIns.ParamsRow[] Params=cr.GetParamsRows();
    //Для каждой записи из БД (каждого свойства) создаётся PropertyDescriptor
            foreach(DSAutoIns.ParamsRow pr in Params){
                sc=new MyPD(pr,t,typeof(Object));
                pdc.Add(sc);
            }
            return pdc;
        }
    //Сообщаем что будем использовать свойства
        public override bool GetPropertiesSupported(ITypeDescriptorContext context) {
            return true;
        }
    }

Для расширения функциональности можно реализовать следующие свойтсва PropertyDescriptor:
Attributes - здесь можно управлять ReadOnly свойства
Category
Description
DisplayName

И методы:
CanResetValue
ResetValue
GetEditor

Что не описано в данном FAQ
IExtendedProperties
и многое другое :)

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)