13.9. Рисование элементов списка ListBox

Интересной может быть особенность рисования не только на поверхности компонентов, но и внутри их. У компонента ListBox есть очень интересное свойство DrawMode, которое определяет режим рисования. По умолчанию это свойство равно Normal, и в этом случае элементы списка рисуются компонентом так, как это заложено в .NET. Но в если изменить DrawMode на OwnerDrawFixed или OwnerDrawVariable, то мы сможем написать собственный код рисования элементов списка.

Так, при использовании режима рисования OwnerDrawFixed все элементы списка будут иметь один жестко определенный размер. В случае с OwnerDrawVariable размер элементов может задаваться вами.

Давайте напишем пример, в котором на форме у нас будет список ListBox, элементы которого отображают различные стили пера Pen. Создайте новое приложение, поместите на форму компонент ListBox и измените его свойство DrawMode на OwnerDrawFixed. В конструкторе класса после вызова метода InitializeComponent() пишем следующий код:

   foreach (String str in Enum.GetNames(typeof(DashStyle)))
   {
     if (str != "Custom")
       listBox1.Items.Add(str);
   }

Здесь у нас цикл, который перебирает все имена стилей перечисления DashStyle, с помощью которого можно изменять стиль рисуемой линии. Внутри цикла проверяем, равен ли текущий стиль Custom, и если он не равен Custom, то добавляем имя в список ListBox. Стиль Custom добавлять нельзя, потому что для него нужно явно указывать маску линии, а мы этого не делаем, и его добавление в список ListBox приведет к ошибке во время рисования.

Теперь создаем для компонента списка ListBox обработчик события DrawItem. Этот обработчик события вызывается каждый раз, когда нужно нарисовать очередной элемент списка. В этом обработчике события пишем код, показанный в листинге 13.5.

Листинг 13.5. Рисование элемента списка

private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
  // прорисовка фона
  e.DrawBackground();

  // определяю имя текущего элемента
  string currName = listBox1.Items[e.Index].ToString();

  // определяю стиль
  DashStyle dashStyle =
      (DashStyle)Enum.Parse(typeof(DashStyle), currName);

  // создаю перо
  Pen p = new Pen(Color.Red, 2);
  p.DashStyle = dashStyle;

  // рисую прямоугольник
  e.Graphics.DrawRectangle(p, e.Bounds.X + 2, e.Bounds.Y + 2,
       e.Bounds.Width - 4, e.Bounds.Height - 4);
  e.Graphics.DrawString(currName, e.Font, new SolidBrush(e.ForeColor),
       e.Bounds.X + 2, e.Bounds.Y);

  // рисую рамку выделения
  e.DrawFocusRectangle();
}

Самым интересным у обработчика события DrawItem является второй параметр, который имеет тип DrawItemEventArgs. Этот класс содержит необходимые свойства и методы, с помощью которых мы можем узнать, какой элемент списка нуждается в прорисовке. Давайте посмотрим на свойства этого класса, потому что они пригодятся для понимания нашего кода:

  • BackColor — цвет фона, который должен использоваться при рисовании эле- мента;
  • Bounds — прямоугольник, определяющий границы элемента, внутри которых нужно рисовать;
  • Font — шрифт для рисования текста;
  • ForeColor — цвет переднего плана;
  • Graphics — объект рисования;
  • Index — индекс элемента в списке, нуждающегося в перерисовке;
  • State — состояние элемента. Этот параметр имеет тип данных перечисления DrawItemState. Основные значения этого перечисления:
    • Checked — элемент отмечен;
    • ComboBoxEdit — элемент редактируется;
    • Default — состояние по умолчанию;
    • Disabled — отключен;
    • Focus — в фокусе;
    • HotLight — курсор мыши на поверхности элемента;
    • Inactive — элемент неактивен;
    • Selected — элемент выделен.

Состояния важны, если вы самостоятельно захотите прорисовывать рамку фокуса элемента, а не воспользуетесь значением по умолчанию.

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

Обратите внимание на тип свойства State, а точнее, на имя возможного значения перечисления: ComboBoxEdit. В имени присутствует фрагмент ComboBox! Да, это состояние относится к выпадающему списку. Мы можем использовать точно такой же подход к рисованию элементов в выпадающем списке. Подход идентичен, поэтому я рассматривать его не буду. Попробуйте сами сделать ComboBox, элементы которого будут рисоваться вами самостоятельно.

Теперь посмотрим на два метода класса DrawItemEventArgs, которые могут упростить вам создание собственного метода рисования элементов:

  • DrawBackground() — нарисовать фон по умолчанию. В принципе, этот метод просто закрашивает область элемента цветом фона;
  • DrawFocusRectangle() — нарисовать рамку фокуса для элемента методом по умолчанию, если это необходимо.

Вернемся к нашему коду обработчика события DrawItem. В самом начале вызывается метод рисования фона по умолчанию: DrawBackground(). Это необходимо, иначе могут возникнуть проблемы с рамкой фокуса, она просто не будет стираться при перемещении с одного элемента списка на другой.

Теперь определяем имя элемента списка, который нужно прорисовать. Это не так уж и сложно, ведь мы знаем его индекс: e.Index. Зная имя, мы можем получить стиль DashStyle с помощью статичного метода Parse() класса Enum. Подобные трюки мы уже проделывали не один раз.

Следующим этапом создается перо красного цвета и толщиной в два пиксела. Бабушка, а зачем тебе такое большое перо? Это чтобы лучше его видеть, внученька! Созданному перу изменяем стиль (DashStyle) на соответствующий текущему элементу.

Теперь можно рисовать прямоугольник, который будет показывать пример использования пера, и сверху рисовать строку с названием стиля. Напоследок вызывается метод DrawFocusRectangle(), который по необходимости нарисует рамку фокуса.

В общем-то, мы могли бы нарисовать ее самостоятельно, но я не стал усложнять код проверкой состояния текущего элемента. Результат работы примера показан на рис. 13.3.

Рис. 13.3. Результат работы программы

Предыдущая глава

13.8. Графический дизайнер

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

Без проблем вступаю в неразборчивые разговоры по e-mail. Стараюсь отвечать на письма всех читателей вне зависимости от страны проживания, вероисповедания, на русском или английском языке.

Пишите мне