Компонент-потомок TListView с возможностью показа стрелочки в заголовке (как у Проводника Windows)

Источник: delphikingdom
Дмитрий Савченко

Автор: Дмитрий Савченко, Королевство Delphi 

При работе над одним проектом мне понадобилось в заголовке компонента TListView отображать стрелочку, указывающую направление сортировки. Стандартный компонент такой возможности не предоставлял. Первым делом я обратился к поиску на Круглом столе, но не смог найти там подсказки, как такое реализовать. Многочисленные поиски в Сети позволили найти только один бесплатный потомок TListView с исходниками, умеющий в числе прочего и рисовать нужную стрелку (Об этом компоненте будет упомянуто чуть ниже). "Подсмотрев" реализацию прорисовки в этом компоненте я и написал свой TatwListView.

Использование компонента

Итак, TatwListView - прямой потомок класса TListView, в котором добавлено лишь одно опубликованное свойство:

{тип стрелки вверх-вниз}

TArrowType = (atUp, atDown);

{тип нового свойства компонента}
TArrowOptions = record
  SortColumnIndex: Integer;
  ArrowType: TArrowType;
end;

{само свойство}
property ArrowOptions: TArrowOptions;

Здесь, думаю, все понятно. Программист изменяет это свойство в любом месте кода - и нужная стрелочка отображается в нужной колонке. Если ArrowOptions.SortColumnIndex имеет отрицательное значение, то стрелка не отображается вообще. ArrowOptions.ArrowType имеет по умолчанию значение atUp.

Как это было сделано

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

Но вначале следует заметить, что, начиная с Windows XP, ОС сама умеет рисовать стрелочки. Поэтому для XP и выше достаточно только указать системе, где ее рисовать. А вот для более старых версий Windows нарисовать стрелочку придется самим. Многие, возможно, скажут, что тогда проще для любой версии ОС включая XP и Vista осуществлять собственную прорисовку, но у меня есть на это пара возражений:

  1. Компонент TListView является надстройкой над стандартным объектом Windows. Поэтому весь функционал (и даже больше, как и в нашем случае), заложен в операционной системе, которая все же лучше знает как и где рисовать ту или иную часть объекта. Не очень рационально, по-моему учить систему делать то, что она и так умеет.
  2. Начиная с XP, Windows можно украсить (хотя, может наоборот, изуродовать) кучей разнообразных стилей, и никто не может знать, как в таком-то стиле будет выглядеть наша собственная стрелка. А в Vista эта стрелка вообще рисуется не справа от текста, а сверху, точно посередине колонки. А ведь мы хотим, чтобы было "также как в Проводнике".

Поэтому, компонент содержит два приватных метода SetColumnArrow и SetColumnArrowNonXP, которые и отвечают за установку стрелки. Вот их код:

procedure TatwListView.SetColumnArrow(ColumnIndex: Integer);
var
 HeaderHndl: HWND;
 Item: THDItem;
begin
 {получаем дескриптор заголовка нашего ListView'а}
 HeaderHndl := ListView_GetHeader(Handle);
 FillChar(Item, SizeOf(Item), 0);
 {значение этого оператора см. в справке от Microsoft}

 Item.Mask := HDI_FORMAT;
 {получаем информацию об указанной колонке в заголовке для дальнейшего ее изменения}
 Header_GetItem(HeaderHndl, ColumnIndex, Item);
 if (ColumnIndex = FArrowOptions.SortColumnIndex) then
 begin
  {предварительно убираем обе стрелки из флагов параметра fmt}

  Item.fmt:= Item.fmt and not (HDF_SORTDOWN or HDF_SORTUP);
  {указываем системе прорисовывать необходимую стрелку добавлением соотв. флага}
  if FArrowOptions.ArrowType = atUp then

   Item.fmt:= Item.fmt or HDF_SORTUP
  else
   Item.fmt:= Item.fmt or HDF_SORTDOWN;
 end
 {на этой колонке не надо рисовать стрелку - сообщаем об этом системе}

 else
  Item.fmt := Item.fmt and not({HDF_BITMAP or HDF_BITMAP_ON_RIGHT or} HDF_SORTDOWN 
  or HDF_SORTUP);

 {применяем установленные атрибуты - система сама нарисует колонку}  
 Header_SetItem(HeaderHndl, ColumnIndex, Item);

end;

procedure TatwListView.SetColumnArrowNonXP(ColumnIndex: Integer);
var
 hdr: HWND;
 Item: THDItem;
begin
 {здесь все так же как и в SetColumnArrow - см. выше}
 hdr := Listview_GetHeader(Handle);
 FillChar(Item, sizeof(Item), 0);
 Item.Mask := HDI_FORMAT;
 Header_GetItem(hdr, ColumnIndex, Item);
 if (ColumnIndex = FArrowOptions.SortColumnIndex) then

 begin
  {рисуем битмап - этот код прорисовки был заимствован из TListViewEx}
  Item.Mask := Item.Mask or HDI_BITMAP;
  Item.fmt  := Item.fmt  or HDF_BITMAP or HDF_BITMAP_ON_RIGHT;
  if FArrowOptions.ArrowType = atUp then

   Item.hbm:= FUpArrow.Handle
  else
   Item.hbm := FDownArrow.Handle;
 end
 else
  Item.fmt := Item.fmt and not(HDF_BITMAP or HDF_BITMAP_ON_RIGHT);
 Header_SetItem(hdr, ColumnIndex, Item);

end;

Где и как вызываются эти методы смотрите в исходниках.

Использованные материалы

Компонент TListViewEx (http://home.hccnet.nl/p.zylstra). Я частично использовал код этого компонента. В Copyright части модуля ListViewEx.pas указано, что этот компонент бесплатен и может быть использован в коммерческих/некоммерческих целях.


Страница сайта http://www.interface.ru
Оригинал находится по адресу http://www.interface.ru/home.asp?artId=21055