tags:

views:

76

answers:

2

I have a weird behavior with TStringGrid in Delphi 7. Delphi does not call the OnMouseUp event if a pop-up menu is associated to the grid. Basically, when the RMB is pressed, the pop of the menu somehow cancels/delays the OnMouseUp. Actually, to be 100% accurate, next time you push a mouse button the OnMouseUp is called twice – once for the current event, and once for the lost/delayed event.

This will screwup the entire logic of the program as unwanted code will be called next time when the user presses a mouse button.

+4  A: 

The automatic popping up of a context menu is a response to a right click of the mouse. The same click also fires the OnMouseUp event. The VCL developers could either choose to fire the 'OnMouseUp' event before the popup is shown, or after. Apparently the latter is in effect, that is, the event is fired when the popup is closed (either by mouse or by the keyboard like pressing 'Esc').

There's no doubling of the event, when you press the left button to close the popup, you're firing the 'OnMouseUp' event again by releasing the left button.


You have several alternatives. One is to derive a new class and override the MouseDown method to fire your own event. An example;

type
  TMyStringGrid = class(TStringGrid)
  private
    FOnRButtonUp: TMouseEvent;
  protected
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
  published
    property OnRButtonUp: TMouseEvent read FOnRButtonUp write FOnRButtonUp;
  end;
[...]

procedure TStringGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  if (Button = mbRight) and Assigned(FOnRButtonUp) then
    FOnRButtonUp(Self, Button, Shift, X, Y);
  inherited;
end;


Another alternative can be to handle VM_RBUTTONUP message. This can either be done by deriving a new class as above, or replacing the WindowProc of the grid. There's an example of replacing the WindowProc here in this question.


Another alternative can be to leave the mouse-up event alone and do your processing in the OnPopup event of the popup menu. This event is fired before the popup is shown. You can get the mouse coordinates with Mouse.CursorPos.


Still, another alternative can be to set the AutoPopup property of the popup menu to False, and in the OnMouseUp event (or better yet in the OnContextMenu event) first do some processing and then show the popup. An example;

procedure TForm1.StringGrid1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  Pt: TPoint;
begin
  // Do processing

  if Button = mbRight then begin
    Pt := (Sender as TStringGrid).ClientToScreen(Point(X, Y));
    PopupMenu1.Popup(Pt.X, Pt.Y);
  end;
end;
Sertac Akyuz
"Apparently the latter is in effect". This is a counter intuitive since the OnMouseUp event is not a mouse-up event anymore. Actually, it is a "mouse-down" event!!! +1 for your very complete answer.
Altar
@Altar > "...counter intuitive..." - I agree, even more so considering the popup-menu is actually a response to a `WM_CONTEXTMENU` which is generated not only with a WM_RBUTTONUP but also with a WM_NCRBUTTONUP and 'Shift+F10' and VK_APPS, and, for instance with VK_APPS, `OnKeyUp` is fired *before* the popup is shown.
Sertac Akyuz
A: 

I already took an approach somehow similar with the one described by Sertac: I just don't use the PopupMenu property anymore to assign a pop-up menu to the grid. Instead, inside my grid (my grid is a heavily modified string grid derived from TStringGrid) I handle the mouse down event and display the pop-up the way I want AND do the extra processing I wanted to do BEFORE the menu pops.

Altar