Главная » C#, Компоненты » Использование стандартных редакторов типов

0

Конечно же, не всегда нужно писать собственные редакторы типов. Часто вполне достаточно стандартных. В этом разделе я хочу рассказать о некоторых из них и возможных проблемах, возникающих при их использовании.

Редактирование коллекций

Далее я расскажу о редакторе типа coiiectionEditor и связанных с ним редакторах.

Будьте внимательны: класс CoiiectionEditor находится в сборке System.Design.oil. Некоторые другие классы, о которых я буду рассказывать далее, находятся в сборке System.Drawing.он. Часто ссылки на эти сборки нужно подключать вручную.

Редактирование StringCoilection

Давайте посмотрим на простое свойство типа StringCoilection:

private StringCoilection StringCoilection = new StringCoilection();

public StringCoilection StringCoilection {

get { return StringCoilection; } set { StringCoilection = value; }

}

При добавлении компонента с таким свойством на форму редактор свойств отображает кнопку с тремя точками, показывая, что он готов его редактировать. Более того, при нажатии на эту кнопку появляется редактор строковой коллекции, но вот добавить в него новую строку не получится — редактор выдает ошибку "Constructor on type ‘System.String’ not found" (рис. 9.11).

Рис. 9.11. Ошибка при попытке редактирования StringCoilection

Исправить ситуацию можно добавлением атрибута, определяющего редактор ТИГТа StringCollectionEditor."

[DesignerSerializationVisibility (

DesignerSerializationVisibility.Content)] [Editor{ "System.Windows.Forms.Design.StringCollectionEditor,

System. Design", "System.Drawing.Design.UITypeEditor, System. Drawing")]

public StringCollection StringCollection

{

get { return StringCollection; } set { StringCollection = value; }

}

Вид редактора при этом изменяется на обычный редактор многострочного текста (рис. 9.12). Про атрибут DesignerSerializationVisibility Я буду рассказывать в главе 10.

Рис. 9.12. Вид стандартного редактора String Collection Editor

Кстати, в некоторых случаях указание строковых имен типов редакторов не работает, и лучше либо указывать редакторы с помощью прямого указания типа Editor(typeof(StringCollectionEditor) , typeof(UlTypeEditor)), либо указывать конкретную версию сборки, в которой надо искать указанные типы':

[Editor("System.Windows.Forms.Design.StringCollectionEditor, System. Design,

Version=2.0.0.0, Culture=neutral,

PublicKeyToken=b03f5f7flld50a3a\ "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutrai, PublicKeyToken=b03f5f7flld50a3a"

)]

Теперь коллекцию строк можно редактировать без проблем.

Редактирование списка объектов

Теперь рассмотрим свойство, имеющее тип списка (в нашем случае ArrayList), который будет состоять из объектов типа Person (листинг 9.7).

private PersonArray personArray = new PersonArray {);

public PersonArray PersonArray <

get { return personArray; } set { personArray – value; }

1

Редактор свойств понимает, что это список неких объектов, но, конечно же, понять, что это список именно объектов person, не может. Соответственно, редактор этого свойства выглядит не очень красиво и, прямо скажем, бесполезным (рис. 9.13).

Рис. 9.13. Стандартный редактор коллекции объектов

Листинг 9 7 Классы ..\irv>r и FuibonAtr.jy

public class PersonArray : ArrayList

{

}

public class Person

{

private string firstName = string.Empty; private string lastName = string.Empty;

[Category{"Персональные данные")] [Description["Имя") ]

public string FirstName

{

get { return firstName; } set { firstName — value; }

}

[Category("Персональные данные")I [Description("Фамилия"}]

public string LastName

{

get { return lastName; ) set { lastName = value; )

}

}

Для добавления возможности редактирования списка объектов нужно создать специальный класс редактора (класс PersonArrayEditor). Базовым классом этого редактора является класс CoiiectionEditor, а для того чтобы редактор мог работать с объектами типа Person, требуется выполнить несколько действий:

?        класс PersonArrayEditor должен перекрывать метод Createinstanсе (). Этот метод должен возвращать объект Person и добавлять его в коллекцию;

?        класс PersonArray должен реализовывать индексатор this [index]. Он позволяет редактору узнать, объекты какого типа будут добавляться в коллекцию;

?        и конечно, редактор PersonArrayEditor должен быть привязан к свойству PersonArray (или типу PersonArray) С помощью атрибута Editor.

Код трех этих классов приведен в листингах 9.8—9.10. Результат работы этого кода показан на рис. 9.14. Обратите внимание на заголовок окна. Вместо

Object Collection Editor редактор подставляет название редактируемого типа объектов, и теперь окно называется Person Collection Editor. Еще одна деталь на рис. 9.14: по умолчанию каждый добавляемый объект имеет имя типа, т. е. MyControl. Person (второй объект на рис. 9.14). Выводить дополнительную информацию позволяет метод GetDisplayText {) класса

PersonArrayEditor:

protected override string GetDisplayText(object value} (

Person person = value as Person;

string result = base.GetDisplayText(value);

if ((person != null) && {person.LastName.Length > 0 |I person.FirstName.Length > 0))

!

result – string.Format{"{0) {1}", person.FirstName, person.LastName);

}

return result;

}

Теперь вместо стандартного MyControl. Person мы увидим имя и фамилию (первый объект на рис. 9.14).

Рис. 9.14. Редактор коллекции объектов Person

Кроме атрибута category, который мы использовали при описании полей класса person (и, соответственно, все свойства иа рис. 9.14 принадлежат указанной категории), МОЖНО использовать атрибут DisplayName, позволяющий изменить имя отбражаемого свойства:

[DisplayName("Фамилия")}

public string LastName

{

get/set

}

А вот с описанием свойств (атрибут Description) придется подождать до разд. "Изменение формы редактора коллекций".

Для добавления возможности сериализации свойства PersonArray требуется добавить К свойству атрибут DesignerSerializationVisibility (см. главу 10). Таким образом, полный список атрибутов свойства PersonArray будет выглядеть так:

[Editor(typeof(PersonArrayEditor), typeof(UITypeEditor)}] [TypeConverter(typeof(PersonArrayTypeConverter))] [DesignerSerializationVisibility(

DesignerSerializationVisibility.Content)] public PersonArray PersonArray

Теперь наш редактор стал полнофункциональным. Листинг 9 В. Класс ъш^г*

using System;

using System.Collections.Generic; using System.Text; using System.Collections; using System.ComponentModel; using System.Drawing.Design;

namespace MyControl

{

public class PersonArray : ArrayList

{

public new person this[int index]

}

set { base[index] = value;                    }

get ( return [Person)base[index]; }

Листинг 9 9 Класс Pos.sonA.rrayL’ia.toL

using System;

using System.Collections.Generic; using System.Text;

using System.ComponentModel.Design;

namespace MyControl

{

class PersonArrayEditor : CollectionEditor

{

public PersonArrayEditor(Type type) : base (type) {

}

protected override string GetDisplayText(object value) {

Person person = value as Person;

string result = base.GetDisplayText(value);

if ((person != null) &&

(person.LastName.Length > О II person.FirstName.Length > 0))

{

result = string.Format("{0} (1)",

person.FirstName, person.LastName);

return result;

}

protected override object Createlnstance(Type itemType) i

object obj = null;

if (itemType. FullName == "MyControl.Person")

{

PersonArray array = {(MyComponent)Context.Instance).PersonArray; obj = new Person(); array.Add(obj);

}

return obj;

Листинг 9 10 Пришмка редактора к свойству

[Editor(typeof(PersonArrayEditor), typeof(UITypeEditor))] public PersonArray PersonArray (

get ( return personArray; } set ( personArray = value; }

}

Редактирование списка объектов нескольких типов

Коллекция объектов не обязательно должна состоять из объектов одного типа. В таких случаях редактору коллекции необходимо сообщить, объекты каких типов предполагается добавлять в коллекцию. Сделать это можно, перекрыв метод CreateNewItemTypes (): protected override Type[] CreateNewItemTypes()

return new Typef] { typeof(Button), typeof(ListBox), typeof(Label) }

Но теперь в методе Createinstanceo мы уже не можем создать экземпляр нужного типа напрямую, т. к. типов может быть несколько. Конечно, можно использовать оператор switch, но это решение не очень гибкое, и мы воспользуемся классом Activator:

protected override object Createlnstance(Type itemType) (

// Здесь надо получить сам массив array.

// Создаем экземпляр класса через Activator.

object obj = Activator.Createlnstance(itemType);

// Добавляем в массив

array.Add(obj ) ;

// Возвращаем сам объект

return obj;

}

Правда, если создаваемые объекты имеют различные конструкторы, все-таки ПридеТСЯ ИСПОЛЬЗОВатЬ switch или if.

Вернемся к нашему примеру. Пусть у нас есть два наследника класса person (листинг 9.11). Не обращайте внимания, что классы пустые, сейчас это не главное. Соответственно, реализация метода CreateNewItemTypes о будет выглядеть примерно так:

protected override Туре[] CreateNewItemTypes() (

return new Type[] (

typeof(Person), typeof(Personl), typeof{person2) };

Такое решение не всегда является удобным, поэтому здесь я хочу рассказать про специальный сервис, позволяющий получать типы, наследованные от указанного. Сервис называется iTypeDiscoveryService. Как обычно, получение ССЫЛКИ на сервис производится С ПОМОЩЬЮ метода GetService (), но… в методе createNewltemTypes О нет ссылки на провайдер, позволяющий получить сервис. Зато такой параметр есть в методе Editvaiueo, и в нем нам придется заполнять специальный массив типов, который потом мы вернем в методе CreateNewltemTypes ().

Получив интерфейс сервиса ITypeDiscoveryService, МОЖНО вызывать метод GetTypes {), возвращающий все типы, которые являются наследниками типа указанного в качестве параметра. Полученный список мы сохраняем в специальный список типов result:

foreach (Type type in tds . GetTypes (typeof (Person) , false))

}

if (iresult.Contains(type))

f

result.Add(type);

}

}

Полный код получившегося редактора представлен в листинге 9.!2, а его вид показан на рис. 9.15. Как видно из рисунка, вместо обычной кнопки Add редактор отображает кнопку с выпадающим списком, который позволяет выбрать тип добавляемого объекта.

Рис. 9.15. Редактор коллекции объектов нескольких типов

Листинг 9 11 Классы Еег.ор, 1тглоп1 и Person^

using System;

using System.Collections.Generic;

using System.Text;

using System.ComponentModel;

namespace MyControl

public class Person

private string firstName; private string lastName;

[Category("Персональные данные")] [Description{"Имя") ] public string FirstName (

get { return firstName; } set { firstName = value; }

}

[Category("Персональные данные"}] [Description("Фамилия")]

public string LastName {

get ( return lastName; } set ( lastName = value; }

}

public override string ToString() (

if (!String.IsNullOrEmpty(LastName) && !String.IsNullOrEmpty(FirstName)}

{

return string.Format("(0} (1}", FirstName, LastName};

}

return base.ToString(};

}

}

public class Personl : Person {

}

public class Person2 : Person

{

}

}

Листинг 9 12 Редактор коллекции состонщои из нескольких типов

using System;

using System.Collections.Generic; using System.Text;

using System.ComponentModel.Design; using System.ComponentModel; using System.Windows.Forms;

namespace MyControl

{

class PersonArrayEditor : CollectionEditor private Type[] returnedTypes;

public PersonArrayEditor(Type type) : base(type)

{

}

П Сообщаем редактору возможные типы объектов protected override Туре[] CreateNewItemTypes()

К

return returnedTypes;

}

public override object EditValue ( ITypeDescriptorContext context, IServiceProvider provider, object value}

{

if (returnedTypes == null}

{

// Если мы хотим использовать сервис, то придется // инициализировать доступные типы здесь, // т. к. нам нужен объект provider returnedTypes = GetReturnedTypes(provider};

}

return base.EditValue(context, provider, value);

// Получаем доступные типы

private Туре[] GetReturnedTypes(IServiceProvider provider) (

List<Type> result = new List<Type>(); 11 Получаем интерфейс сервиса

ITypeDiscoveryService tds = (ITypeDiscoveryService) provider.GetService(typeof(ITypeDiscoveryService));

if (tds != null) (

// Нам нужны все "родственники" типа Person

foreach (Type type in tds.GetTypes(typeof(Person), false))

// Если в нашем списке такого типа еще нет, //то добавляем его if (!result.Contains(type)) (

result.Add(type);

}

}

}

return result.ToArray();

}

// Представление объекта в редакторе

protected override string GetDisplayText(object value) I

if (value != null}

return value.ToString(); else

return base.GetDisplayText(value);

}

// Объекты теперь имеют разный тип, поэтому

// для создания экземпляра объекта используется класс Activator protected override object Createlnstance(Type itemType) (

PersonArray array = ((MyComponent)Context.Instance).PersonArray; object obj = Activator.Createlnstance(itemType); array.Add(obj);

return obj;

}

}

1

Изменение формы редактора коллекций

Класс CollectionEditor содержит внутренний класс CollectionForm, с помощью которого реализуется редактор коллекций. Получить ссылку на эту форму можно посредством метода CreateCoiiectionFormO класса CollectionEditor. Например, следующий код заменяет заголовок окна:

protected override CollectionForm CreateCoiiectionFormO

}

CollectionForm form = base.CreateCollectionForm(); form.Text = "Редактор списка людей"; return form;

}

При большом желании можно реализовать собственный редактор и вернуть его,в качестве результата этого метода.

Класс CollectionForm является наследником класса Form, соответственно, с редактором коллекций можно работать как с обычной формой: перебирать элементы, добавлять свои и т. д.:

// Перебираем все элементы формы

foreach (Control control in form.Controls)

1

foreach (Control controll in control.Controls)

{

}

1

Как видно из кода, перебор всех элементов формы нужно проводить во вложенном цикле. В качестве примера я покажу, как отобразить значение атрибута Description в редакторе коллекции (рис. 9.16), так же как он отображается в обычном редакторе свойств. Сделать это очень просто— нужно найти компонент с ТИПОМ VsPropertyGrid И ВКЛЮЧИТЬ СВОЙСТВО HelpVisible (ЛИСТИНГ 9.13).

^Листинг 913 Включение отображения описания свойств

protected override CollectionForm CreateCoiiectionFormO !

CollectionForm form = base.CreateCollectionForm(}; form.Text = "Редактор списка людей";

// Перебираем все компоненты формы

foreach (Control control in form.Controls)

{

foreach (Control controll in control.Controls) (

// Нашли редактор свойств if (controll.GetType () .ToStringO ==

"System.Windows.Forms.Design.VsPropertyGrid")

11 Включаем отображение описаний свойств ((PropertyGrid)controll).HelpVisible = true; ( (PropertyGrid)controll).HelpBackColor = SystemColors.Info;

}

}

}

return form;

}

Рис. 9.16. Отображение описания свойства

Замена строки (Collection)

По умолчанию редактор свойств коллекций отображает строку (Collection). Заменить эту строку на более информативную (или локализированную) позволяет добавление в код специального конвертера типа (см. главу 8J, код которого показан в листинге 9.14:

[Editor(typeof(PersonArrayEditor), typeof(UITypeEditor))J [TypeConverter(typeof(PersonArrayTypeConverter))] public PersonArray PersonArray

Теперь вместо стандартной строки мы увидим строку Список… (рис. 9.17).

Листинг 9.14 Конвертер типа для замены (Collection) на свою строку                                                   |

using System;

using System.Collections.Generic; using System.Text; using System.ComponentModel; using System.Globalization;

namespace MyControl I

class PersonArrayTypeConverter : TypeConverter [

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,

Culturelnfo culture, object value, Type destinationType)

t

if (destinationType = typeof(string)) {

return "Список…";

]

return base.ConvertTo(context, culture,

value, destinationType);

}

}

}

Рис. 9.19. Редактор имени файла, позволяющий выбрать расширение файла

Аналогично, редактор FoiderNameEditor предоставляет возможность редактирования имени каталога и настройки диалога FoiderBrowser (рис. 9.20):

class DocFolderTypeEditor : FolderNaineEditor

{

protected override void InitializeDialog(

FolderNaineEditor. FoiderBrowser folderBrowser)

{

base.InitializeDialog(folderBrowser); 11 Устанавливаем свойства folderBrowser

}

}

Описание класса FolderBrowser можно найти в MSDN.

Рис. 9.20. Редактор имени каталога

Редактор-дерево

Еще один интересный редактор типа, о котором я хочу рассказать, позволяет отображать редактируемое значение в виде дерева. Соответствующий класс редактора называется objectseiectorEditor и является наследником обычного uiTypeEditor. В дополнение ко всем базовым методам этот класс добавляет виртуальный метод:

void FillTreeWithData (

ObjectseiectorEditor.Selector selector, ITypeDescriptorContext context, IServiceProvider provider);

Перекрыв этот метод, можно заполнить дерево, представленное специальным классом Selector, конкретными значениями.

Класс selector является наследником класса Treeview и фактически добавляет только один метод, позволяющий добавить новый узел в дерево:

public ObjectSelectorEditor.SeiectorNode AddNode( string label, object value,

ObjectSelectorEditor.SeiectorNode parent); Класс seiectorNode — это обычный TreeNode, но имеющий специальное поле public object value; и соответствующий конструктор: public SeiectorNode(string label, object value); Собственно, код редактора выглядит следующим образом:

public class SelectorEditor : ObjectSelectorEditor {

protected override void FillTreeWithData(Selector selector,

ITypeDescriptorContext context, IServiceProvider provider)

{

base.FillTreeWithData(selector, context, provider);

SeiectorNode nodel – selector.AddNode("LI", "Vl", null); SeiectorNode node2 = selector.AddNode("L2", "V2", nodel); SeiectorNode node3 = selector. AddNode {"L3”, "V3", nodel);

selector.ShowLines = true; selector.ExpandAll();

}

}

В результате редактор будет выглядеть так, как на рис. 9.21. Обратите внимание на последние две строки. Первая из них включает отображение линий в дереве, а вторая — раскрывает дерево. Без этих строк вы увидите только значение li, а остальное будет скрыто.

В примере, приведенном ранее, в дереве отображаются строки li, L2, ьз, но реальными значениями свойства будут являться значения vl, V2 и V3:

this.objectSelectorEditorControll.Test = "v2";

Разумеется, значениями свойства могут быть не только строки. Точнее, тип значений должен совпадать с типом свойства, к которому прикреплен редактор. Можно, например, организовать набор классов:

SelectorNode nodel – selector.AddNode(

"Button", new Button(}, null); SelectorNode node2 = selector.AddNode(

"CheckBox"’, new CheckBoxO, null); SelectorNode node3 – selector.AddNode(

"RadioButton", new RadioButton{), null);

Что-то подобное я показывал в разд. 8.10, но там список был линейным, а с помощью этого редактора можно создать дерево. Да и код получается более простым. Но самое существенное отличие заключается в том, что в этот список можно добавлять уже существующие объекты, не создавая их заново. Например, можно создать редактор выбора компонентов, находящихся на форме. Сделать это с помощью конвертера типа не получится.

Для того чтобы можно было редактировать свойства классов-значений, нужно добавить атрибут ExpandableObjectConverter:

private object testl;

(Editor(typeof(SelectorEditorl), typeof(UITypeEditor))] (TypeConverterAttribute(typeof(ExpandableObjectConverter))] public object Testl

{

get ( return testl; } set ( testl = value; }

}

Как это выглядит в редакторе свойств, показано на рис. 9.22.

Литература:

Агуров П. В. C#. Разработка компонентов в MS Visual Studio 2005/2008. – СПб.: БХВ-Петербург, 2008. — 480 е.: ил.

По теме:

  • Комментарии