Рисование многострочных заголовков с использованием стандартного компонента TDBGrid.
При использовании стандартного компонента TDBGrid для рисования доступна только область данных колонок, изначально не включающая в себя фиксированные области TDBGrid, рисующиеся самим компонентом. Зная тот факт, что при событиях рисования доступна вся клиентская область окна, можно попробовать обмануть компонент и рисовать в другой области, чем та, которая передается процедуре рисования. Так как событие OnDrawCell вызывается для каждой ячейки Grid'а, а заголовки желательно рисовать один раз, заводим массив признаков нарисованных заголовков: GridTitles : : array of Boolean; Обработчик события OnDrawColumnCell выглядит достаточно просто:
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin if not GridTitles[Column.Index] then DrawGridTitle(Column.Index); end; |
Если заголовок колонки не нарисован, то нарисовать его. Процедура рисования должна определить координаты области заголовка и ее размеры и заново перерисовать эту область. Сама процедура оформлена как локальная, для того, чтобы не передавать параметры, переданные обработчику события. Для простоты заголовок делается двухстрочным, но ничего не мешает рисовать произвольное количество строк. RowCount объявлено константой и равно 2.
procedure DrawGridTitle(ColIndex : Integer); var Titles : array[1..RowCount] of String; ARect : TRect; { Собственно область заголовка }
RH : Integer; { Высота области заголовка }
BlankPos : Integer; { Позиция разбиения заголовка }
begin BlankPos := Pos(' ', Column.Title.Caption); if BlankPos <> 0 then begin { Рисуем многострочный заголовок только для тех колонок, у которых есть пробел в названии. Заголовки остальных колонки DBGrid нарисует сам. }
Titles[1] := Copy(Column.Title.Caption, 1, BlankPos-1); Titles[2] := Copy(Column.Title.Caption, BlankPos+1, Length(Column.Title.Caption) - BlankPos); RH := RectHeight(Rect); { В прямоугольнике Rect передаются координаты текущей ячейки, область для рисования заголовка можно получить, указывая в качестве вертикальной координаты 0. Высота области рисования сейчас равна высоте стандартной ячейки DBGrid, как раз на одну строку заголовка. }
SetRect(ARect, Rect.Left, 0, Rect.Right, RH); InflateRect(ARect, -2, -2); { Поправка на окантовку Titles } Dec(RH, 2); { Смещение для отступа текста от края по вертикали } with DBGrid1.Canvas do begin Brush.Color := DBGrid1.FixedColor; FillRect(ARect); { Залить область заголовка, стерев все, что там нарисовано DBGrid'ом } { Рисование первой строки в заголовке } ARect.Bottom := RH; DrawText(Handle, PChar(Titles[1]), -1, ARect, DT_CENTER or DT_SINGLELINE); { Рисование второй строки в заголовке, предварительно сместив область рисования вниз на размер строки. } OffsetRect(ARect, 0, RH-2); DrawText(Handle, PChar(Titles[2]), -1, ARect,DT_CENTER or DT_SINGLELINE); end; end; GridTitles[ColIndex] := true; //Нарисовали заголовок для этой колонки end; |
Высота любой строки любого наследника TCustomGrid определяется свойством RowHeights[номер строки]. Так как это свойство объявлено protected, для того, чтобы высота области заголовков DBGrid'а была большая, чем стандартная, используется обычный прием доступа к защищенным свойствам компонента, с описанием наследника от требуемого класса и повышением области видимости требуемого свойства: type THackGrid = class(TCustomGrid) public property RowHeights; end; Высоту области надо задать один раз, что и делается в обработчике события FormShow
procedure TForm1.FormShow(Sender: TObject); var .... H : Integer; { Определение необходимой высоты строки для многострочных заголовков } H := DbGrid1.Canvas.TextHeight('gW'); THackGrid(DBGrid1).RowHeights[0] := (H + 2) * RowCount; { RowCount принудительно объявлено 2 } end; |
рис. 1
После первого запуска программы обнаружен интересный эффект - при переключении на другое окно и обратном переключении на окно с Grid'ом многострочность заголовков пропадает. Аналогичным образом она пропадает при перемещении по гриду с помощью вертикального и горизонтального ScrollBar'ов. Для события переключения окна положение можно исправить, указав необходимость перерисовки заголовков в событии FormActivate, со ScrollBar'ами бороться придется подменой оконной процедуры DBGrid'а. Сделаем метод формы, сбрасывающий признаки рисования у всех заголовков:
procedure TForm1.InvalidateGridTitles; var I : Integer; begin for I:=0 to Pred(DBGrid1.Columns.Count) do GridTitles[I] := false; end; |
procedure TForm1.GridWndProc(var Message: TMessage); begin case Message.Msg of WM_ERASEBKGND, WM_VSCROLL: InvalidateGridTitles(); WM_HSCROLL: begin InvalidateGridTitles(); // сожалению, приходится мириться с необходимостью перерисовки всего // DBGrid'а при горизонтальном скроллинге, иначе, все усилия по рисованию // многострочных заголовков пропадают :-( InvalidateRect(GridWnd, nil, true); end; end; with Message do Result := CallWindowProc(OldWndProc, GridWnd, Msg, wParam, lParam); end; |
В первом варианте при обработке собщения WM_HSCROLL не был написан код для перерисовки всего окна DBGrid. Как я ни старался, победить ситуацию пропадания многострочных заголовков мне не удалось, поэтому и был добавлен код, принудительно перерисовывающий все окна DBGrid.