Имитация внутренних группировок и метки колонок
Работая с заголовками мы не один раз их перерисовывали, вписывая текст и добавляя 3D-окантовку. Это умение можно использовать в любом месте сетки грида, а не только в заголовках.
Рисуем ячейку в стиле заголовка в любом месте TDBGrid
Добавим нашему гриду еще один метод - DrawCellButton, который будет рисовать в любой ячейке 3D-окантовку, то есть делать имитацию заголовка. Передавать в нее будем прямоугольник этой ячейки, текст, выравнивание текста, шрифт, которым текст будет выведен и состояние (State) грида. Состояние нам понадобится для нормальной работы с фиксированными колонками.
procedure TexDBGrid.DrawCellButton(Rect: TRect; Text: String; Style: TFontStyles; State: TGridDrawState; Alignment: TAlignment); Var Shift : Integer; begin //Очищаем ячейку Canvas.Brush.Color:=clBtnFace; Canvas.Font.Color:=clBtnText; Canvas.Font.Style:=Style; Canvas.FillRect(Rect);
// Если ячейка фиксирована, то мы получим TRect меньшего размера, // чем для обычной ячейки. Это нужно учесть Shift:=-2 + ORD(gdFixed In State);
// вписываем текст InflateRect(Rect,Shift,0); WriteText(Canvas, Rect, Text , Alignment ); InflateRect(Rect,(-1)*Shift,0);
// рисуем по размеру ячейки button // только если это не фиксированная ячейка, так как для нее окантовка уже нарисована IF NOT (gdFixed in State) Then Begin // Рисуем аналог разделительных линий между фиксированными ячейками грида // (они рисуются черным цветом, в отличие от серых линий между ячейками // данных (grids.pas)) InflateRect(Rect, 1, 1); Rect.Top:=Rect.Top + 1; FrameRect(Canvas.Handle, Rect, GetStockObject(BLACK_BRUSH));
Rect.Top:=Rect.Top - 1; // Закончили имитацию линий между фиксированными ячейками. InflateRect(Rect, -2, -2); Paint3dRect(Canvas.Handle, Rect); End;
end;
Такой, на первый взгляд экзотический, вариант ячейки поможет нам создать видимость внутренних группировок в гриде (рис. 4).
Имитация внутренних группировок
Для создания внутренних группировок необходимо подготовить не только TDBGrid, но и набор данных, которые он будет отображать. Ведь TDBGrid не умеет показывать строк, которых нет в его источнике данных (TDataSource). Подготовим данные по такому запросу: выберем всю информацию по странам и добавим список континентов с суммами полей "население" и "площадь". Обычный UNION-запрос:
Select 1 as TypeRecord , Continent , Name, Area , Population From country Union Select 0 as TypeRecord,Continent ,Continent as Name, Sum(Area) as Area , Sum(Population) as Population From country Group By Continent Order by 2,1
Итак, мы получим для каждого континента список его стран и еще одну запись, которую мы будем использовать как служебную запись для группировки, суммы по континенту эта строка уже содержит. Идентифицировать служебную запись можно по значению служебного поля TypeRecord (именно для этого оно и введено). Добавим в обработку события OnDrawColumnCell рисование группировочной строки:
procedure TfExDBG.__GridFixDrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); Var Alignment : TAlignment; begin // рисуем на строке итогов имитацию заголовка грида IF Column.Field.DataSet.FieldByName('TypeRecord').AsInteger = 0 Then TexDBGrid(Sender).DrawCellButton(Rect,Column.Field.DisplayText,[fsBold],State,Alignment) end;
Вуаля! :о)
А вот еще один вариант группировок - без итогов по каждой колонке, только отделение групп данных друг от друга (рис. 5).
Для его реализации добавим метод, аналогичный DrawCellButton, вернее создадим новый на его основе. Метод DrawRowButton делает тоже самое, что и DrawCellButton, но только всегда растягивает картинку на всю видимую строку грида.
procedure TexDBGrid.DrawRowButton(Rect: TRect; Text: String; Style: TFontStyles; Alignment: TAlignment); Var FullRect : TRect; Col : TColumn; begin FullRect:=Rect; FullRect.Left:=IndicatorWidth + 1; FullRect.Right:=CalcTitleRect(Columns[Columns.Count-1],0,Col).Right;
DrawCellButton(FullRect,Text,Style,[],Alignment); end;
Метки колонок: рисуем в заголовке TCheckBox или TRadioButton
Вновь вернемся к заголовкам. Допустим нам надо реализовать возможность как-то отметить колонку. В принципе для таких целей может служить два контрола TCheckBox и TRadioButton. Для рисования в заголовках воспользуемся специальным событием нашего нового грида: OnDrawTitleRect
procedure TfExDBG.OnDrawTitleRect(Sender: TObject; ACol: Integer; Column: TColumn; ARect: TRect); Var Style, TypeButton : Word; FRect : TRect; begin IF ACol >= TexDBGrid(Sender).FixedCols Then Begin InflateRect(ARect, -1, -1);
TDBGrid(Sender).Canvas.FillRect(ARect);
// Ширина прямоугольника для рисования контрола - 20 пикселей FRect:=ARect; IF RectWidth(FRect) > 20 Then FRect.Right:=FRect.Left + 20;
// Определяем отмечено или нет текущее поле IF Column.Field.Tag = 1 Then Style:=DFCS_CHECKED Else Style:=0;
// Выбираем тип контрола для отметки колонки IF FTitleIsCheckBox Then TypeButton:=DFCS_BUTTONCHECK Else TypeButton:=DFCS_BUTTONRADIO;
// Рисуем отметку DrawFrameControl(TDBGrid(Sender).Canvas.Handle, FRect, DFC_BUTTON, TypeButton OR Style);
FRect.Left:=FRect.Right + 1; FRect.Right:=ARect.Right;
// Текст заголовка WriteText(TDBGrid(Sender).Canvas,FRect,Column.Title.Caption,Column.Title.Alignment); End; end;
Обработку нажатия на метку колонки проводим в обработчике события OnMouseUp. В приведенном примере для хранения отметки столбца используется свойство TField.Tag. Естественно, это только один из возможных вариантов.
procedure TfExDBG.GridFixMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); Const MinX = 2; MaxX = 20; Var Row, Col , i : Integer; Grid : TexDBGrid; Begin Grid:=TexDBGrid(Sender);
// Получим номер строки и столбца грида, над которыми произошел клик мышкой Grid.MouseToCell(X,Y,Col,Row);
IF Button = mbLeft Then Begin // Левая кнопка мыши - проверяем попадание в заголовок // и обязательное попадание на сам крыжик IF (Row = 0) AND (Col > Grid.FixedCols ) AND (Grid.Columns[Col - 1].Field <> nil) Then Begin Dec(X, Grid.TitleRect(Col-1).Left);
// Проверяем попадание в область крыжика IF (X > MinX) and (X < MaxX) Then Begin Tag:=Grid.Columns[Col - 1].Field.Tag;
// Снимаем отметку со всех колонок (если это TRadioButton) IF NOT FTitleIsCheckBox Then For i:=0 To Grid.Columns.Count - 1 Do Grid.Columns[i].Field.Tag:=0;
// И отмечаем текущую Grid.Columns[Col - 1].Field.Tag:=1 - Tag;
// Перерисовываем только заголовки, а не весь грид Grid.RefreshTitles; RefreshSelect; End; End; End; End;
Использование фиксированных колонок
И последнее, что мы сотворим с нашим гридом :о), это снабдим его свойством FixedCols, которого так не хватает в стандартном TDBGrid'е.
Для тех, кто может быть не знает, отметим, что у стандартного TDBGrid есть фиксированный столбец, он используется гридом для внутренних нужд. Это тот самый индикатор слева, в котором рисуется треугольник, указывая на текущую строку. Добавляя свое свойство FixedCols, необходимо это учитывать
TexDBGrid = class(TDBGrid) private ... FFixedCols : Integer; ... public Property FixedCols : Integer read GetFixedCols write SetFixedCols; ...
//************************************************************************************************** procedure TexDBGrid.SetFixedCols(const Value: Integer); Var FixedCount,i : Integer; begin
// Следует учесть индикатор грида IF Value <= 0 Then FixedCount:=IndicatorOffset Else FixedCount := Value + IndicatorOffset;
IF DataLink.Active AND NOT (csDesigning in ComponentState) AND (ColCount > IndicatorOffset + 1) Then Begin IF FixedCount >= ColCount Then FixedCount:=ColCount - 1;
Inherited FixedCols := FixedCount;
// На фиксированных колонках нельзя останавливаться по табуляции For i := 1 To FixedCols Do TabStops[I] := False; End;
FFixedCols := FixedCount - IndicatorOffset; end; //************************************************************************************************** function TexDBGrid.GetFixedCols: Integer; begin IF DataLink.Active Then Result := Inherited FixedCols - IndicatorOffset Else Result := FFixedCols; end; //**************************************************************************************************
Необходимо восстанавливать данные о фиксированных колонках каждый раз, когда параметры колонок будут пересчитываться. Смотрите в иллюстрирующем проекте процедуры TexDBGrid.LayoutChanged; и TexDBGrid.SetColumnAttributes.
Для того, чтобы в нашем гриде фиксированные колонки вели себя также, как ведут они себя, например, в TStringGrid, нужно обработать реакцию на мышь и клавиатуру.
//************************************************************************************************** Procedure TexDBGrid.KeyDown(var Key: Word; Shift: TShiftState); Var KeyDownEvent: TKeyEvent; Begin KeyDownEvent := OnKeyDown; IF Assigned(KeyDownEvent) Then KeyDownEvent(Self, Key, Shift);
IF NOT Datalink.Active OR NOT CanGridAcceptKey(Key, Shift) Then Exit;
// наша задача - не пустить в область фиксированных колонок, // то есть SelectedIndex не может быть меньше, чем FFixedCols IF ssCtrl IN Shift Then Begin IF (Key = VK_LEFT) AND (FixedCols > 0) Then Begin SelectedIndex := FixedCols; Exit; End; End Else Case Key Of VK_LEFT: IF (FixedCols > 0) AND NOT (dgRowSelect in Options) Then IF SelectedIndex <= FFixedCols Then Exit;
VK_HOME: IF (FixedCols > 0) AND (ColCount <> IndicatorOffset + 1) AND NOT (dgRowSelect IN Options) Then Begin SelectedIndex := FixedCols; Exit; End; End;
OnKeyDown := Nil; Try Inherited KeyDown(Key, Shift); Finally OnKeyDown := KeyDownEvent; End;
end; //************************************************************************************************** procedure TexDBGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); Var Cell : TGridCoord; begin
Cell:=MouseCoord(X,Y);
//При скроллировании данных фиксированные колонки должны оставаться на месте IF (Cell.X >= 0) AND (Cell.X < FixedCols + IndicatorOffset) AND Datalink.Active Then Begin IF (dgIndicator IN Options) Then Inherited MouseDown(Button, Shift, 1, Y) Else IF (Cell.Y >= 1) AND (Cell.Y - Row <> 0) Then Datalink.Dataset.MoveBy(Cell.Y - Row); End Else inherited MouseDown(Button, Shift, X, Y);
end; //**************************************************************************************************
Вот, собственно и все, что мы хотели рассказать.
Игорь Шевченко, Елена Филиппова, Королевство Дельфи 1 2 3 4 5 6
8 8 8
| |