- •Краткий путеводитель
- •Проектируйте
- •Сохраняйте гибкость
- •Коллекции элементов
- •Отрисовка
- •Придерживайтесь стандартов
- •Оптимизируйте
- •Взаимодействие с мышью
- •Взаимодействие с клавиатурой
- •Поведение в режиме дизайна
- •Использование атрибутов
- •Коллекции
- •Конверторы типов
- •Расширение компонентов
- •Локализация
- •Источники данных
- •Доступность
- •Конвертор типа
Поведение в режиме дизайна
Не полагайтесь на порядок установки значений свойств контрола дизайнером и тем более не подразумевайте, что некоторое свойство будет уже выставлено к моменту выставления другого свойства. Если избежать этого невозможно в силу особых свойств контрола, используйте интерфейс ISupportInitialize. Если некоторый компонент реализует этот интерфейс, дизайнер вставит вызов BeginInit перед началом инициализации и EndInit по окончании. Таким образом, вы сможете отследить, что инициализация закончена и, например, вызвать Recalculate для расчёта необходимых данных.
Во многих случаях необходимо знать, используется ваш контрол в данный момент в нормальных условиях (в работающем приложении) или же в режиме дизайна. Для этого необходимо использовать свойство DesignMode у сайта контрола (объекта, реализующего ISite). Если контрол уже сконструирован и находится в своём контейнере (например, форме), то его сайт можно получить, используя свойство Site – оно есть у любого компонента и контрола.
Если же вам необходимо узнать это еще в конструкторе, то всё становится несколько сложнее. Для начала попробуйте перепроектировать контрол таким образом, чтобы определение режима не требовалось в конструкторе или методе, вызываемом из него.
Если это невозможно по каким-либо причинам, придётся использовать медленный способ трассировки стека вызовов. В данном примере реализации такой проверки мы сначала пытаемся использовать быстрый способ. Если это не удалось, то мы просматриваем стек вызовов в поисках объекта, реализующего IDesignerHost. При нахождении такового мы делаем вывод, что мы находимся в дизайн-тайме. В реальном приложении может иметь смысл ограничить глубину просмотра стека.
|
private bool IsDesignMode() { if(Site != null ) return Site.DesignMode;
StackTrace stackTrace = new StackTrace(); int frameCount = stackTrace.FrameCount - 1;
for( int frame = 0; frame < frameCount; frame++ ) { Type type = stackTrace.GetFrame(frame).GetMethod().DeclaringType; if (typeof(IDesignerHost).IsAssignableFrom(type)) return true; } return false; }
|
Также можно проверить, что исполняемой design-средой является Visual Studio: if (Application.ExecutablePath.ToLower().IndexOf("devenv.exe") > -1). Этот способ, хотя и быстрее, не позволит вам правильно определить другие возможные дизайнеры, например SharpDevelop.
Использование атрибутов
Дизайнер форм не знает предназначения свойства вашего контрола - предназначено ли оно для дизайнера или только для работы из кода, и какое у него значение по умолчанию. Поэтому всегда размечайте каждое публичное свойство соответствующими атрибутами Category, Description, DefaultValue, Browsable, DesignerSerializationVisibility. При отсутствии некоторых из этих атрибутов среда самостоятельно предполагает некоторое значение по умолчанию, но тем не менее лучше размечать все свойства, поскольку в какой-либо другой среде, другом дизайнере форм или вообще отдельном приложении используемые по умолчанию значения могут быть совершенно другими. Краткое описание этих атрибутов:
-
Category – устанавливает имя группы, в которую необходимо поместить свойство или событие при отображении в PropertyGrid.
-
Description – устанавливает описание для свойства или события. Отображается в отдельной секции PropertyGrid.
-
DefaultValue – устанавливает значение свойства, используемое по умолчанию. Дизайнер может подсвечивать измененное свойство, позволять сбросить значение свойства и решить, нуждается ли свойство в сохранении (сериализации).
-
Browsable – указывает, должно ли свойство или событие показываться в PropertyGrid.
-
EditorBrowsable – указывает, в каком случае редактор исходного текста будет показывать наличие данного свойства. Может быть Always (всегда показывать), Advanced (показывать только в соответствующем режиме) и Never (не показывать вообще).
-
DesignerSerializationVisibility – устанавливает режим сохранения (сериализации) свойства. Может быть Visible (сохранять), Hidden (не сохранять), Content (сохранять содержимое).
Помечайте свойства атрибутом DefaultValue, чтобы сообщить дизайнеру форм о значении по умолчанию. Если значение по умолчанию вашего свойства не может быть сохранено в метаданных сборки (т.е. это не простой тип, не строка и не Type), то вы не сможете применить атрибут DefaultValue напрямую. Если у вас есть TypeConverter для типа свойства, и он может преобразовать строку к экземпляру нужного типа, то атрибут DefaultValue можно использовать в виде [DefaultValue(Type, string)], например, для свойства типа Size можно использовать такой вариант: [DefaultValue(typeof(Size), "32;32")]. Учтите, что при этом всегда используется Invariant Culture.
Если вы не можете использовать атрибут DefaultValue из-за отсутствия TypeConverter или нетривиальной логики, можно реализовать bool ShouldSerializeNNNN() и void ResetNNNN(), где NNNN – это имя свойства. ShouldSerialize должен возвращать true, если значение свойства отлично от значения по умолчанию. Reset должен вернуть свойство к значению по умолчанию. Дизайнер, обнаружив эти методы и не обнаружив атрибута DefaultValue, будет использовать их по необходимости. Следите за тем, чтобы значение по умолчанию действительно выставлялось в конструкторе. Эти методы не обязаны быть публичными, потому что среда использует их через рефлексию (reflection). Тем не менее, часто их выгодно делать внутренними (internal) для последующего использования в TypeConverter.
Используйте атрибут DefaultProperty у класса контрола для того, чтобы сказать дизайнеру, какое свойство является главным. При выборе вашего контрола такое свойство будет сделано текущим. Учтите, что если вы редактировали свойство одного контрола, а потом переключились на другой контрол, в котором есть свойство с таким же именем, дизайнер сохранит текущее свойство независимо от атрибута DeafultProperty. Аналогично выставляйте атрибут DefaultEvent у класса, указывая основное событие, например Click. При двойном щелчке на контроле в режиме дизайна автоматически будет создан (или открыт, если уже создан) обработчик этого события.
Если некоторый метод или свойство являются потенциально опасными и требуют дополнительных знаний об использовании, имеет смысл пометить такой член класса атрибутом [EditorBrowsable(EditorBrowsableState.Advanced)]. В таком случае его увидят в intellisense только те пользователи, у которых в свойствах Visual Studio сброшен флажок Hide advanced members. Можно и вообще спрятать свойство, используя [EditorBrowsable(EditorBrowsableState.Never)]. Например, в классе Control есть метод ResetBackColor, являющийся публичным и виртуальным. Однако он не подсказывается редактором, и набрать его можно только вручную.
Если некоторый класс является компонентом (реализует интерфейс IComponent), а вы не хотите, чтобы он появлялся под редактором формы в области компонентов (component tray), используйте атрибут [DesignTimeVisible(false)].
Чтобы сообщить среде, что вы хотите видеть свой класс на панели доступных контролов и компонентов (Tool Box), используйте атрибут ToolboxItem(true). Задать картинку для контрола можно с помощью атрибута ToolboxBitmap. Например, пометка атрибутом ToolboxBitmap(typeof(MyControl)] приведет к поиску ресурса с именем namespace.MyControl.bmp в сборке с типом MyControl и использование этой картинки на панели инструментов. Картинка должна быть 16-цветной, размером 16х16 пикселей. Цвет левого нижнего пикселя будет считаться прозрачным. Если вы хотите использовать иконку, а не bmp-файл, вам придётся указать это явно, используя вариант атрибута: [ToolboxBitmap(typeof(MyControl), "ToolboxIcons.MyControl.ico")].
Если некоторое свойство должно наследовать своё значение от контейнера, иначе говоря, быть прозрачным при некоторых условиях, используйте атрибут AmbientValue вместо DefaultValue. Это сообщит дизайнеру, знакомому со структурой объектов, что реальное значение нужно брать у контейнера. Это особенно важно использовать для локализуемых свойств, потому что иначе для них будет сгенерирован код чтения из ресурсов. Например, атрибут [AmbientValue(null)] указывает на то, что при выставлении свойства в null контрол начинает использовать аналогичное свойство родителя. Примерами таких свойств могут быть Font или BackColor. Учтите, что, несмотря на этот атрибут в коде getter-а, всё равно необходимо опрашивать родителя самостоятельно из кода геттера (getter) свойства, дизайнер просто учитывает это при отображении значений в PropertyGrid и генерации кода в InitializeComponent.
Если изменение некоторого свойства влечет за собой изменение значения другого свойства, необходимо сообщить об этом системе дизайна. Для этого используется атрибут RefreshProperties. Например, если свойство помечено [RefreshProperties(RefreshProperties.All)], то при его изменении все свойства данного компонента будут заново опрошены, и вся информация обновлена.
Если необходимо отображать свойство как «специальное», т.е. с круглыми скобками вокруг имени, наподобие (Name) или (DataBindings), используйте атрибут [ParenthesizeProperty(true)]. Не стоит злоупотреблять этой возможностью, поскольку такие свойства выбиваются из общей сортировки по именам – они отображаются в самом верху окна свойств (PropertyGrid) или соответствующей категории.
