I need to write the code to select multiple rows on Shift+MouseClick, so that I am able to fill down the values. Is there any way I can do it? My DBGrid options are as follows:
dbGrid1.Options = [dgEditing, dgAlwaysShowEditor, dgTitles, dgColLines, dgRowLines, dgTabs, dgConfirmDelete, dgCancelOnExit, dgMultiSelect]
I originally thought that a problem you would be likely to have with this is that Shift + Click is handled by the grid itself, so the time the Grid's OnMouseUp fires, the grid has already moved the dataset cursor to the row that was shift-clicked. So extending the grid selection from where it was before the shift-click would not be straightforward, because you would lack the information of where the ds cursor previously was.
However, it turns out to be quite straightforward to do. I've added some comments to the code below to explain how it works.
For simplicity, I've implemented it using a ClientDataSet with an integer ID field. The point of using an ID field is that the code uses current and prior values of the ID, in a sort of hand-over-hand way as the dataset scrolls, to do its stuff. Doing similar using bookmarks would be messier because of the need to continually allocate and deallocate them, so I leave that as an exercise for the reader.
Code:
// Form variables
SettingBookmarkRange : Boolean;
CurrentRowID,
PreviousRowID: Integer;
procedure TForm1.CDS1AfterScroll(DataSet: TDataSet);
begin
if SettingBookmarkRange then exit;
PreviousRowID := CurrentRowID;
CurrentRowID := CDS1.FieldByName('ID').AsInteger;
Caption := Format('Current %d, prior %d', [CurrentRowID, PreviousRowID]);
end;
procedure TForm1.SetBookmarkRange;
var
BM,
TempBM : TBookmark;
NewBM : TBookmarkStr;
FoundPrevious : Boolean;
begin
// This code is called after the user does a Shift-Click in the grid
// First we set a flag to temporarily prevent the CurrrentRowID and
// PreviousrowID from being updated during the dataset's OnScroll event
SettingBookmarkRange := True;
BM := CDS1.GetBookmark;
// Set a flag to keep track of whether we've found the row with the PreviousRowID
FoundPrevious := False;
try
CDS1.DisableControls;
// First, search forwards to see if we can find the the row with the PreviousRowID
// In other words, we're first checking that the Shift-Click was on a row *above*
// the previous row
CDS1.Next;
while not FoundPrevious and not CDS1.Eof do begin
if CDS1.FieldByName('ID').AsInteger = PreviousRowID then begin
FoundPrevious := True;
// We found the row with the PreviousRowID, so now get the Grid to add it, and
// all the rows back to where we started, in its SelectedRows list
while not CDS1.Bof and (CDS1.FieldByName('ID').AsInteger <> CurrentRowID) do begin
DBGrid1.SelectedRows.CurrentRowSelected := True;
CDS1.Prior;
end;
end
else
CDS1.Next;
end;
if not FoundPrevious then begin
// If we get here, the Shift-Click must have been in a row further down the
// grid than the previously-current one
while not FoundPrevious and not CDS1.Bof do begin
if CDS1.FieldByName('ID').AsInteger = PreviousRowID then begin
FoundPrevious := True;
// We found the row with the PreviousRowID, so now get the Grid to add it, and
// all the rows back to where we started, in its SelectedRows list
while not CDS1.Eof and (CDS1.FieldByName('ID').AsInteger <> CurrentRowID) do begin
DBGrid1.SelectedRows.CurrentRowSelected := True;
CDS1.Next;
end;
end
else
CDS1.Prior;
end;
end;
finally
CDS1.GotoBookmark(BM);
CDS1.FreeBookmark(BM);
CDS1.EnableControls;
SettingBookmarkRange := False;
end;
end;
procedure TForm1.DBGrid1MouseUp(Sender: TObject; Button: TMouseButton; Shift:
TShiftState; X, Y: Integer);
begin
Caption := IntToStr(CDS1.Fields[0].Value);
if ssShift in Shift then
SetBookMarkRange;
end;
Default selection behavior for multiselect grids is Ctrl-Click, not Shift-Click. Using it doesn't require any specific mouse click handlers.
To implement Shift-Click instead, you have to overrule/implement the mouse click handlers of the grid.
I used a interposer for TDBGrid class
. It works with a Ctrl+Shift keys:
type
TDBGrid = class(Vcl.DBGrids.TDBGrid)
private
procedure SelectRange;
protected
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer); override;
end;
{...}
implementation
{...}
procedure TDBGrid.SelectRange;
var
CurrentBookMark, CursorBookMark, FirstBookMark, LastBookMark: TBookmark;
Dir: integer;
begin
if SelectedRows.Count <= 1 then
exit;
DataSource.DataSet.DisableControls;
try
FirstBookMark := SelectedRows.Items[0];
LastBookMark := SelectedRows.Items[SelectedRows.Count - 1];
SelectedRows.Clear;
CurrentBookMark := DataSource.DataSet.GetBookmark;
Dir := DataSource.DataSet.CompareBookmarks(FirstBookMark, CurrentBookMark);
if Dir = 0 then
Dir := DataSource.DataSet.CompareBookmarks(LastBookMark, CurrentBookMark);
if Dir > 0 then
DataSource.DataSet.GotoBookmark(LastBookMark)
else if Dir < 0 then
DataSource.DataSet.GotoBookmark(FirstBookMark)
else
Exit;
while not DataSource.DataSet.eof do
begin
CursorBookMark := DataSource.DataSet.GetBookmark;
SelectedRows.CurrentRowSelected := true;
if DataSource.DataSet.CompareBookmarks(CurrentBookMark, CursorBookMark) = 0 then
begin
DataSource.DataSet.FreeBookMark(CursorBookMark);
break;
end;
DataSource.DataSet.FreeBookMark(CursorBookMark);
if Dir < 0 then
DataSource.DataSet.Next
else
DataSource.DataSet.Prior;
end;
DataSource.DataSet.FreeBookMark(CurrentBookMark);
finally
DataSource.DataSet.EnableControls;
end;
end;
{ TDBGrid }
procedure TDBGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
inherited;
if Shift = [ssCtrl, ssShift, ssLeft] then
SelectRange;
end;