1.1. Изменение высоты и расположения CHeaderCtrl и высоты строк CListCtrl с помощью сообщений HDM_LAYOUT и WM_MEASUREITEM

Этот материал ранее был опубликован в виде статьи на http://www.codeproject.com (на английском языке) после обсуждения на форуме http://sql.ru/forum/C++/.

Здесь фактически воспроизводится итоговый материал обсуждения из того же форума:

Изменение высоты и расположения CHeaderCtrl и высоты строк CListCtrl с помощью сообщений HDM_LAYOUT и WM_MEASUREITEM

Июнь 2009 года

Решена проблема «деформации» заголовка и его местоположения, продемонстрировано изменение высоты строк списка и форматирование текста в ячейках контрола.

Загрузить демонстрационные файлы с www.codeproject.com (требуется регистрация (бесплатная) на этом сайте):

TypicalListCtrl.zip - 51.58 Kb
ModifiedListCtrl.zip - 60.98 Kb

Загрузить демонстрационные файлы с sql.ru (сначала просто зайдите на этот сайт, затем воспользуйтесь ссылками):

TypicalListCtrl.zip - 51.58 Kb
ModifiedListCtrl.zip - 60.98 Kb

Загрузить те же файлы отсюда (переименуйте расширение в zip):

TypicalListCtrl.001 - 51.58 Kb
ModifiedListCtrl.001 - 60.98 Kb

Формулировка проблемы и способов ее решения

Не знаю, чем можно объяснить такой факт, что заголовок CHeaderCtrl / SysHeader32 стандартного списка Windows CListCtrl / SysListView32 рисуется с некоторым искажением, которое сразу бросается в глаза. Это портит впечатление от программ использующих эти контролы. Обычно линии заголовка отстают на 1 пиксел от линий списка и сам заголовок почему-то на 2 пиксела короче, чем следовало бы. Чтобы скрыть этот эффект, часто линии списка убирают совсем. Это хорошо видно на рис. 1-3.

Типичный CListCtrl с данными и сеткой
Рис. 1. Типичный CListCtrl с данными и сеткой.

CListCtrl без линий сетки
Рис. 2. Тот же CListCtrl без линий сетки.

Увеличенный CListCtrl с линиями сетки
Рис. 3. Увеличенный CListCtrl с линиями сетки.

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

Мы также желаем использовать заданные шрифты и форматирование текста в ячейках заголовка и списка. Кроме этого, демонстрационный пример, для типичного списка, мы будем заполнять данными, обычным способом, т.е. посредством InsertItem / SetItem, а для модифицированного списка будем использовать виртуальный режим (стиль LVS_OWNERDATA), через SetItemCount и обработкой сообщения LVN_GETDISPINFO.

Результат нашей работы представлен на рис. 4. Заметим, что хотя мы желали для типичного CListCtrl’a выравнивание первого столбца «Ind. No.» по правому краю, этот контрол проигнорировал наше «распоряжение» и выровнял столбец по левому краю (рис. 1-3). И только для модифицированного контрола мы смогли добиться нужного выравнивания (рис. 4). Кроме того, мы показали, как можно изменять форматирование данных по умолчанию.

Модифицированные CListCtrl / CHeaderCtrl с линиями сетки
Рис. 4. Модифицированные CListCtrl / CHeaderCtrl с линиями сетки.

Если мы меняем высоту заголовка за счет увеличения размеров шрифта или иконок, то указанная «деформация» тем не менее, сохраняется. Очевидно, что это «свойство» не самого MFC класса CListCtrl а контрола Windows SysListView32.

Чтобы ликвидировать «деформацию» заголовка, нужно дополнительно сместить его вправо на 1 пиксел и увеличить его высоту на 2 пиксела, желательно без явного привлечения таких функций как MoseWindow / SetWindowPos, а также без необходимости делать собственную перерисовку CHeaderCtrl / CListCtrl в обработчиках типа OnPaint. Кроме этого, мы не хотим использовать функции типа SetFont не по назначению (только для увеличения размеров заголовка, с последующим переопределением его шрифта). То же самое касается и иконок, которые мы желаем использовать исключительно по назначению, а не для изменения только размеров заголовка или строк (скажем, за счет использования тонких иконок заданной высоты и не отличимых от фона). Есть еще вариант пересоздавать заголовок на базе CHeaderCrtrl за счет функции Create, который допускает явное указание размеров и его местоположения, но этот вариант нас также не устраивает как слишком громоздкий.

Таким образом, у нас остается фактически один вариант для изменения высоты и местоположения заголовка (за счет обработки сообщения HDM_LAYOUT) и один вариант изменения высоты строк списка (за счет обработки сообщения WM_MEASUREITEM). Способ с HDM_LAYOUT интересен еще и потому, что очень часто программисты используют его не совсем корректно, что иногда приводит к избыточному коду или неверной работе программы. Заодно мы покажем, как можно форматировать текст в ячейках списка и его заголовка.

Обработка сообщения HDM_LAYOUT

Чтобы получить и обработать сообщение заголовка таблицы HDM_LAYOUT необходимо определить обработчик:

afx_msg LRESULT OnLayout(WPARAM wparam, LPARAM lparam);

в файле заголовка типа HeaderCtrlEx.h. Затем в соответствующем файле HeaderCtrlEx.cpp указать в карте сообщений макрос:

ON_MESSAGE(HDM_LAYOUT, OnLayout)

и определить соответствующую функцию OnLayout:

/////////////////////////////////////////////////////////////////////////
// OnLayout
/////////////////////////////////////////////////////////////////////////
LRESULT CHeaderCtrlEx::OnLayout(WPARAM, LPARAM lParam) {
  LPHDLAYOUT pHL = reinterpret_cast<LPHDLAYOUT>(lParam);
	
  //*** The table list rectangle
  RECT *pRect = pHL->prc;

  //*** The table header rectangle
  WINDOWPOS *pWPos = pHL->pwpos;

  //*** Here's equivalent code for the code which follows after
  /*
  pWPos->hwndInsertAfter = NULL;

  //*** Moves the table header to the righ
  pWPos->x = pRect->left + m_nHdrWidthDefect;
  pWPos->y = pRect->top;
  pWPos->cx = pRect->right - pRect->left;

  //*** New table header height
  pWPos->cy = m_nHdrHeight + m_nHdrHeightDefect;

  pWPos->flags = SWP_NOACTIVATE|SWP_NOZORDER;

  //*** Decreases the table list height on the table header height
  pRect->top += m_nHdrHeight;

  return TRUE;
  */
  //*** Sends HDM_LAYOUT message to the base class
  int nRet = CHeaderCtrl::DefWindowProc(HDM_LAYOUT, 0, lParam);

  //*** Moves the table header to the right
  pWPos->x += m_nHdrWeightDefect;

  //*** New table header height
  pWPos->cy = m_nHdrHeight + m_nHdrHeightDefect;

  //*** Decreases the table list height on the table header height
  pRect->top = m_nHdrHeight;

  return nRet;
}  // OnLayout

Фактически, в ней показаны два варианта изменения размеров и местоположения заголовка класса CHeaderCtrlEx без явного привлечения функций типа SetFont / MoveWindow / SetWindowPos / CHeaderCtrlEx::Create или использования псевдо иконок (для достижения тех же целей). Не смотря на кажущуюся простоту этой функции, многие программисты допускают ошибки в обработчике сообщения HDM_LAYOUT. Возможно, это вызвано недостаточно четким описанием структуры LPHDLAYOUT в MSDN.

Обработка сообщения WM_MEASUREITEM

Аналогично предыдущему, определяем обработчик:

afx_msg void MeasureItem(LPMEASUREITEMSTRUCT pMIS);

в файле заголовка типа ListCtrlEx.h. Затем в соответствующем файле ListCtrlEx.cpp указываем в карте сообщений макрос:

ON_WM_MEASUREITEM_REFLECT()

и определяем соответствующую функцию MeasureItem:

/////////////////////////////////////////////////////////////////////////
// MeasureItem
/////////////////////////////////////////////////////////////////////////
void CListCtrlEx::MeasureItem(LPMEASUREITEMSTRUCT pMIS) {
  //*** The table list height
  pMIS->itemHeight = m_nListHeight;
}  // MeasureItem

Форматирование текста

Немногим более сложно определяется форматирование текста заголовка по нашему усмотрению. Вот код CHeaderCtrlEx::DrawItem :

////////////////////////////////////////////////////////////////////////
// DrawItem
////////////////////////////////////////////////////////////////////////
void CHeaderCtrlEx::DrawItem(LPDRAWITEMSTRUCT pDIS) {
  HDITEM hDI;
  TCHAR szBuf[MAXITEMTEXT];

  hDI.mask = HDI_TEXT;
  hDI.pszText = szBuf;
  hDI.cchTextMax = MAXITEMTEXT;

  GetItem(pDIS->itemID, &hDI);

  CDC *pDC;
  HDC hDC = pDIS->hDC;  // Handle to device context
  pDC = CDC::FromHandle(hDC);

  //*** Selects necessary font
  pDC->SelectObject(m_pFont);
  //pDC->SelectObject(GetStockObject(DEFAULT_GUI_FONT));

  int x = 0;  // x-coordinate of reference point
  int y = 0;  // y-coordinate of reference point
  UINT nOptions = 0;  // Text output options ETO_CLIPPED|ETO_OPAQUE
  RECT *pIRect = NULL;  // Optional clipping and/or opaquing rectangle

  pIRect = &pDIS->rcItem;
  SIZE Size = {0};

  //*** Gets the header cell sizes
  if(!GetTextExtentPoint(hDC, szBuf, wcslen(szBuf), &Size)) {
    _M("Failed to call GetTextExtentPoint for table header!");
    return;
  }

  //*** x-coordinate of reference point
  x = (pIRect->left + pIRect->right - Size.cx)/2 - 1;
  x = (x < pIRect->left + 2) ? pIRect->left + 2 : x;

  //*** y-coordinate of reference point
  y = (pIRect->bottom - pIRect->top - Size.cy)/2 - 1;
	
  //*** Specifies that the current background color fills the rectangle
  // pIRect
  nOptions |= ETO_CLIPPED;

  //*** Decreases the text border to the right
  pIRect->right -= 4;

  //*** Writes the text in the (x, y) – coordinates
  pDC->ExtTextOut(x, y, nOptions, pIRect, szBuf, wcslen(szBuf), NULL);

  //*** Restores system font
  pDC->SelectStockObject(SYSTEM_FONT);
}  // DrawItem

И наиболее существенный код для форматирования текста списка:

///////////////////////////////////////////////////////////////////////
// SetColItemText
///////////////////////////////////////////////////////////////////////
void CListCtrlEx::SetColItemText(CDC *pDC, CString& stColText, CRect& 
    TextRect, UINT nJustify) {
  int x = 0;  // x-coordinate of reference point
  int y = 0;  // y-coordinate of reference point
  UINT nOptions = 0;  // Text-output options ETO_CLIPPED|ETO_OPAQUE

  int nTextLen = stColText.GetLength();
  HDC hDC = pDC->m_hDC;
  SIZE Size = {0};

  if(!GetTextExtentPoint(hDC, stColText, nTextLen, &Size)) {
    _M("Failed to call GetTextExtentPoint for table list!");
    return;
  }

  // Align the text in the whole table
  CRect TmpRect(TextRect);

  //*** x-coordinate of reference point
  x = (TextRect.left + TextRect.right - Size.cx)/2 - 1;
  x = (x < TextRect.left + 2) ? TextRect.left + 2 : x;

  //*** y-coordinate of reference point
  y = (TextRect.bottom - TextRect.top - Size.cy)/2 - 1;
	
  //*** Specifies that the current background color fills the rectangle
  nOptions |= ETO_OPAQUE;

  //*** Draw the background fast
  pDC->ExtTextOut(TextRect.left, TextRect.top, nOptions, TextRect, NULL, 
    0, NULL);

  TmpRect.left++;  // Cosmetic
  TmpRect.top += y;  // y-coordinate of reference point
  TmpRect.InflateRect(-3, 0);  // Text does not touch borders
	
  UINT nFormat = 0;

  switch(nJustify & LVCFMT_JUSTIFYMASK) {
    case LVCFMT_LEFT:
      nFormat = DT_LEFT;
      break;
    case LVCFMT_RIGHT:
      nFormat = DT_RIGHT;
      break;
    case LVCFMT_CENTER:
      nFormat = DT_CENTER;
      break;
    default:
      _M("CListCtrlEx: Error of the text formatting!");
      return;
  }

  //*** Writes the text in the TmpRect
  ::DrawText(hDC, stColText, nTextLen, TmpRect, nFormat);
}  // SetColItemText

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




Сайт создан в системе uCoz