7

Event form 1:

procedure TForm1.Panel1DblClick(Sender: TObject);
begin
  TForm2.Create(Self).ShowModal;
end;

Event form 2:

procedure TForm2.DBGrid1CellClick(Column: TColumn);
begin
  ShowMessage('Test');
end;

What should I do to avoid fom2's onCellClick event?

dummzeuch
  • 10,406
  • 2
  • 47
  • 146
  • I am guessing that the DB Grid is physically in the same place (overlays) the panel so that it receives the Mouse Up action. Is that so? Also, why are you using TForm2.create? It seems to me that you will create multiple copies of TForm2, which cannot be good. – Dsm Jan 28 '19 at 11:59
  • That's right. In my application I have a problem clicking on the panel and firing the onCellClick event without having clicked the DBGrid, I need to ignore the onCellClick event – Luiz Felipe Heemann Jan 28 '19 at 12:09
  • That you get the message is pretty unavoidable in these circumstances, so when showing the screen I would probably use a timer to ignore OnClick events for a short time - half a second maybe. You could use the OnShow event of Form2 to do that but I prefer to write an procedure (which I would generally call 'Execute' for consistency with other modal forms) that handles all start up actions and then calls ShowModal. I must emphasise though that your use of **Form2.Create** is almost certainly wrong. – Dsm Jan 28 '19 at 12:53
  • Your code leaks form instances too. In the sense that the TForm2 instances aren't destroyed until the TForm1 instance dies. – David Heffernan Jan 28 '19 at 12:59
  • Calling ShowModal on your Form2 instance while you are handling a dbl-click event on Form1 is not a good idea. Instead, in the dbl-click handler send yourself a custom message and create/show Form2 in your handler for the custom message. – MartynA Jan 28 '19 at 13:29
  • @MartynA You must post the message rather than send it – David Heffernan Jan 28 '19 at 13:43
  • @DavidHeffernan: Indeed, a slip of the tongue. Thanks for correcting ... – MartynA Jan 28 '19 at 13:47
  • 2
    That's what happens when you implement a click in a mouse up event, CellClick is triggered in response to a `WM_LBUTTONUP.` Whereas a `WM_LBUTTONDBLCLK` is posted in the second button down. I've seen several times handling a mouse up is suggested to implement a click. A click is different, it's down/capture/up. This is a VCL design error. – Sertac Akyuz Jan 28 '19 at 13:50
  • This is a MCVE and not represent the real code on my project. The Form2 leak are not relevant here. The problem is how to open Form2 without firing the OnCellClick() present in the DBGrid in Form2. The use of PostMessage() message haddling like suggest does the same problem above. – Luiz Felipe Heemann Jan 29 '19 at 17:26

3 Answers3

4

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 Akyuz
  • 52,752
  • 4
  • 91
  • 157
  • Interval of 1 might not be enough. at least not in my tests (100 worked fine). +1 for the analysis. I was sure that a `PeekMessage` + `PM_REMOVE` would do the job but it seems there is nothing in the message queue (?). – kobik Jan 30 '19 at 18:36
  • @kobik - Thanks. However your case demonstrates my analysis is not correct at all. PeekMessage in OnCreate of the form works fine here. That's why the 1 ms timer also works. You'll find the message in the queue some time after (~10 - ~100ms). I can't think of any reason why it's working that way. – Sertac Akyuz Jan 30 '19 at 19:07
  • 1
    @kobik - Figured it out, it's in the answer I posted. You can release the mouse button whenever you want. Apparently your mouse is a little slower. Now I have to figure out how do I salvage this... – Sertac Akyuz Jan 30 '19 at 19:53
3

@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.

kobik
  • 20,439
  • 4
  • 54
  • 115
0

Have you tried doing an Application.ProcessMessages() in the DblClick handler?

procedure TForm1.Panel1DblClick(Sender: TObject);
begin
  Application.ProcessMessages;
  TForm2.Create(Self).ShowModal;
end;
Nat
  • 5,287
  • 22
  • 37
  • 1
    I tried this and seemed to work, although I did not mention in the answer. But it will suffer from the same non-deterministic button release timing like the solution in my answer - hold the mouse button down a bit longer for the second press for it to fail, i.e. click-release-click-hold- Application.ProcessMessages (nothing to process) - release (message is posted). – Sertac Akyuz Jan 31 '19 at 01:19