luix10
luix10

Reputation: 49

DBCtrlGrid Drag and Drop

Tried with no success to drag and drop a row to switch positions (using a ClientDataSet in memory)

The specific case is: a ClientDataSet with image file names who will result in an ordered list that will be used to create export to a PDF document where each image is a page (this is why the order is important). The DbCtrlGrid is used to visualize a thumbnail of the image, and I was trying to use drag-and-drop to exchange their positions, but I couldn't get information about the row where I dropped in the end.

It would help a method to get info about the row where the mouse is over when the OnDragDrop event triggers or any other idea

please

Upvotes: 0

Views: 1242

Answers (1)

MartynA
MartynA

Reputation: 30715

I imagine your q is prompted by the fact that although the TDBCtrlGrid has a PanelIndex property which tells you which one of the grid's virtual panels is active (i.e. is the one for the current row in the dataset), this doesn't change while you've moving the mouse around e.g. during a drag operation. However, it is not difficult to calculate this yourself, as follows.

The Height and Width of a TDBCtrlGrid are exact multiples of its RowCount and ColCount. In the simple case of ColCount =1, it is trivially simple to calculate which Row contains a given Y coordinate within the grid:

function TForm1.PanelIndexFromYPos(Y : Integer) : Integer;
var
  PanelHeight : Integer;
begin
  PanelHeight := DBCtrlGrid1.ClientHeight div DBCtrlGrid1.RowCount;

  Result := Y div PanelHeight;
end;

(obviously this is for the simple case of a single column goVertical orientated grid but would be easy to generalise)

Now, the TBDCtrlGrid's EndDrag (and MouseOver) tells you the Y coordinate of the TPoint where the drag operation ends, so you can use this PanelIndexFromYPos function to tell you which row index the user has dropped the dragged row onto. As @KenWhite explained, you then need to re-order your CDS to reflect the new position the dragged row should be in. This is easy to do if your CDS has a DisplayIndex field representing what row position a given record in the CDS and the CDS has an active index on this field. Re-ordering the CDS's records is a bit of a rigmarole, as will be apparent from the following sample project.

TForm1 = class(TForm)
  CDS1: TClientDataSet;
  DataSource1: TDataSource;
  DBGrid1: TDBGrid;
  DBCtrlGrid1: TDBCtrlGrid;  //  Note: DragMode set to dmManual;
  DBText1: TDBText;  // In the DBCtrlGrid
  DBText2: TDBText;
  DBText3: TDBText;
  edSourceIndex: TEdit;
  edDestIndex: TEdit;
  btnTest: TButton;
  Memo1: TMemo;
  Label1: TLabel;
  procedure FormCreate(Sender: TObject);
  procedure DBCtrlGrid1MouseDown(Sender: TObject; Button: TMouseButton;
    Shift: TShiftState; X, Y: Integer);
  procedure DBCtrlGrid1DragOver(Sender, Source: TObject; X, Y: Integer;
    State: TDragState; var Accept: Boolean);
  procedure DBCtrlGrid1DragDrop(Sender, Source: TObject; X, Y: Integer);
  procedure btnTestClick(Sender: TObject);
private
  procedure MoveRow(SourceIndex, DestIndex : Integer);
  procedure LogMove(OldValue, NewValue: Integer);
  procedure ShowPanelInfo(Y: Integer);
protected
  function PanelIndexFromYPos(Y : Integer) : Integer;
public
  SourceIndex : Integer; // the DbCtrlGrid PanelIndex of the row being dragged
  DestIndex : Integer;  // the PanelIndex where the row is dropped
end;
[...]

function TForm1.PanelIndexFromYPos(Y : Integer) : Integer;
var
  PanelHeight : Integer;
begin
  PanelHeight := DBCtrlGrid1.ClientHeight div DBCtrlGrid1.RowCount;

  Result := Y div PanelHeight;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  AField : TField;
begin
  //  Create the fields for the CDS
  AField := TIntegerField.Create(Self);
  AField.FieldName := 'ID';
  AField.DataSet := CDS1;

  //  This DisplayIndex field will be used to determine which row number in
  //  the DBCtrlGrid will occupy, by indexing the CDS on this field
  AField := TIntegerField.Create(Self);
  AField.FieldName := 'DisplayIndex';
  AField.DataSet := CDS1;

  AField := TStringField.Create(Self);
  AField.FieldName := 'Name';
  AField.Size := 20;
  AField.DataSet := CDS1;

  CDS1.CreateDataSet;

  //  Add some data which will appear in the grid in reverse-alphabetical order
  CDS1.InsertRecord([1, 3, 'A']);
  CDS1.InsertRecord([2, 2, 'B']);
  CDS1.InsertRecord([3, 1, 'C']);
  CDS1.InsertRecord([4, 0, 'D']);

  CDS1.IndexFieldNames := 'DisplayIndex';

end;

procedure TForm1.DBCtrlGrid1MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then begin
      SourceIndex := PanelIndexFromYPos(Y);
      DBCtrlGrid1.BeginDrag(False);
   end;
end;

procedure TForm1.DBCtrlGrid1DragOver(Sender, Source: TObject; X,
  Y: Integer; State: TDragState; var Accept: Boolean);
begin
  Accept := True;
end;

procedure TForm1.DBCtrlGrid1DragDrop(Sender, Source: TObject; X,
  Y: Integer);
begin
  ShowPanelInfo(Y);
  DestIndex := PanelIndexFromYPos(Y);
  MoveRow(SourceIndex, DestIndex);
end;

procedure TForm1.MoveRow(SourceIndex, DestIndex : Integer);
var
  BM : TBookMark;
  Index : Integer;

  procedure SetCDSIndex(Value : Integer);
  var
    OldValue : Integer;
  begin
    OldValue := CDS1.FieldByName('DisplayIndex').AsInteger;
    CDS1.Edit;
    CDS1.FieldByName('DisplayIndex').AsInteger := Value;
    CDS1.Post;
    LogMove(OldValue, Value);
  end;

begin
  if SourceIndex = DestIndex then exit;
  CDS1.DisableControls;
  try
    if CDS1.FindKey([SourceIndex]) then begin
      BM := CDS1.GetBookmark;  //  This is to keep track of the dragged row without needing to
                               //  keep track of its (changing) DisplayIndex
      if SourceIndex > DestIndex then begin
        //  i.e. we're moving the dragged row up in the grid
        //  so starting with the row above it we move the rows upwards
        //  eventually leaving a gap to drop the dragged row into
        Index := SourceIndex - 1;
        while Index >= DestIndex do begin
          if CDS1.FindKey([Index]) then begin
            SetCDSIndex(Index + 1);
          end;
          Dec(Index);
        end;
      end
      else begin
        //  i.e. we're moving the dragged row down in the grid
        //  so starting with the row below it we move the rows upwards
        //  eventually leaving a gap to drop the dragged row into
        Index := SourceIndex + 1;
        while Index <= DestIndex do begin
          if CDS1.FindKey([Index]) then begin
            SetCDSIndex(Index - 1);
          end;
          Inc(Index);
        end;
      end;
    end;
    CDS1.GotoBookMark(BM);
    if CDS1.FieldByName('DisplayIndex').AsInteger = SourceIndex then begin
      SetCDSIndex(DestIndex);
    end;
    CDS1.FreeBookmark(BM); //  should really have it's own try...finally but hey!
  finally
    CDS1.EnableControls;
  end;
end;

procedure TForm1.LogMove(OldValue, NewValue : Integer);
begin
  Memo1.Lines.Add(Format('Name: %s Old: %d  New: %d ', [CDS1.FieldByName('Name').AsString, OldValue, NewValue]));
end;

procedure TForm1.ShowPanelInfo(Y : Integer);
begin
  Label1.Caption := Format('y: %d panelindex: %d', [Y, PanelIndexFromYPos(Y)]);
end;

procedure TForm1.btnTestClick(Sender: TObject);
begin
  // For debugging, to test mving rows without needing to drag/drop
  MoveRow(StrToInt(edSourceIndex.Text), StrToInt(edDestIndex.Text));
end;

end.

Upvotes: 1

Related Questions