When I perform the OnDblClick event (Form1) to open Form2, it fires the OnCellClick event of Form2, without having clicked on the form2 grid

The OS posts a WM_LBUTTONDBLCLK on the second down of the left mouse button. When you execute a ShowModal call here, the application does not get the chance to process the, yet to be posted, WM_LBUTTONUP message until after your dialog is shown. Since TDBGrid fires the OnCellClick event while the control is handling a WM_LBUTTONUP message and the message happens to be posted to the grid since the modal form is the active window now, you encounter the problem.

The behavior of the grid is kind of documented;

Occurs when the user releases the mouse in one of the cells of the grid.

although it could be argued that it should've mention that you don't even have to press the mouse button...

This is an unfortunate design decision, this is not how a click works. Think of pressing the button on one cell and releasing on another. No OnCellClick should be fired. Current behavior is rather confusing, the event fires for the cell you pressed the button on - provided you release the button on a valid cell and not on empty space.

As you have found out, you can even fire the event by pressing the button on a different form and releasing it on a cell of the grid on this form. In this case the event fires for the currently selected cell and mouse position does not play any role in it at all. My opinion is that OnCellClick is a total mess.



You can use kobik's answer for a solution. Below solution fails if for some reason mouse button is held down on the second press for any time period.


Posting a self received message to delay the showing of the dialog, as suggested in the comments to the question, does not work because posted messages have higher priority then input messages. See documentation for GetMessage for more detail.

If you follow the link, you'll notice the timer approach, also as suggested in the comments to the question, will work. Unlike the comment suggests the timing interval does not matter since the WM_TIMER message have the lowest priority. And this is a good thing which makes it a fail-safe approach.

I wanted to put the timer on the modal dialog as it owns the problem control.

procedure TForm2.FormCreate(Sender: TObject);
begin
  DBGrid1.Enabled := False;
  Timer1.Interval := 1;
  Timer1.Enabled := True;
end;

procedure TForm2.Timer1Timer(Sender: TObject);
begin
  DBGrid1.Enabled := True;
  Timer1.Enabled := False;
end;

@Sertac gave a great explanation of the behaviour.

I will try to give another fix by creating an interposer class for TDBGrid e.g.:

type
  TDBGrid = class(DBGrids.TDBGrid)
  protected
    FDown: Boolean;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
  end;

  TForm2 = class(TForm)
    ...
    DBGrid1: TDBGrid;
    ...
  end;

implementation

procedure TDBGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  FDown := True;
  try
    inherited;
  except
    FDown := False;
    raise;
  end;
end;

procedure TDBGrid.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if FDown then
  try
    inherited;
  finally
    FDown := False;
  end;
end;

The FDown flag simply indicates that a MouseUp must be followed only after a MouseDown message.
From my quick test I did not noticed any implications. but there might be.