Non-breaking breakpoint constantly triggers OnMouseMove event in Delphi XE3 - delphi

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.

Related

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

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?
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.
Have you tried doing an Application.ProcessMessages() in the DblClick handler?
procedure TForm1.Panel1DblClick(Sender: TObject);
begin
Application.ProcessMessages;
TForm2.Create(Self).ShowModal;
end;

Run-time Component at cursor position

I couldn't find the answer anywhere on the web. Tried Google and many others.
In Delphi 7 how to create a run-time component at cursor position?
I tried a simple code:
procedure TForm1.TButton1Click(Sender: TObject);
var NewCheckBox: TCheckBox;
MB: TMouseButton;
CPos: TPoint;
begin
GetCursorPos(CPos);
NewCheckBox:=TCheckBox.Create(Self);
NewCheckBox.Parent:=Form1;
NewCheckBox.Caption:='NewCheckBox';
NewCheckBox.Left:=CPos.X;
NewCheckBox.Top:=CPos.Y;
end;
But this doesn't work right. The components appear not at the cursor and I cannot place them wherever I want. The code places the component just as I click the button not when I click on the form where I want it to be placed. I want to create a visual of the component that is about to be created and drag it all the way to the form from the button on a toolbar.
I tried Drag-And-Drop but nothing works then, the Drop procedure always shows me a deny sign and does nothing.
The code below will create your checkbox when you right-click on the form. It could do with a bit of refinement, e.g. to handle adding multiple checkboxes, etc, but might help you get going in the right direction.
procedure TForm1.CreateCheckBox(X, Y : Integer);
begin
// NewCheckBox is a Form variable
NewCheckBox:=TCheckBox.Create(Self);
NewCheckBox.Parent:=Form1;
NewCheckBox.Caption:='NewCheckBox';
NewCheckBox.Left:= X;
NewCheckBox.Top:= Y;
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift:
TShiftState; X, Y: Integer);
begin
if Button = mbRight then
if NewCheckbox = Nil then
CreateCheckBox(X, Y);
end;
Btw, when you use drag & drop on your form, getting the entry sign means that you have not set up an OnDragOver event for the form which sets the Accept parameter to True.

Add keyboard events to TPaintBox component

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.

Listbox highlight only when pressed

I am creating a Delphi XE6 Firemonkey mobile application and want to highlight a listbox item but only while it is being pressed. For an example of the effect I am after, create a new Firemonkey desktop application, add a TListBox and add the following event handlers and code:-
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
//populate the listbox
for i := 0 to 19 do
ListBox1.Items.Add(IntToStr(i));
end;
procedure TForm1.ListBox1ItemClick(const Sender: TCustomListBox;
const Item: TListBoxItem);
begin
ListBox1.ItemIndex:=-1;
end;
Now click an item in the listbox and the highlight should disappear on release of the mouse button. Repeating the exercise for mobile sees only a long press producing the desired result and a short press causes the highlight to remain. So I dropped a timer on the form, setting enabled to FALSE, an interval of 200 and creating an OnTimer event:-
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
//populate the listbox
for i := 0 to 19 do
ListBox1.Items.Add(IntToStr(i));
end;
procedure TForm1.ListBox1ItemClick(const Sender: TCustomListBox;
const Item: TListBoxItem);
begin
Timer1.Enabled:=TRUE;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
ListBox1.ItemIndex:=-1;
Timer1.Enabled:=FALSE;
end;
Progress is made but by rapid pressing of the listbox it is easily possible for the listbox to remain highlighted. I tried the timer option on a TListView and it appears to work as hoped but I'm eager to find a solution for TListBox.
Next brainwave was to add a button to the listboxitem:-
procedure TForm1.FormCreate(Sender: TObject);
var i:integer;
BoxItem: TListBoxItem;
ListBoxSpeedButton: TSpeedButton;
begin
for i := 0 to 99 do
begin
ListBox1.Items.Add(IntToStr(i));
BoxItem := ListBox1.ListItems[ListBox1.Items.Count-1];
ListBoxSpeedButton:=TSpeedButton.Create(nil);
ListBoxSpeedButton.Parent:=BoxItem;
ListBoxSpeedButton.CanFocus:=FALSE;
ListBoxSpeedButton.Align:=TAlignLayout.Client;
end;
end;
However, when scrolling the listbox, the button gets activated and when using a custom Speedbutton, the scrolling is jerky and unresponsive and I can't help feeling I'm using controls when there is no need.
Is there a simple solution here?
Uhm... I will start off by saying DONT USE listboxes when scrolling.... FMX Listboxes are meant to be stagnant and performance when scrolling is horrendous. Use a TListView and TListViewItems. There are tons of examples on SO and in the packaged Delphi XE6 examples on how to implement a list via TListView. That being said, there is no need for timers.. Make use of events already available to use for such things, such as OnMouseDown and onMouseUp which are events assigned to basically every FMX control ( ListBox ListBoxItem or ListView, etc.).... Tons of ways to go about implementing this...
Try this - setting onMouseDown and MouseUp events for every listboxItem to what you see below:
procedure TForm1.ListBoxItem5MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
begin
if sender is TListboxItem then
ListBox.ItemIndex:=TListBoxItem(sender).index
end;
procedure TForm1.ListBoxItem5MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
begin
ListBox.ItemIndex:=-1;
end;

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.

Resources