MouseWheelDown event fires multiple times in succession - delphi

Delphi 7 on Windows 7
I want to scroll down a list (TElTree) using the mouse wheel... so I start writing some code in the ElTree's OnMouseWheelDown event. Then I notice that it's scrolling 2 rows at once. The mouse wheel settings in Control Panel are set to scroll only 1 line at a time. If I place a breakpoint inside the event handler, I discover that the event handler is itself being executed twice in quick succession.... Why? How to ensure it only executes once (code please)?

When the wheel scrolls, the OS sends WM_MOUSEWHEEL messages. The high-order word of the wParam parameter indicates how far the wheel has turned. If it has turned one "click," then its value will be 120, or WHEEL_DELTA. But it might be less than that if your scroll wheel recognizes scrolling less than a click's worth.
Correct WM_MOUSEWHEEL message handlers need to consider that parameter and either keep a "scrolling accumulator" to keep track of how far the wheel has scrolled or else have the ability to scroll less than a full line. Message handlers that assume that each message signifies a full click of the wheel will be sorry.

Solution is quite simple: After calling the function you want to perform OnMouseWheel you must set Handled:=True so that the routine is not called multiple times. e.g.
procedure TMainForm.FormMouseWheelUp(Sender: TObject; Shift: TShiftState;
MousePos: TPoint; var Handled: Boolean);
begin
YourFunctionToExecute(Sender, Shift, MousePos, Handled);
Handled:=True;
end;

Related

How to ignore the mouse wheel scrolling for TComboBox controls? [duplicate]

This question already has answers here:
How to suppress mouse wheel in TcxComboBox
(1 answer)
Suppress mouse scroll events on specific controls but NOT their parent?
(1 answer)
Closed last year.
I've found numerous things in this regard, but they're either for third-party controls, different specific situations, or for a completely different language. What I need should be fairly simple.
I have a TDBGrid with a TComboBox placed over a specific cell. As the user scrolls through this grid, the combo box moves along to the corresponding cell. User is further able to change the value of this combo box to update the database.
However, when using the mouse wheel to scroll, if the mouse pointer just happens to be over this combo box, it ends up changing the value of the combo box, rather than scrolling the grid.
How do I suppress the scrolling in the combo box?
I am answering this QA style, because I found such a simple solution on my own which I couldn't find on various forums.
The simplest method is to add an OnKeyDown event handler to the TComboBox, and add the following:
procedure TfrmMain.cboStatusKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key in [VK_UP, VK_DOWN] then
Key:= 0;
end;
Keep in mind that this also blocks keyboard up/down events. If you also wish to block keyboard left/right events, then you can also do this:
procedure TfrmMain.cboStatusKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key in [VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT] then
Key:= 0;
end;

Mouse Leave event in Firemonkey 10.3

I have a form with a control (myControl) and a child control (myChildControl).
I want to manage the mouse exit on myControl so that I can take some actions.
The problem is that the On Mouse Leave event is fired even though the mouse is still physically inside myControls but getting over myChildControl, while in this case I wouldn't need the event to be fired.
I cannot set the HitTest property in the child control as I need to take some actions on mouse events on it too.
Conceptually the Mouse Leave event fires properly, but what's the cleanest way to manage this case?
Set HitTest = False for the child control. Then in the OnMouseDown of the parent control, use PtInRect() to check whether the mouse down event occured over the child.
If needed you can do the same for other mouse events too.
Edit after comment regarding several child controls.
I don't really agree with you about messyness. Following example for any number of child controls that might need to respond to mouse down events, on the parent control (TPanel in this example). Adding the last two lines for eventual other events is not a big deal, IMO.
procedure TForm20.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
var
ctrl: TControl;
begin
for ctrl in (Sender as TControl).Controls do
if PtInRect(ctrl.ParentedRect, PointF(X, Y)) then
if assigned(ctrl.OnMouseDown) then
ctrl.OnMouseDown(ctrl, Button, Shift, x-ctrl.Position.X, y-ctrl.Position.Y);
end;
A completely different approach, would be to use the OnMouseEnter event of the control your parent control is placed on (or if placed on the form, which doesn't have a OnMouseEnter, use the OnMouseMove), to trigger the OnExit substitute. As this could trigger very often, you could avoid unneeded actions, by declareing a boolean for ActionRequired

Mouse over stringgrid when dragging?

Parts of my stringgrid are eligible drop targets, and some are not (first row is column headings, first column is a sort of index and subsequent columns may be dropped to). I have that coded and working.
Now I am thinking that it might be nice to gve a visual indiation to the user as he drags the mouse over a cell which is a potential drop target. I woudl like to highlight the first cell in the row and column of the cell over which he is currently hovering (or possibly the entire row and column, forming a sort of crosshair; I am as yet undecided). I reckon I can code that in OnDrawCell.
I had thought to use OnMouseMove and cehck if Dragging then, but ...
My problem is that when I am dragging the OnMouseMove event never gets called.
Is there any other way to know when the cursor is hovering over a strigngrid during a drag operation?
The OnDragOver event is specifically designed for doing this; it's called automatically, and provides the X and Y coordinates where the mouse pointer is located. There's a code sample available at that link location that demonstrates using it as well - it's for a TListBox, but the principle is the same.
procedure TForm1.FormCreate(Sender: TObject);
begin
ListBox1.Items.Add('Not');
ListBox1.Items.Add('In');
ListBox1.Items.Add('Alphabetical');
ListBox1.Items.Add('Order');
end;
// This OnDragOver event handler allows the list box to
// accept a dropped label.
procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
Accept := Source is TLabel;
end;

How do I use DoMouseWheel to scroll a line at a time?

I've written a grid control and would like to add support for the mouse wheel to it. I thought it would be as simple as overriding the DoMouseWheel virtual method, but there is a bit of a problem with it.
You can set the number of lines to scroll at a time in Control Panel and the default there is three. And this makes perfect sense when scrolling through a document or web page, but on a grid, I think the expectation is rather to scroll a line at a time. But it seems that Delphi's wheel support will call DoMouseWheel three times for every notch that I scroll, meaning that I can only scroll to each third line in the grid (or whatever that global setting is).
How do I go about scrolling a single line at a time for every turn of the mouse wheel?
Update: The short answer here is to simply set Result to True after scrolling - then it doesn't scroll three times, but only once.
Just copy the code from the TCustomGrid class, which overrides both DoMouseWheelDown() and DoMouseWheelUp() to scroll exactly one line at a time.
In general, it is not a very good idea to fight against the system defaults and/or the user preferences. In this case means that you should respect whatever the system or the user has decided to set in the scrolling time.
Having said so, if you really believe that the multiscroll effect is totally wrong and misleading for the kind of component you want to drive, you might envision a way to get rid of this. You could try to set some timer and ignore all but one of the mouseWheel events that happen in a given lapse of time (in the range of milliseconds). One thing you should do is to set a configuration option in your program, to let the user to turn off this behaviour.
In my case, I used the JVDBGrid component, but I think this work for DbGrid too. You may overwrite the following functions: OnMouseWheelDown and OnMouseWheelUp.
E.g.:
Type declaration:
type
TMyGrid = class(TJvExDBGrid);
Implementation
procedure TFExample.JvDBGrid1MouseWheelDown(Sender: TObject; Shift: TShiftState; MousePos: TPoint; var Handled: Boolean);
begin
Handled := TMyGrid(Sender).DataLink.DataSet.MoveBy(1) <> 0;
end;
procedure TFExample.JvDBGrid1MouseWheelUp(Sender: TObject; Shift: TShiftState; MousePos: TPoint; var Handled: Boolean);
begin
Handled := TMyGrid(Sender).DataLink.DataSet.MoveBy(-1) <> 0;
end;

How do I know when I've stopped scrolling a TScrollBar?

I've used some programs with scroll bars that update the linked content while you're still dragging the "thumb", and others that don't until you release the mouse. This implies that there are different types of Windows messages involved here. But all I can find from TScrollBar is an OnScroll event which fires continually while you're dragging. It also doesn't have a OnMouseDown or OnMouseUp event. Is there any way to set up an "OnEndDragging" notification for a TScrollBar?
Try this code (tested with Delphi 2009), it will fill the form client area with a random colour while you track the thumb, and fill it in yellow when the thumb is released:
procedure TForm1.ScrollBar1Scroll(Sender: TObject; ScrollCode: TScrollCode;
var ScrollPos: Integer);
begin
Randomize;
if ScrollCode = scTrack then
Color := RGB(Random(256), Random(256), Random(256));
if ScrollCode = scEndScroll then
Color := clYellow;
end;
The TScrollCode values map to the WPARAM values that you will find documented for WM_HSCROLL and WM_VSCROLL.
Programs that update their scrolling region "live" as the user drags the thumb are handling the sb_ThumbTrack code for the wm_HScroll and wm_VScroll messages. Those that only update when the user releases the thumb are handling the sb_ThumbPosition code.
There's a compromise on those two options, which is to update after the user hasn't moved the thumb for a little bit, even if the user hasn't actually released it yet. Handle sb_ThumbTrack, and then set a timer. If the timer fires, update the display. If another sb_ThumbTrack arrives, reset the timer.

Resources