Select Multiple Rows on Shift+Click in DBGrid

2019-08-18 15:31发布

问题:

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]

回答1:

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;


回答2:

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.



回答3:

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;