Аннотация:
Как заменить имя переменной в левой колонке “человеческим” именем свойства?
Как отобразить расширенную подсказку по свойству в нижнем окне?
Как сгруппировать свойства по категориям?
Как отобразить свойство, недоступное для редактирования?
Как заменить стандартные True/False в отображении свойств типа bool?
Как заменить стандартное отображение имен членов перечисления?
Как показать свою картинку для каждого значения из перечисления?
Как организовать выбор значения из выпадающего списка, формируемого программно?
Как реализовать отображение составного свойства?
Как организовать выбор файла с заданным расширением?
Как организовать редактирование свойства в собственной форме?
Как организовать редактирование свойства в выпадающем списке?
Как управлять видимостью свойства в зависимости от значения другого свойства?
Как избавиться от стандартного “(Collection)” в правой колонке для свойств-коллекций?
Как заменить стандартные подписи (Members, properties) в окне Collection Editor?
Как добавить в Collection Editor окно с расширенной подсказкой по редактируемым свойствам?
Как задать отличный от алфавитного порядок следования свойств внутри категории?
Как запомнить и восстановить положение разделителя колонок в PropertyGrid?
SB>Спасибо за статью. SB>Как реализовать локализацию свойств в гриде?
SB>Спасибо.
Пример:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class DisplayNameLocalizedAttribute : DisplayNameAttribute
{
private Type resourceSource;
private string resourceName;
protected DisplayNameLocalizedAttribute()
{
}
public DisplayNameLocalizedAttribute(Type resourceSource, string resourceName)
{
this.resourceSource = resourceSource;
this.resourceName = resourceName;
UpdateDisplayName();
}
public void UpdateDisplayName()
{
ResourceManager rm = new ResourceManager(resourceSource);
DisplayNameValue = rm.GetString(resourceName);
}
}
Используем так:
public class YearRequestByMonthConfig : BaseReportConfig
{
//YearRequestByMonthRptConfigResources - компонент с ресурсами[DisplayNameLocalized(typeof (YearRequestByMonthRptConfigResources), "Department")]public string Department
{
get { return department; }
set { department = value; }
}
}
1. Имхо, часто, к сожелению, забывают в TypeCoverter-ах проверять "destType":
class BooleanTypeConverter : BooleanConverter
{
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture,
object value,
Type destType)
{
return (bool)value ?
"Есть" : "Нет";
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture,
object value)
{
return (string)value == "Есть";
}
}
2.
Реализовать EnumTypeConverter, осуществляющий преобразование к строке с учетом атрибута Description…
ИМХО, DiaplayName гораздо умеснее в подобной ситуации. А не сделал ли кто уже подобный класс, но который умеет работать с Flags-enum-ами? То есть где было бы реализовано ConvertFrom из локализованной строки в значение? Всегда ли правильно будет использовать Thread.CurrentThread.CurrentUICulture для выбора вариантов или культуру можено как-то выцепить из контекста?
3.
public override void PaintValue(PaintValueEventArgs e)
{
// картинки хранятся в ресурсах с именами, соответствующими
// именам каждого члена перечисления Sexstring resourcename = ((Sex)e.Value).ToString();
// достаем картинку из ресурсов
Bitmap sexImage =
(Bitmap)Resources.ResourceManager.GetObject(resourcename);
Rectangle destRect = e.Bounds;
sexImage.MakeTransparent();
// и отрисовываем
e.Graphics.DrawImage(sexImage, destRect);
}
Не надо ли вызывать sexImage.Dispose()?
4.
Как реализовать отображение составного свойства?
При использовании этого аттрибута с написанными мною UserControl-ами, на вкладке событий PropertyGrid-а почему-то не добавляются обработчики для компонентов, показанных через "ExpandableObjectConverter" Как с этим можно справиться?
Help will always be given at Hogwarts to those who ask for it.
Реализовать EnumTypeConverter, осуществляющий преобразование к строке с учетом атрибута Description…
_FR>ИМХО, DiaplayName гораздо умеснее в подобной ситуации.
слово-то покрасивше, только компилятор говорит:
error CS0592: Attribute 'DisplayName' is not valid on this declaration type. It is valid on 'class, method, property, indexer, event' declarations only.
Реализовать EnumTypeConverter, осуществляющий преобразование к строке с учетом атрибута Description…
_FR>>ИМХО, DiaplayName гораздо умеснее в подобной ситуации.
OE>слово-то покрасивше, только компилятор говорит:
OE>error CS0592: Attribute 'DisplayName' is not valid on this declaration type. It is valid on 'class, method, property, indexer, event' declarations only.
OE>как это разрулить?
Дать мне по башке, иначе никак Виноват, поторопился.
В любом случае, значение надо грузить из ресурсов, поэтому:
Только вот такой вроде как простой вопрос возник (создание редакторов проблем не вызвало, а тут такая мелочь...):
PropertyGrid события не показывает, но ведь они есть в стандартном Properties окне дизайнера, который тоже отображает изменения сделанные атрибутами.
Это я к тому что такой атрибут как Category — не работает для event'ов. Это событие просто не появляется в Properties, хоть указывай атрибут, хоть не указывай. Никак нельзя сделать чтобы он показывался?
// для свойства, естесственно, работает
[Category("Category1")]
public int Position
{
get { return position; }
set { position = value; }
}
// а вот для события нет...public delegate void ChangedEventHandler();
[Category("Category2")]
public ChangedEventHandler PositionChanged;
PositionChanged не появляется в Properties... Хотя он конечно доступен программно. Просто хотелось бы чтобы он был виден и через дизайнер.
Здравствуйте, Трубаров Вячеслав, Вы писали:
ТВ>Это я к тому что такой атрибут как Category — не работает для event'ов.
Работает.
ТВ>// а вот для события нет...
ТВ>public delegate void ChangedEventHandler();
ТВ>[Category("Category2")]
ТВ>publicevent ChangedEventHandler PositionChanged;
Без ключегвого свова "event": PositionChanged — лишь открытое поле.
... << RSDN@Home 1.2.0 alpha rev. 670>>
Now playing: «Тихо в лесу…»
Help will always be given at Hogwarts to those who ask for it.
кто-нибудь сталкивался с необходимостью отображения изменений (произошедших вне PropertyGrid) в гриде?
PropertyGrid, как я понял, даже не думает подписываться на изменения показываемых PropertyDescriptor — в студии, видимо,
он отслеживает события IComponentChangeService — если это так, как его туда подцепить?
На 1.2 в Гриде была официальная бага(не могу найти ссылку ) Проявление примерно следующее
Редакторуем класс, свойство которого массив обьектов(наример два). Обьекты имеют поля. Так вот если
1) розвернуть все плюсики, в т.ч. свойст обьектов в массиве, установить фокус на свойство обьекта в массиве (не уверен)
2) вызвать редактор массива
3) поудалять, подобавлять полей
4) закрыть редактор
Возникает "index out of bounds".
Лечилось написанием своего редактора (возможно и можно подцепить стандартный) массивов и перед выходом из UITypeEditor.EditValue сворачиванием дерева
// Принудительно обнновляем грид если редактировалась динамическая структура
PropertyGrid* m_propertyGrid = __try_cast<GlobalizedPropertyGrid::GlobalizedObject*>(context->get_Instance())->PropGrid;
GridItem* gi = m_propertyGrid->SelectedGridItem;
GridItem* parent = NULL;
while (gi != NULL)
{
parent = gi;
gi = gi->Parent;
}
if (parent != NULL)
{
GridItemCollection* coll = parent->get_GridItems();
for(int i = 0; i < coll->Count; i++)
{
GridItem* child = coll->get_Item(i);
if (child->GridItemType == GridItemType::Category)
{
GridItemCollection* coll2 = child->get_GridItems();
for(int i = 0; i < coll2->Count; i++)
{
coll2->get_Item(i)->Expanded = false;
}
}
else
child->Expanded = false;
}
}
m_propertyGrid->Refresh();
Более полный фрагмент
// Отображает визальный диалог для редактирования<br>
// коллекции соответствующего типа<br>
// Связь между редактируемым свойством и типом <br>
// коллекции устанавливается статически в ресурсы <br>
// программы во время разработкиpublic __gc class FieldListCollectionUITypeEditor : public System::Drawing::Design::UITypeEditor
{
public:
FieldListCollectionUITypeEditor() {}
System::Drawing::Design::UITypeEditorEditStyle GetEditStyle(System::ComponentModel::ITypeDescriptorContext* context) {
return UITypeEditorEditStyle::Modal;
}
// Редактирует свойство
Object* EditValue(System::ComponentModel::ITypeDescriptorContext* context, System::IServiceProvider* provider, Object* value) {
String* ResoursePath = S"NIST.VALUE";
// Return the value if the value is not of type FieldBase* __gc[].
// Необходимо включить проверку на входные данные
// if (value->GetType() != __typeof(System::String)&&( value->GetType() != __typeof(System::Int32)))
// return value;
String* propertyName = S"";
if (context->get_PropertyDescriptor()!=NULL)
{
propertyName = context->get_PropertyDescriptor()->get_Name();
}
//
System::Reflection::Assembly* ass = System::Reflection::Assembly::GetEntryAssembly();
// Now use table name and display name id to access the resources.
ResourceManager* rm = new ResourceManager(ResoursePath,ass);
// Get the string from the resources.
System::String* n = rm->GetString(String::Concat(propertyName,S"TYPE"));
if (n!=NULL)
{
NIST::CollectionEditor* editor = new NIST::CollectionEditor();
// инициализируем редактор if (n->Equals(S"String"))
{
FieldCollectionString* fc = __try_cast<FieldCollectionString*>(value);
//Create temp copy of colrction
FieldCollection* temp = new FieldCollectionString();
for (int i = 0; i < fc->Count; i++)
{
__try_cast<FieldCollectionString*>(temp)->Add(fc->get_Item(i));
}
editor->setSource(temp,n);
}
else if (n->Equals(S"DateTime"))
{
FieldCollectionDateTime* fc = __try_cast<FieldCollectionDateTime*>(value);
//Create temp copy of colrction
FieldCollection* temp = new FieldCollectionDateTime();
for (int i = 0; i < fc->Count; i++)
{
__try_cast<FieldCollectionDateTime*>(temp)->Add(fc->get_Item(i));
}
editor->setSource(temp,n);
}
else
{
FieldCollection* fc = __try_cast<FieldCollection*>(value);
//Create temp copy of colrction
FieldCollection* temp;
if (n->Equals(S"FieldDAI"))
{
temp = new FieldCollectionDAI();
}
else if (n->Equals(S"FieldCNO"))
{
temp = new FieldCollectionCNO();
}
else if (n->Equals(S"FieldORN"))
{
temp = new FieldCollectionORN();
}
else if (n->Equals(S"FieldNAM"))
{
temp = new FieldCollectionNAM();
}
else if (n->Equals(S"FieldGSA"))
{
temp = new FieldCollectionGSA();
}
else if (n->Equals(S"FieldFPR"))
{
temp = new FieldCollectionFPR();
}
else
return value;
for (int i = 0; i < fc->Count; i++)
{
temp->Add(fc->get_Item(i));
}
editor->setSource(temp,n);
}
// получаем результатif (editor->ShowDialog()==DialogResult::OK)
{
if (n->Equals(S"String"))
{
value = __try_cast<FieldCollectionString*>(editor->collection);
}
else if (n->Equals(S"DateTime"))
{
value = __try_cast<FieldCollectionDateTime*>(editor->collection);
}
else
value = editor->collection;
}
}
// Принудительно обнновляем грид если редактировалась динамическая структура
//if (context->get_Instance()->GetType() == __typeof(GlobalizedPropertyGrid::GlobalizedObject))
{
PropertyGrid* m_propertyGrid = __try_cast<GlobalizedPropertyGrid::GlobalizedObject*>(context->get_Instance())->PropGrid;
GridItem* gi = m_propertyGrid->SelectedGridItem;
GridItem* parent = NULL;
while (gi != NULL)
{
parent = gi;
gi = gi->Parent;
}
if (parent != NULL)
{
GridItemCollection* coll = parent->get_GridItems();
for(int i = 0; i < coll->Count; i++)
{
GridItem* child = coll->get_Item(i);
if (child->GridItemType == GridItemType::Category)
{
GridItemCollection* coll2 = child->get_GridItems();
for(int i = 0; i < coll2->Count; i++)
{
coll2->get_Item(i)->Expanded = false;
}
}
else
child->Expanded = false;
}
}
m_propertyGrid->Refresh();
}
return value;
}
// Draws a representation of the property's value.void PaintValue(System::Drawing::Design::PaintValueEventArgs* e) {
}
// Indicates whether the UITypeEditor supports painting a
// representation of a property's value.bool GetPaintValueSupported(System::ComponentModel::ITypeDescriptorContext* context) {
return false;
}
};
Здравствуйте, laad, Вы писали:
L>кто-нибудь сталкивался с необходимостью отображения изменений (произошедших вне PropertyGrid) в гриде? L>PropertyGrid, как я понял, даже не думает подписываться на изменения показываемых PropertyDescriptor — в студии, видимо, L>он отслеживает события IComponentChangeService — если это так, как его туда подцепить?
внутри UITypeEditor что-то вроде:
IComponentChangeService ccs = provider.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
ccs.OnComponentChanged(..);
Здравствуйте, vensub, Вы писали:
V>Здравствуйте, laad, Вы писали:
L>>кто-нибудь сталкивался с необходимостью отображения изменений (произошедших вне PropertyGrid) в гриде? L>>PropertyGrid, как я понял, даже не думает подписываться на изменения показываемых PropertyDescriptor — в студии, видимо, L>>он отслеживает события IComponentChangeService — если это так, как его туда подцепить?
V>внутри UITypeEditor что-то вроде:
V>
V>IComponentChangeService ccs = provider.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
V>ccs.OnComponentChanged(..);
V>
Спасибо Ж)
на самом деле, там несколько тоньше: нужно установить PropertyGrid.Site такой, чтобы он отвечал на GetService( typeof( IDesignerHost) )
только в таком случае PropertyGrid начинает запрашивать другие сервисы, в том числе IComponentChangeService.
Вообще говоря, похоже на некоторую нестыковку у микрософта — с одной стороны, PropertyDescriptor поддерживает в принципе оповещения об изменении значения, с другой — PropertyGrid на него забивает и использует сервис. Похоже, в ходе разработки поняли, что нужны транзакции для нормальной скорости работы всех связки — и сделали IComponentChangeService.
Re: PropertyGrid FAQ
От:
Аноним
Дата:
06.02.07 16:52
Оценка:
Здравствуйте, Алексей Кирюшкин, Вы писали:
АК>Статья: АК>PropertyGrid FAQ
Есть вопрос по editor-ам.
Есть тип MyCollectionEditor, производный от CollectionEditor.
Конструктор CollectionEditor выглядит так:
public CollectionEditor (Type collection_type)
Мне же нужно, чтобы конструктор моего класса MyCollectionEditor, принимал
помимо типа коллекции, ещё некоторые другие параметры, т.е. чтобы мой конструктор
выглядел вот так:
public MyCollectionEditor (Type collection_type, SomeType additional_data)
Так вот, возможно ли как-то прикрутить мой MyCollectionEditor к редактированию коллекции
в PropertyGrid?
Отличная статья, огромное спасибо.
Но у меня есть одна проблема.
Мне нужно сделать редактирование поля из БД.
пусть я должен хранить идентификатор сущности в БД, а показывать значение соотв. поля этому идентификатору.
К примеру, я храню идентификатор сущьности "Еденица измерения", а в поле PropertyGrid вместо цифрового идентификатора показывается его наименование.
У меня получилось это отчасти.
Показать имя по идентификатору.
Но как только мне понадобилось
вызывать соотв. форму выбора еденицы измерения (поле PropertyGrid редактируется только через соотв. модальное окно выбора ед. измерения) у меня ничего не получилось.
Прошу прощения за каламбур.
Итак есть класс Unit
public class Unit
{
private decimal unitId;
public decimal UnitId
{
get{return unitId;}
set{unitId = value;}
}
}
после того как я подсуну этот класс в PropertyGrid.SelectedObject, я хочу видеть в
отображаемом поле не Id, а наименование идентификатора.
Выбор Unit у меня происходит из отдельной формы. Ее вызов я делаю реализацией своего EditAttribute.
Но для преобразования Id в наименование я использовал потомка от DecimalConverter. Но ничего не получилось, когда я стал использовать и Editor и TypeConverter.
Результат — отображение только идентификатора.
Когда я комментил Editor, то все работало, но я не мог тогда использовать свою модальную форму для выбора единицы измерения.
Люди добрые помогите, или подскажите, что читать. У меня уже истерика начинается.
DT>пусть я должен хранить идентификатор сущности в БД, а показывать значение соотв. поля этому идентификатору.
Решение очень простое.
Делается класс, который хранит как идентификатор, так и строковое представление (наименование сущности).
public class YearRequestByMonthConfig : BaseReportConfig
{
public class EntityHolder
{
public EntityHolder()
{
}
public EntityHolder(decimal id, string name)
{
this.id = id;
this.name = name;
}
public decimal Id
{
get { return id; }
set { id = value; }
}
private decimal id;
public string Name
{
get { return name; }
set { name = value; }
}
public override string ToString()
{
return Name;
}
private string name;
}
Далее при реализации выбора сущности из БД делаем чего-то такое:
А пол, которое редактируем (скажем какое-то подразделение)
отображаем так:
[Browsable(true)]
[DisplayNameLocalized(typeof (YearRequestByMonthRptConfigResources), "Department")]
[Editor(typeof (DepartmentSelectEditor), typeof (UITypeEditor))]
public EntityHolder Department
{
get { return department; }
set { department = value; }
}