Синхронизация гридов
Синхронизация размеров и положения колонок двух гридов
Задача состоит в том, чтобы заставить два TDBGrid, расположенных один под другим, полностью синхронизировать свою работу с колонками: изменение размеров колонок и их перемещение должно происходить в обоих гридах отдновременно. Самое распространенное применение этой задачи в отображении грида с данными и грида с итогами (см. рис. 3). Верхний грид содержит список всех стран с данными по площади и населению(MainGrid), нижний - список, где эти же данные сгруппированы по континентам(TotalGrid).
При синхронизации действий будем считать, что тот грид, который инициирует это действие - ведущий, а второй в этой ситуации - ведомый. Чтобы не зациклить синхронизацию, введем дополнительную переменную:
SynchProccesed : Boolean;
Для синхронизации необходимо обработать три события:
Изменение позиции колонки;
Горизонтальный скролинг(изменение колонки, которая оказывается первой видимой в гриде);
Изменение ширины колонки.
Для отслеживания перемещения колонок воспользуемся событием OnColumnMoved. Синхронизацию проведем незатейливо: полностью перепишем колонки ведомого грида, взяв за основу колонки ведущего:
procedure TfExDBG.mainGridColumnMoved(Sender: TObject; FromIndex, ToIndex: Integer); Var Grid : TDBGrid; begin // TDBGrid(Sender) инициирует перемещение колонок, он - ведущий грид // определяем "ведомый" грид IF TDBGrid(Sender).Name = 'TotalGrid' Then Grid:=MainGrid Else Grid:=TotalGrid;
// Сейчас ведомому гриду не нужно реагировать на изменение его колонок, // инициируя в свою очередь синхронизацию с другим гридом SynchProccesed:=True;
Grid.Columns.Assign(TDBGrid(Sender).Columns);
// Синхронизация завершена SynchProccesed:=False;
end;
Для отслеживания горизонтального скролинга как нельзя лучше подходит метод TCustomDBGrid.TopLeftChanged. К сожалению, в стандартном TDBGrid этот метод не доступен (protected). Поэтому, лучшим вариантом будет не мучить стандартный грид, а создать собственного наследника. Положительные стороны этого способа уже описывались в начале статьи.
TexDBGrid = class(TDBGrid) private FOnTopLeftChanged : TNotifyEvent; ... public Procedure TopLeftChanged; override; ... published Property OnTopLeftChanged : TNotifyEvent read FOnTopLeftChanged write FOnTopLeftChanged; ... End; ... //-------------------------------------------------------------------------------------------------- Procedure TexDBGrid.TopLeftChanged; Begin Inherited; IF Assigned(FOnTopLeftChanged) then FOnTopLeftChanged(Self); End;
Теперь нам доступно событие OnTopLeftChanged. Синхронизация заключается в том, чтобы сделать первой видимой колонкой ведомого грида ту же колонку, что и у ведущего. Для этого нам понадобится свойство TCustomGrid.LeftCol (см. help). Это свойство protected, но так как мы создаем собственного наследника от TDBGrid, то повысить его видимость нам не составит труда.
procedure TfExDBG.GridTopLeftChanged(Sender: TObject); Var Grid : TexDBGrid; begin
IF NOT SynchProccesed Then Begin
// TDBGrid(Sender) инициирует скролинг, он - ведущий грид // определяем "ведомый" грид IF TDBGrid(Sender).Name = 'TotalGrid' Then Grid:=MainGrid Else Grid:=TotalGrid;
SynchProccesed:=True; Grid.LeftCol:=TexDBGrid(Sender).LeftCol; SynchProccesed:=False; End;
end;
И, наконец, третий пункт: отслеживаем изменение ширины колонки. Синхронизация в этом случае будет заключаться только в том, чтобы ширину колонок ведомого грида сделать равной ширине колонок ведущего.
Procedure TfExDBG.SynchronizeGrids( MasterGrid , SlaveGrid : TDBGrid ); Var i : Integer; Begin
IF NOT SynchProccesed Then Begin SynchProccesed:=True; For i:=0 To MasterGrid.Columns.Count - 1 Do SlaveGrid.Columns[i].Width:=MasterGrid.Columns[i].Width ; SynchProccesed:=False; End;
End;
А вот в какой момент применить этот метод? Ведь у грида нет события OnResizeColumn... Внимательно изучив help, обратим внимание на метод SetColumnAttributes:
Sets the column widths and disables tabbing to cells that can't be edited.
procedure SetColumnAttributes; virtual;
Description
Applications cannot call this protected method. It is called automatically when the Columns property is recomputed, to adjust the column widths and ensure that the user can only tab to fields that can be edited.
Этот метод автоматически вызывается всякий раз, когда изменяются настройки колонок, в том числе их ширина. Мы нашли то, что нам нужно! По аналогии с OnTopLeftChanged создадим в нашем гриде событие OnSetColumnAttr:
TexDBGrid = class(TDBGrid) private FOnTopLeftChanged, FOnSetColumnAttr : TNotifyEvent; ... protected Procedure SetColumnAttributes; override; public Procedure TopLeftChanged; override; ... published Property OnTopLeftChanged : TNotifyEvent read FOnTopLeftChanged write FOnTopLeftChanged; Property OnSetColumnAttr : TNotifyEvent read FOnSetColumnAttr write FOnSetColumnAttr; ... End; ...
procedure TexDBGrid.SetColumnAttributes; begin inherited; IF Assigned(FOnSetColumnAttr) Then FOnSetColumnAttr(Self); end;
Обработаем это событие для обоих гридов:
// Так как определять ведомый грид приходится не один раз, правильно выделить это в отдельный метод Function TfExDBG.GetSlaveGrid( MasterGrid : TexDBGrid) : TexDBGrid; Begin // MasterGrid инициирует синхронизацию, он - ведущий грид // определяем "ведомый" грид IF MasterGrid.Name = 'TotalGrid' Then Result:=MainGrid Else Result:=TotalGrid; End;
Procedure TfExDBG.OnSetColumnAttr(Sender: TObject); Begin IF NOT SynchProccesed Then SynchronizeGrids( TexDBGrid(Sender) ,GetSlaveGrid(TexDBGrid(Sender)) ); End;
Ну а теперь, пробуйте! :о)
Для того, чтобы расслабиться перед следующим "броском", пристроим к нашему гриду несколько простых, но приятных бантиков :о)
Вызываем разные меню для заголовков и области данных
В момент нажатия правой кнопки мыши нам доступны ее координаты относительно самого грида (так называемые клиентские координаты). Для того, чтобы понять, в какой области мы оказались (в области заголовка или данных), нам необходимо получить номер столбца и строки той ячейки, в которую мы попали. Для этого создадим соответствующий метод в нашем наследнике:
procedure TexDBGrid.MouseToCell(X, Y: Integer; var ACol, ARow: Integer); Var Coord: TGridCoord; Begin Coord := MouseCoord(X, Y); ACol := Coord.X; ARow := Coord.Y; End;
И теперь обработаем событие OnMouseUp:
procedure TfExDBG.GridMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); Var Row, Col : Integer; APoint : TPoint; Grid : TexDBGrid; begin Grid:=TexDBGrid(Sender);
// Получим номер строки и столбца грида, над которыми произошел клик мышкой Grid.MouseToCell(X,Y,Col,Row);
IF Button = mbRight Then // Если мышка не попала на незаполненную область грида IF (Col >= 0) AND (Row >=0 ) Then Begin // Нажатие правой кнопки мыши, проверяем какое меню требуется вызвать IF Row = 0 Then Grid.PopUpMenu:=pmTitle Else Grid.PopUpMenu:=pmData;
// Получаем из координат мыши(относительно грида - клиентские координаты) // экранные координаты для всплывающего меню APoint := Grid.ClientToScreen(Point(X,Y)); Grid.PopUpMenu.Popup(APoint.X,APoint.Y); End; end;
Выделение цветом текущей строки
При установке в опциях грида свойства dgRowSelect, текущая строка всегда выделяется полностью, но нельзя редактировать поля. Как выделить цветом строку при условии, что любой поле можно редактировать? Основной проблемой здесь является вопрос, как понять, что строка, которая рисуется и есть текущая. Смотрим свойство TDataLink.ActiveRecord
Specifies the index of the current record within the internal set of records buffer maintained by the dataset for the Owner of the TDataLink object.
property ActiveRecord: Integer;
Description
Use ActiveRecord to discover or set the current record in the set of one or more records managed by the dataset. The set of records managed by the dataset corresponds to the number of records from the dataset visible at one time. For example, when the TDataLink object is owned by a data-aware grid, the set of records managed by the dataset corresponds to the number of rows shown by the grid, and the ActiveRecord represents the current row.
Очень полезное свойство.
Property ActiveRecord : Integer read GetActiveRecord; ...
function TexDBGrid.GetActiveRecord: Integer; begin Result:=DataLink.ActiveRecord; end;
И внесем необходимые изменения в обработку рисования строк грида:
procedure TfExDBG.mainGridDrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin // Выделяем текущую строку IF TexDBGrid(Sender).ActiveRecord = TexDBGrid(Sender).Row-1 Then TDBGrid(Sender).Canvas.Brush.Color:=RGB($CC,$CC,$99);
IF (gdSelected IN State) Then Begin TDBGrid(Sender).Canvas.Brush.Color:= clHighLight; TDBGrid(Sender).Canvas.Font.Color := clHighLightText; End; // А теперь пусть он рисует сам ! TDBGrid(Sender).DefaultDrawColumnCell(Rect,DataCol,Column,State); end;
А вот еще один способ выделять строку как в RowSelect (из Demo к TDBGridEh Дмитрия Большакова):
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); var Grid : THSDBGrid; begin Grid := THSDBGrid(Sender); if (Rect.Top = Grid.CellRect(Grid.Col, Grid.Row).Top) and //В данном случае проверяется, что мы рисуем текущую строку (not (gdFocused in State) or not Grid.Focused) then //И фокус находится не на текущем столбце или фокус вообще не на гриде Grid.Canvas.Brush.Color := TColor($D86A10); Grid.DefaultDrawColumnCell(Rect,DataCol,Column,State); end;
Для этого надо в нашем наследнике объявить в секцию public процедуру CellRect, как
function CellRect(ACol, ARow: Longint): TRect;
а ее реализацию выполнить:
function TexDBGrid.CellRect(ACol, ARow: Integer): TRect; begin Result := inherited CellRect (ACol, ARow); end;
1 2 3 4 5 6
8 8 8
|