Сложные заголовки
А теперь снова вернемся к заголовкам и пойдем по дорожке, только что проложенной в самом начале статьи. Если мы умеем рисовать в заголовках, то мы можем очень многое, практически все :о)
На рис.2 изображен грид со сложными заголовками. Разберем один из возможных способов достижения такого результата.
Сложные заголовки
Изначально наш грид выглядит вот так:
Для того, чтобы добавить объединяющие заголовки для существующих, совершенно явно следует увеличить по высоте область заголовков грида.
И в нужном месте дорисовать самим объединяющую часть заголовка.
Реализация описанной методики в нашем наследнике TexDBGrid:
Введем свойство, которое будет включать/выключать режим сложных заголовков.
TexDBGrid = class(TDBGrid) private FSubHeader : Boolean; // подзаголовки ... published Property SubHeader : Boolean read FSubHeader write SetSubHeader;
Именно это свойство будет регулировать высоту области заголовков.
... Const TITLE_SUBHEADER = 2; TITLE_DEFAULT = 1; ... //******************************************************* procedure TexDBGrid.CalcTitle; begin RowHeights[0] := 19 * FTitleLines ; end; //******************************************************* procedure TexDBGrid.SetSubHeader(const Value: Boolean); begin FSubHeader := Value;
IF FSubHeader Then FTitleLines:=TITLE_SUBHEADER Else FTitleLines:=TITLE_DEFAULT; CalcTitle; end;
В метод TexDBGrid.DrawCell добавляем обработку
IF FSubHeader Then Begin // Рисуем объединяющий заголовок Header к мелким заголовкам Title DrawSubHeader(DataCol, Canvas);
// Рисуем заголовки Title FRect:=ARect; FRect.Top:=RectHeight(ARect) div FTitleLines;
DrawTitleCell(FRect,Columns[DataCol]); End Else DrawTitleCell(FRect,Columns[DataCol]);
Здесь рисование заголовка разбито на две процедуры: DrawSubHeader и DrawTitleCell. Где DrawTitleCell рисует в прямоугольнике 3D-окантовку, заливает его цветом FixedCols и вписывает текст. То есть имитирует обычный заголовок колонки. А вот на процедуре DrawSubHeader остановимся поподробнее.
Для того, чтобы нарисовать объединяющий заголовок для нескольких колонок, нужно получить прямоугольник (TRect), который объединяет эти колонки и текст, который следует писать в объединяющем заголовке. Для обеспечения гибкой настройки создадим два свойства:
published Property OnGetHeaderText : TOnGetHeaderText read FOnGetHeaderText write FOnGetHeaderText; Property OnGetHeaderRect : TOnGetHeaderRect read FOnGetHeaderRect write FOnGetHeaderRect;
С помощью этих свойств можно будет настраивать обработчики соответствующих событий.
Procedure DrawSubHeader(ACol : Integer; Canvas : TCanvas); Var HRect : TRect; Begin // Получаем прямоугольник, объединяющий несколько колонок, // для которых рисуем сложный заголовок HRect:=GetHeaderRect(ACol); // По высоте берем только часть прямоугольника // так как вторая часть - обычный заголовок HRect.Bottom:=RectHeight(HRect) div TITLE_SUBHEADER; Canvas.FillRect(HRect);
// Вписываем текст, // который получаем методом GetHeaderText InflateRect(HRect,-1,-1); WriteText(Canvas, HRect, GetHeaderText(ACol) , taCenter);
// Рисуем 3D-окантовку Paint3dRect(Canvas.Handle,HRect); End;
Внутри методов GetHeaderRect и GetHeaderText будут вызываться обработчики событий FOnGetHeaderRect и FOnGetHeaderText.
При этом, следует помнить, что в каждый момент могут быть видны не все колонки из объединенных в блок. Воспользуемся функцией TCustomDBGrid.CalcTitleRec, которая возвращает прямоугольник для определенной колонки и строки. Если в данный момент эта колонка не видна, то будет возвращен нулевой прямоугольник.
Function TexDBGrid.GetHeaderRect(ACol : Integer) : TRect; Var MasterCol : TColumn; Index,Shift , Count,i : Integer; Begin // Если в опциях отключен показ сетки, это нужно учесть при расчете // общего прямоугольника IF [dgColLines] * Options = [dgColLines] Then Shift:=1 Else Shift:=0;
Index:=ACol; Count:=1; // получаем информацию для текущей колонки грида: // в какой объединяющий блок она входит // Index - с какой колонки начинается объединяющий блок // Count - сколько колонок он включает IF Assigned(FOnGetHeaderRect) Then FOnGetHeaderRect(ACol, Index, Count);
IF Index+Count-1 > Columns.Count-1 Then Begin Index:=ACol; Count:=1; End;
// В результате нужно получить прямоугольник, состоящий из // всех, включенных в объединенный блок колонок Result:=CalcTitleRect(Columns[Index],0,MasterCol);
For i:=Index+1 To Index + Count -1 Do Result.Right:=Result.Right + RectWidth(CalcTitleRect(Columns[i] ,0,MasterCol)) + Shift;
End;
И для примера покажем, как именно могут использоваться обработчики событий получения объединяющего прямоугольника и текста при использовании сложных заголовков:
Const GeoColumns = 3; ParamColumns = 2; ... //---------------------------------------------------------------------------------------- // Получить для текущей колонки информацию о том, в какое объеденение колонок она попадает //---------------------------------------------------------------------------------------- procedure TfExDBG.GetHeaderRect(ACol: Integer; var IndexStart, Count: Integer); begin IF ACol < GeoColumns Then Begin IndexStart:=0; Count:=GeoColumns; End Else Begin IndexStart:=GeoColumns; Count:=ParamColumns; End end; //---------------------------------------------------------------------------------------- // Получить для текущей колонки текст заголовка объеденени //---------------------------------------------------------------------------------------- procedure TfExDBG.GetHeaderText(ACol: Integer; var Text: String); begin IF ACol < GeoColumns Then Text:='География' Else Text:='Параметры'; end; //----------------------------------------------------------------------------------------
Предложенный способ просто один из возможных, он не позволяет настраивать параметры объединяющих заголовков в design-time, рассчитан на использование двухуровневых заголовков и предполагает наличие сложных заголовков у всех колонок грида.
Например, для того, чтобы сделать так, как показано на рисунке ниже, следует свойство SubHeader привязывать не ко всему гриду, а к каждой его колонке.
|
Рассказать о реализации всех вариантов сложных заголовков не представляется возможным. Изучив наши примеры, Вы можете сами совершенствовать новый грид, по собственному усмотрению.
Запрет перемещения колонок с разрешением менять их ширину
В случае использования сложных заголовков не следует забывать о том, что необходимо контролировать стандартную работу грида с колонками. Например, совершенно естественно, что колонки, которые входят в объединенный блок, не должны передвигаться за его пределы.
В опциях грида объединены запрет/разрешение на передвижение колонок и на изменение их ширины (dbColumnResize). Если запретить перемещать колонки, тогда нельзя будет менять их ширину. В нашем случае это неудачное сочетание будет крайне неудобно с точки зрения пользователя. Введем еще одно поле, которое будет отдельно запрещать перемещение колонок:
TexDBGrid = class(TDBGrid) private ... FAllowColumnMoved: Boolean; ... public Property AllowColumnMoved : Boolean read FAllowColumnMoved write SetAllowColumnMoved;
Изучив исходные коды DBGrids.pas, обратим внимание на метод BeginColumnDrag (см. help). Этот метод вызывается тогда, когда начинается перетаскивание колонок.
Переопределим его в нашем наследнике:
function TexDBGrid.BeginColumnDrag(var Origin, Destination: Integer; const MousePt: TPoint): Boolean; Begin Result:=FAllowColumnMoved;
// Разрешить передвигать колонки только если это разрешено в настройках: AllowColumnMoved IF Result Then Result:= Inherited BeginColumnDrag(Origin,Destination,MousePt); End;
Так как мы контролируем непосредственно начало процесса перемещения, то возможность менять ширину колонок остается у пользователя.
1 2 3 4 5 6
8 8 8
| |