Add keyboard events to TPaintBox component - delphi

I use a TPaintBox inside my application. Several mouse event handlers are already set up: mouse down, mouse up, etc. However, I also want to respond to keyboard input: if the user presses any function key, I would like to execute a separate procedure (event handler) and not the Mouse* event handler functions. But I also need the mouse position inside my new procedure.
How do I code this, as TPaintBox does not support any key press events?
procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
// here some code
end;
procedure TForm1.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
// more code here
end;
procedure TForm1.PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
// here other code
end;

TPaintBox does not descend from TWinControl, but rather from TGraphicControl, which means that it cannot receive input focus and so it has no functionality to react to keyboard events.
Possible solutions:
Implement the OnKeyPress event of the parent form on which the PaintBox resides and enable the form's KeyPreview property.
Add an action with the specific key press set as its ShortCut property and implement its OnExecute event handler. (See also: When does a ShortCut fire?).
Implement an OnShortCut event handler for the MainForm or the Application.
Place and align the PaintBox on to a TWinControl and implement the OnKeyPress event of that container.
For combinations of mouse and keyboard input, check the Shift parameter of the mouse events or use the Win32 GetKeyState() and GetKeyboardState() functions.

Related

How to close custom positioned PopupMenu in delphi?

I have a project with CoolTrayIcon and PopupMenu with disabled AutoPopup property.
I would like to position the PopupMenu and show it for the user.
The position is OK but menu doesn't close if the user clicks away or press ESC button.
I have not found any property like Active which could help if the menu is used or not.
Here I position the menu:
procedure TForm1.CoolTrayIcon1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
pnt: TPoint; yy:integer;
begin
GetCursorPos(pnt);
yy:=pnt.y; yy:=yy-500;
if (Button=mbRight) then begin
PopupMenu1.Popup(pnt.X, yy);
end;
end;
How could I manage to close menu if it is needed?
This is a known issue that is discussed here:
PRB: Menus for Notification Icons Do Not Work Correctly
You need to wrap the call to Popup() as follows:
SetForegroundWindow(Handle);
PopupMenu1.Popup(pnt.X, yy);
PostMessage(Handle, WM_NULL, 0, 0);
In this code, Handle is the window handle of the form associated with the notification icon.

Why WM_LBUTTONUP message dispear in Delphi?

The MouseDown and MouseUp will run when I write like this:
procedure TForm1.Edit1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Label1.Caption:='ddddddd';
end;
procedure TForm1.Edit1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
ShowMessage('mouseup');
end;
But when I write like this, the WM_LBUTTONUP dispear and Edit1MouseUp will not run, why?
procedure TForm1.Edit1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
ShowMessage('mousedown');
end;
procedure TForm1.Edit1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
ShowMessage('mouseup');
end;
When you call ShowMessage, that shows a modal window. The call to ShowMessage does not return until the modal window closes. The modal window runs its own message loop, and that eats the mouse up message. So, the WM_LBUTTONUP that is in the message queue already, or is about to be placed in the message queue, is actually processed by the message box rather than your Delphi form.
How exactly is that message processed? Well, it depends. If the message was posted before the modal window is shown, then it will be dispatched to the owner window, which is disabled. If it is posted after the modal window is shown, then it may be dispatched to the modal window.
This is one of the reasons why actions are invoked by mouse up rather than mouse down. Perhaps you've not noticed that yet, but try clicking on a button in any common application and note that the response only occurs when the mouse goes up. Indeed if you press the mouse down on a button, move the cursor away from the button, and then release the button, the action does not trigger.
Now try something similar with your second code sample. Press the mouse down in the edit control but don't release it immediately. Note that the result of the mouse going down is that a modal window is now showing. It runs its own message loop and your form is disabled. Now release the mouse button. Clearly the WM_LBUTTONUP message is going to be pulled off the queue by the modal window's message loop.
In your second scenario, the edit control is never posted a WM_LBUTTONUP, hence OnMouseUp event is not fired.
When you call ShowMessage in OnMouseDown, a dialog is launched. This not only releases the mouse capture from the edit control, it also disables the form. With both, the message has nowhere to be posted: not to a window with the capture, and not to a window under the cursor (see documentation).
You can simulate the behavior with this code, without showing a message:
procedure TForm1.Edit1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Assert(GetCapture = Edit1.Handle);
ReleaseCapture;
Enabled := False;
end;
procedure TForm1.Edit1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
ShowMessage('mouseup'); // will not fire.
end;
The moral is: do not use ShowMessage to debug situations involving activation/focus. As it may not be quite evident as in this case, in general do not use it as a debugging tool at all.

Non-breaking breakpoint constantly triggers OnMouseMove event in Delphi XE3

I was trying to use a non-breaking breakpoint to log some values in a control's OnMouseMove event handler. I noticed that the breakpoint was constantly being hit while the mouse was idle (not moving) over the control.
After a little investigation, I came to the conclusion that the breakpoint was responsible for this behaviour. This problem can easily be reproduced with the following example. When the breakpoint is enabled, the counter is constantly incremented while the mouse is over the form. When the breakpoint is disabled, the counter increases only when the mouse moves over the form.
TForm1 = class(TForm)
Edit1: TEdit;
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
public
FCounter: Integer;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
Inc(FCounter);
Edit1.Text := IntToStr(FCounter);
end;
I can find other ways of logging the information I need, however, I would like to know there are any workarounds that will let me use my original approach.

How to connect 20 shapes to a single 'OnMouseDown'?

I have a Delphi program which contains the following code:
procedure TForm1.Shape1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
<code here>
end;
Thing is, I want the exact same code for all 20 shapes. How do I go around doing this effectively? Thanks!
Use multi-select (e.g. SHIFT+CLICK, or click and drag a selection rectangle) to select all 20 shapes.
In the Object Inspector set the OnMouseDown event handler to be the common event handler. This will assign the same event handler to all 20 selected shapes.
If you need to know which shape was clicked from inside your common event handler, use (Sender as TShape).

TStringGrid - OnMouseUp is not called!

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

Resources