logo
03

Оптимизируйте

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

Создание Brush, Pen является относительно дорогостоящей операцией, а изменение свойств контрола, влияющих на них, происходит достаточно редко. Поэтому вместо создания таких объектов прямо в OnPaint, храните соответствующие Brush, Pen и другие аналогичные данные в классе вместе с переменными, хранящими соответствующие цвет, толщину и другие параметры. Меняйте их синхронно с изменением соответствующих свойств. Не забывайте вызывать Dispose для таких объектов при смене свойств и в Dispose самого контрола. Поскольку до создания Handle контрола такие данные вряд ли понадобятся, разумно создавать эти кэширующие объекты в переопределенном методе CreateHandle. Схему работы с такими данными можно изобразить так:

public class MyControl : Control

{

private Color _someColor;

private Brush _someBrush;

public Color SomeColor

{

get { return _someColor; }

set

{

if (_someColor == value)

return;

if (IsHandleCreated)

_someBrush.Dispose();

_someColor = value;

if (IsHandleCreated)

_someBrush = new SolidBrush(_someColor);

}

}

protected override void CreateHandle()

{

base.CreateHandle();

_someBrush = new SolidBrush(_someColor);

}

protected override void Dispose(bool isDisposing)

{

if (isDisposing && IsHandleCreated)

{

_someBrush.Dispose();

}

base.Dispose(isDisposing);

}

}

Унаследованные свойства вроде BackColor можно переопределить (override), чтобы поддерживать кэш для Brush.

Не используйте двойную буферизацию, если структура вашего контрола позволяет отрисовать его таким образом, чтобы каждая точка была выставлена ровно один раз. Например, простейший ProgressBar может быть отрисован двумя прямоугольниками. В таком случае, установите стиль ControlStyles.AllPaintingInWmPaint, при этом игнорируется сообщение Windows об очистке фона окна, и метод OnPaintBackground не вызывается.

Устанавливайте соответствующие стили контрола до создания его окна, например, чтобы избавиться от мерцания в сложном контроле, можно использовать такие стили:

SetStyle(

ControlStyles.DoubleBuffer

| ControlStyles.AllPaintingInWmPaint

| ControlStyles.UserPaint, true);

Используйте специальные методы классов Graphics, ControlPaint где возможно. Например, DrawImageUnscaled быстрее простого DrawImage. Если вы рисуете элементы ImageList, используйте метод ImageList.Draw(...) вместо Graphics.DrawImage(_imageList.Images[0], ...) – это не только быстрее, но и единственный способ отрисовки иконку (Icon) без потери альфа-канала.

Если в вашем компоненте много событий, можно использовать встроенную в класс Component возможность унификации их обработки. Защищенное свойство Events поддерживает словарь событий и позволяет манипулировать событиями единообразно. Кроме того, если пользователь подписывается лишь на некоторые из них (а это самый частый вариант), то неиспользуемые события не будут занимать место в памяти. С другой стороны, на каждое использованное событие расходуется примерно в три раза больше памяти, чем если бы делегат был просто членом класса, поэтому если событий мало, или если часто используется больше трети событий, то овчинка не стоит выделки. Пример использования такой техники показан ниже:

// создаем уникальный ключ для нашего события

private readonly static object EventDataChanged = new object();

public event EventHandler DataChanged

{

add

{

// добавить обработчик в общую таблицу

Events.AddHandler(EventDataChanged, value);

}

remove

{

// удалить обработчик из общей таблицы

Events.RemoveHandler(EventDataChanged, value);

}

}

protected virtual void OnDataChanged(EventArgs e)

{

// получить обработчик из таблицы

EventHandler eventHandler = (EventHandler)Events[EventDataChanged];

// если есть подписчики, то оповестить их

if (eventHandler != null)

eventHandler(this, e);

}

Обработка действий пользователя

Пользователь – это наименее предсказуемый элемент системы, и в то же время наиглавнейший из всех. В конце концов, именно ради пользователя вы создаете свой компонент. Успех ваших трудов определяется двумя основными факторами. Первый – удобство конечного пользователя, определяющий фактор при выборе вашего контрола программистом. Второй – удобство программиста, который будет пытаться заставить ваш контрол вести себя так, как ему нужно, опять же в интересах конечного пользователя. О программистах мы поговорим в следующем разделе, посвященном поддержке дизайнера форм и редактора, а сейчас рассмотрим вопросы, связанные с действиями и ожиданиями пользователя.

В этом аспекте одним из важнейших моментов является сохранение правильного состояния контрола. Необходимо поддерживать внутренне непротиворечивое состояние при любой последовательности поступающих событий. Пользователь может нажать клавишу и, не отпуская её, кликнуть мышкой, или нажать левую кнопку мыши, затем кликнуть правой или провернуть колесико, и лишь затем отпустить левую кнопку. Во всех таких случаях ваш контрол должен среагировать некоторым непротиворечивым способом. В простейшем случае при определении «странного» действия можно сбросить все режимы и, во всяком случае, не сделать ничего плохого. В этом вам поможет информация о согласованном состоянии компонента, собранная при анализе и проектировании.

Одно из важнейших общих правил при описании взаимодействия с пользователем – это отделение обработки ввода от собственно действия. Не стоит сооружать огромных switch-case конструкций с логикой внутри OnKeyDown или OnMouseUp. В этих функциях надо лишь определиться, какое действие должно быть выполнено, и вызвать соответствующий метод, который произведет необходимые изменения. Например, при нажатии стрелочки вверх можно вызвать защищенный виртуальный метод MoveUp и всю логику перемещения курсора вверх произвести в нём. Впоследствии, можно будет изменять логику поведения контрола независимо от обработки действий пользователя. Кроме того, наследник вашего класса сможет переопределить поведение естественным образом.