Связь и интернет Архив Программирование
   
Сделать стартовойСделать закладку            
   ПОИСК  
   
Главная / Pascal и Delphi /
8  Perl
8  PHP
8  JavaScript
8  HTML
8  DHTML
8  XML
8  CSS
8  C / C++
8  Pascal и Delphi
8  Турбо Ассемблер
8  MySQL
8  CASE-технологии
8  Алгоритмы
8  Python
8  Обратная связь
8  Гостевая книга
Новости о мире


НеОбычный TDBGrid - Программирование от RIN.RU
НеОбычный TDBGrid



Многострочные заголовки


Рисование многострочных заголовков с использованием стандартного компонента 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;




Результат работы:





После первого запуска программы обнаружен интересный эффект - при переключении на другое окно и обратном переключении на окно с 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.FormActivate(Sender: TObject);
begin
InvalidateGridTitles();
end;




И в подмененной оконной процедуре DBGrid'а:



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.


Рисование многострочных заголовков с использованием наследника компонента TDBGrid.


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


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



procedure THSDBGrid.DrawCell(ACol, ARow: Integer; ARect: TRect;
State: TGridDrawState);
var
TitleText : String; { Полный заголовок }
Titles : array of String; { Части заголовка }


{ Разбиение полного заголовка на части с возвращением числа получившихся
частей }
function SplitTitle : Integer;
const
TitleSeparator = ' '; { Можно этот символ вынести в published property }
var
CurPos, J: Integer;
CurStr: string;
begin
SetLength(Titles, FTitleLines);
{ Определяем, сколько реально строк присутсвует в заголовке.
Просто считается количество символов TitleSeparator }
J := 0;
CurStr:= TitleText;
repeat
CurPos:= Pos(TitleSeparator, CurStr);
if (CurPos > 0) and (J < Pred(FTitleLines)) then begin
Titles[J] := Copy(CurStr, 1, Pred(CurPos));
CurStr:= Copy(CurStr, CurPos+Length(TitleSeparator),
Length(CurStr)-CurPos-Length(TitleSeparator)+1);
Inc(J);
end else begin
Titles[J] := CurStr;
if J >= Pred(FTitleLines) then { Не надо копировать больше, чем может
вместить заголовок }
Break;
end;
until CurPos=0;
Result := J+1;
end;


var
DataCol, I, TitleParts : Integer;
TextRect : TRect;
LineHeight : Integer;
begin
if (dgTitles in Options) AND (gdFixed in State) AND (ARow = 0) AND
(ACol <> 0) then begin
{ Должна быть нарисована ячейка заголовка }
{ Стандартное действие DBGrid }
if csLoading in ComponentState then begin
Canvas.Brush.Color := Color;
Canvas.FillRect(ARect);
Exit;
end;
DataCol := ACol;
if dgIndicator in Options then
Dec(DataCol);
{ Изменение размеров области заголовка под окантовку, если хочется сделать
плоские заголовки, то InflateRect надо пропустить }
if [dgRowLines, dgColLines] * Options = [dgRowLines, dgColLines] then
InflateRect(ARect, -1, -1);
TitleText := Columns[DataCol].Title.Caption;
Canvas.Brush.Color := FixedColor;
{ Если захочется сделать прозрачный заголовок, то вызов FillRect надо будет
пропустить }
{ Если будет желание рисовать фоновую картинку в области заголовка, то
нарисовать ее можно здесь }
Canvas.FillRect(ARect);
{ Теперь можно нарисовать собственно текст }
Canvas.Font := Font;
if FTitleLines = 1 then begin
WriteText (Canvas, ARect, 1, 1, TitleText,
Columns[DataCol].Title.Alignment);
end else begin
TitleParts := SplitTitle();
TextRect := ARect;
LineHeight := RectHeight(ARect) DIV TitleParts;
TextRect.Bottom := TextRect.Top + LineHeight;
for I:=0 to Pred(TitleParts) do begin
WriteText (Canvas, TextRect, 1, 0, Titles[I],
Columns[DataCol].Title.Alignment);
OffsetRect(TextRect, 0, LineHeight);
end;
end;
{ Окантовка ячейки заголовка, если хочется сделать плоские заголовки,
то DrawEdge надо пропустить }
if [dgRowLines, dgColLines] * Options = [dgRowLines, dgColLines] then begin
InflateRect(ARect, 1, 1);
DrawEdge(Canvas.Handle, ARect, BDR_RAISEDINNER, BF_BOTTOMRIGHT);
DrawEdge(Canvas.Handle, ARect, BDR_RAISEDINNER, BF_TOPLEFT);
end;
DoDrawTitleCell (DataCol, Columns[DataCol], ARect);
end else
inherited;
end;




Кроме того, появляется возможность вызывать пользовательское событие при рисовании области заголовков, причем после того, как заголовок уже нарисован самим компонентом.


Задание высоты заголовков в наследнике также выполняется проще, так как имеется доступ к защищенным свойствам родительского компонента.



procedure THSDBGrid.CalcTitleHeight;
begin
if dgTitles in Options then
RowHeights[0] := (Canvas.TextHeight('gW') + 2) * FTitleLines;
end;




Высоту области заголовка необходимо задавать один раз при создании окна грида и каждый раз, при изменении свойств грида, влияющих на его внешний вид. При создании окна и при изменении свойств грида вызываются виртуальные методы CreateWnd и LayoutChanged, в перекрытые версии которых добавлен вызов процедуры CalcTitleHeight.


<<<  НазадВперед  >>>
 1  2  3  4  5  6 


 8  Комментарии к статье  8 8  Обсудить в чате

 
  
  
    Copyright ©  RIN 2003 - 2004      * Обратная связь