Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Взгляд на CListCtrl изнутри - меняем внешний вид


DevDoc home page
 
   
 

Выпуск №9

Сегодня в выпуске еще одна статья о возможностях CListCtrl. Речь пойдет о том, как можно изменить внешний вид элементов списка.

На сайте появилась еще одна статья от ZeroIce. Он рассматривает оригинальные способы извлечения паролей из EditBox и реализации клавиатурного шпиона.

Все материалы доступны на сайте http://www.devdoc.ru Наш девиз - новые статьи каждую неделю. Ресурс находится в постоянном развитии. Если у Вас есть интересный материал, вы можете опубликовать его на сайте. Инструкция по публикации.

Пожалуйста, присылайте свои вопросы и пожелания к темам статей на sub12@devdoc.ru

Если вам нравиться эта рассылка рекомендуйте ее своим друзьям. Подписаться можно по адресу http://subscribe.ru/catalog/comp.soft.prog.devdoc


Постоянная ссылка на статью: http://www.devdoc.ru/index.php/content/view/customdraw_clistctrl.htm

Автор: Кудинов Александр
Последняя модификация: 2007-02-23 18:09:35

Взгляд на CListCtrl изнутри - меняем внешний вид

Скачать исходники

Введение

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

Дополнительно к виртуализации способа хранения данных, элемент управления предоставляет функцию Custom Draw. Название само говорит о назначении этого режима. В MSDN дано очень подробное описание с примерами. Хорошая новость в том, что стандартные элементы управления, начиная с версии 4.7, предоставляют эту возможность. К сожалению, толком нигде не описано, для чего он может использоваться.

В одном из наших проектов надо было реализовать список для нестандартного содержимого. Я поверхностно знал о Custom Draw и Owner Draw режимах, которые доступны для списков. Преимущества и недостатки каждого из режимов были загадкой. Для получения ответа на этот вопрос я детально изучил оба режима. В последующем я неоднократно применял эти знания и хорошо знаком с деталями реализации.

На практике Custom Draw имеет меньшую гибкость, чем Owner Draw. Однако это с лихвой компенсируется простотой использования. Программе надо обрабатывать всего одно сообщение - NM_CUSTOMDRAW. Прелесть еще в том, что большую часть работы Windows берет на себя и надо реализовать только то, что необходимо.

Специально для статьи я написал демонстрационный код. Его можно скачать по ссылке в начале статьи. Я компилировал его в среде MS VC .NET 2003 под Windows XP. Думаю, что не составит большого труда скомпилировать его с помощью других версий компилятора.

Обратите внимание, что стандартные элементы управления версии 4.7 идут с IE 4 и выше. На сегодняшний день это не должно быть проблемой для большинства систем.

Основы

Я постараюсь не излагать прописных истин, которые можно найти в документации, а сжато описать принцип использования Custom Draw для списка.

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

Замечание: обрабатывать события можно и в классе, произведенном от CListCtrl. Этот метод может быть предпочтительней, если вы разрабатываете элемент управления для использования в разных местах программы.

Обработка сообщений Custom Draw

На разных этапах рисования содержимого списка Windows посылает программе сообщения NM_CUSTOMDRAW. Это похоже на использование Callback’ов. Программа может проигнорировать сообщение, и тогда Windows выполнит рисование в данной точке самостоятельно. Если проигнорировать все сообщения, то вы увидите стандартный лист контрол. В реальности Вы должны обрабатывать только часть сообщений, чтобы немного подправить внешний вид и добавить разные эффекты. Таким образом, вы будете рисовать только то, что необходимо, а остальное возьмет на себя Windows.

По умолчанию, система уже посылает сообщение NM_CUSTOMDRAW и необходимо только добавить обработчик. Вставим в карту сообщений макрос:

ON_NOTIFY(NM_CUSTOMDRAW, IDC_LV_CUSTOM_DRAW, OnNMCustomdrawLvCustomDraw)

Метод OnNMCustomdrawLvCustomDraw определен следующим образом:

afx_msg void OnNMCustomdrawLvCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);

Такая запись говорит, что при поступлении события NM_CUSTOMDRAW от списка IDC_LV_CUSTOM_DRAW надо вызвать обработчик OnNMCustomdrawLvCustomDraw. Все просто. Начиная с этого момента, приложение может самостоятельно контролировать процесс рисования внешнего вида списка.

Замечание: Если вы хотите обрабатывать событие внутри своего класса, произведенного от CListCtrl, надо использовать макрос ON_NOTIFY_REFLECT в его карте событий.

Этапы рисования

Как было указано выше, процесс рисования списка разбит на 4 этапа. На каждом из них рисуются определенные элементы. Процесс разбит на две основные стадии: очистка и рисование. Windows посылает сообщение NM_CUSTOMDRAW в начале и в конце каждой стадии. Таким образом, всего получается 4 этапа рисования.

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

Приведу пример сообщений, которые могут посылаться:

  • Перед очисткой элемента списка
  • После очистки элемента списка
  • Перед рисованием элемента списка
  • После рисования элемента списка

Замечание. За всю практику мне ни разу не удалось использовать последние два типа сообщений. Они хоть и описаны в MSDN, но в реальности я не смог добиться, чтобы окно их получило.

На практике может потребоваться обработка всего одного или двух сообщений из этого списка. Я использовал только сообщения для рисования, и этого вполне хватало для реализации сложных вещей.

Обработчик сообщений определяет, какие события он хочет обрабатывать. Он возвращает одну из констант, которая сообщает Windows, какая часть рисования выполнена, и что делать дальше. Существует всего 5 вариантов ответа вашего обработчика.

  • Обработчик не сделал ничего. Требуется, чтобы Windows нарисовала весь контрол или какой-то его элемент целиком.
  • Обработчик изменил шрифт для списка. В этом случае Windows пересчитает размер рисуемого элемента.
  • Обработчик сам нарисовал элемент списка. Windows не надо больше ничего делать.
  • Обработчик хочет получать дополнительные сообщения NM_CUSTOMDRAW во время этапов рисования элементов списка (item).
  • Обработчик хочет получать дополнительные сообщения NM_CUSTOMDRAW во время этапов рисования строки и подэлементов (subitem).

Этапы рисования могут относиться как к элементу управления в целом, так и к отдельным его элементам. Именно поэтому можно получать разное количество сообщений. Сначала программа получает NM_CUSTOMDRAW для всего элемента управления в целом. Если обработчик вернет значение 4 из списка выше – Windows будет посылать дополнительные сообщения для рисования каждого элемента. Ответ 5 – это запрос на еще большее количество сообщений. Они будут посылаться также для всех этапов рисования элементов колонок. Это актуально, если список в режиме Report View.

Обработчик OnNMCustomdrawLvCustomDraw получает на входе указатель на структуру типа LPNMLVCUSTOMDRAW, которая содержит всю необходимую информацию:

  • Этап рисования
  • Размеры элемента
  • Дескриптор окна и его идентификатор.
  • Дескриптор графического устройства (DC) для рисования.
  • Индекс элемента в списке
  • Индекс подэлемента (subitem)
  • 32-битный параметр, который может быть привязан к каждому элементу. Его можно менять с помощью методов CListCtrl

Смотрите документацию в MSDN на эту структуру для более подробной информации.

Пример обработчика

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

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

Вот как это делается:

void CCListCtrl_CustomDrawDlg::OnNMCustomdrawLvCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
{
 LPNMLVCUSTOMDRAW pNMCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
 // Будем выполнять стандартную обработку для всех сообщений по умолчанию
 *pResult = CDRF_DODEFAULT;
 
 //Сначало надо определить текущую стадию
 if ( CDDS_PREPAINT == pNMCD->nmcd.dwDrawStage )
 {
  //если рисуется весь элемент целиком - запрашиваем получение сообщений
  //для каждого элемента списка.
  *pResult = CDRF_NOTIFYITEMDRAW;
 }
 else if ( CDDS_ITEMPREPAINT == pNMCD->nmcd.dwDrawStage )
 {
  //Стадия, которая наступает перед отрисовкой каждого элемента списка
  //Мы не выполняем рисование текста. Вместо этого просто меняется цвет и
  //даем указание Windows, что рисование должен выполнить контрол.
  
  DWORD iItemIdx = pNMCD->nmcd.dwItemSpec;
 
  //Вычисляем цвет по "магической" формуле
  pNMCD->clrText = RGB(BYTE(iItemIdx * 40), 255, BYTE(iItemIdx * 20));
 
  // Уведомляем систему, чтобы она самостоятельно нарисовала элемент.
  *pResult = CDRF_DODEFAULT;
 }
}

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

Как видите, изменение цвета текста – очень простая процедура. Так же легко можно поменять цвет фона под текстом. Для этого надо записать в переменную pNMCD->clrTextBk желаемый цвет.

Теперь давайте усложним пример, и будем обрабатывать еще больше стадий рисования. В приведенном выше примере мы меняли цвет для строки целиком. В следующем – цвет будет меняться для каждой колонки отдельно. Делается это не намного сложнее.

Прошу обратить внимание, что в MSDN описание Custom Draw не совсем корректно. По крайней мере, когда я пробовал делать именно так, как там написано – ничего не получалось. Речь идет о коде возврата CDRF_NOTIFYSUBITEMDRAW. В MSDN сказано, что я могу вернуть его в самом начале цикла рисования. На практике получается, что это значение имеет эффект только при обработке этапа CDDS_ITEMPREPAINT.

void CCListCtrl_CustomDrawDlg::OnNMCustomdrawLvCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
{
 LPNMLVCUSTOMDRAW pNMCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
 // Будем выполнять стандартную обработку для всех сообщений по умолчанию
 *pResult = CDRF_DODEFAULT;
 
 //Сначало надо определить текущую стадию
 if ( CDDS_PREPAINT == pNMCD->nmcd.dwDrawStage )
 {
  //если рисуется весь элемент целиком - запрашиваем получение сообщений
  //для каждого элемента списка.
  *pResult = CDRF_NOTIFYITEMDRAW;
 }
 else if ( CDDS_ITEMPREPAINT == pNMCD->nmcd.dwDrawStage )
 {
  *pResult = CDRF_NOTIFYSUBITEMDRAW;
 }else if( (CDDS_ITEMPREPAINT | CDDS_SUBITEM) == pNMCD->nmcd.dwDrawStage) 
 {
  //Стадия, которая наступает перед отрисовкой каждого столбца элемента списка
  //Мы не выполняем рисование текста. Вместо этого просто меняется цвет и
  //даем указание Windows, что рисование должен выполнить контрол.
 
  DWORD iItemIdx = pNMCD->nmcd.dwItemSpec;
 
  //Вычисляем цвет по "магической" формуле
  pNMCD->clrText = RGB(BYTE(iItemIdx * 40), 255, BYTE(iItemIdx * 20));
  if(0 == pNMCD->iSubItem)
  {
   pNMCD->clrTextBk = RGB(80, 10, 80);
  }else if(1 == pNMCD->iSubItem)
  {
   pNMCD->clrTextBk = RGB(80, 50, 50);
  }else
  {
   pNMCD->clrTextBk = RGB(50, 50, 80);
  }
 
  // Уведомляем систему, чтобы она самостоятельно нарисовала элемент.
  *pResult = CDRF_DODEFAULT;
 }
}

В обоих примерах обрабатывается этап перед началом рисования элемента списка, а система выполняет процесс рисования самостоятельно. Это накладывает некоторые ограничения на допустимые действия. Мы можем менять внешний вид элементов в очень ограниченном виде. А именно - менять цвет шрифта и заднего фона. Начиная с версии 6.0 можно применять дополнительные эффекты, о которых можно прочитать в MSDN.

Если сделать обработку сообщений NM_CUSTOMDRAW, на этапе после рисования элемента можно самостоятельно добавлять ряд эффектов. Например, можно скорректировать внешний вид маркера выделения, перерисовать иконку рядом с элементом и т.п. Эти этапы целесообразно использовать, только если надо внести незначительные изменения отображения.

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

Продолжение следует…


Уже заканчивая верстать этот выпуск, вспомнил историю, которая произошла чуть меньше года назад. Я проводил собеседование с программистом, у которого были притязания на управление командой разработчиков, разработку архитектуры и т.п. За плечами более 6 лет работы и ожидания по зарплате были соответствующие. На мою просьбу оценить свои знания по ООП и С++ он поставил себе 10 балов из 10 возможных. В ходе дальнейшей беседы выяснилось, что оценка сильно завышена. Претендент плыл в виртуальных функциях, не знал, что есть ключевое слово mutable, зачем нужен dynamic_cast и т.п. Про знания в сфере психологии и управлении я вообще молчу. В итоге я не только отказал в приеме на работу, но и занес его в свой черный список.

Зачем я это все рассказал, вы узнаете в следующем выпуске.


Если вам нравиться эта рассылка рекомендуйте ее своим друзьям. Подписаться можно по адресу http://subscribe.ru/catalog/comp.soft.prog.devdoc

Copyright (C) Kudinov Alexander, 2006-2007

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


В избранное