Components on top of components block the mouse position - delphi

My Delphi 7 app has two TPageControls with a TSplitter between them. On each TPageControl are two TTabSheets. One each TTabSheet is a TWebBrowser. Got the picture?
The problem with this component arrangement is that it is impossible to track the location of the mouse since the TWebBrowser does not have a OnMouseMove event and the TForm's OnMouseMove event is never triggered under this pile of ClientAligned components.
What I need to know is the XY position of the mouse, relative to the app's form, at all times. IOW, I need to know when the mouse moved, and when it does, a function that would:
GetMouseLocationNow(var X, Y : Integer);
How can I do this?

To track mouse move application-wide, you have to track WM_MOUSEMOVE message. You can use TApplicationEvents component for that. So, drop TApplicationEvents on form, and process WM_MOUSEMOVE in OnMessage event. Low order word in LParam specifies X coordinate of the cursor (relative to the window that the message is posted to), and high order word Y coordinate.
procedure TfrmMain.ApplicationEventsMessage(var Msg: tagMSG; var Handled: Boolean);
var
Pt: TPoint;
begin
if Msg.message = WM_MOUSEMOVE then begin
Pt := Point(WORD(Msg.lParam), HiWord(Msg.lParam));
windows.ClientToScreen(Msg.hwnd, Pt);
windows.ScreenToClient(Handle, Pt);
MouseMoved(Pt.X, Pt.Y);
end;
end;
procedure TfrmMain.MouseMoved(const AX, AY: Integer);
begin
// do the work here
end;

Related

Why doesn't my paintbox image appear when the form activates

I'm attempting to write an analog VU meter. I use a bitmap of a VU meter and draw the needle on to the bitmap. I am using a trackbar and it's onChange event to test the meter:
procedure TForm1.TrackBar1Change(Sender: TObject);
var
angle : integer;
x,y : integer;
Peaked : boolean;
begin
Angle := 120 - Round(sTrackBar1.Position / sTrackBar1.Max * 100 )+20;
Peaked := Angle < PeakVol;
if Peaked then
Buffer.Picture := VUImagePeaked.Picture
else
buffer.picture := VUImage.Picture;
buffer.Picture.Bitmap.Canvas.Pen.Color := clSilver;
buffer.Picture.Bitmap.Canvas.Pen.Width:=2;
buffer.Canvas.MoveTo(pivot.x,Pivot.y);
x := 150 + Round(Cos(DegToRad(Angle)) * NeedleLen);
y := PaintBox1.Height - Round(Sin(DegToRad(Angle)) *NeedleLen);
buffer.Canvas.LineTo(x,y);
PaintBox1.Canvas.Draw(0,0,buffer.Picture.Bitmap)
end;
Seems to work but what I can't get is to display the bitmap of the meter when the program starts. I have even resorted to copying the above code to both the Form.Create and Form.Activate event handlers but no joy. I created a button and added the following code to trigger the onChange event handler for the trackbar. This works and displays the meter.
procedure TForm1.Button1Click(Sender: TObject);
begin
TrackBar1.Position := 1;
end;
When I copied this to the Form.Activate handler it doesn't. Can anyone please tell me what I'm doing wrong? I'm using Delphi Berlin starter edition. Thanks
A TPaintBox must be painted using its OnPaint event. This event is triggered every time Windows needs you to redraw the control. You can't simply draw to the control's Canvas at any time, because it will just be drawn over the next time the control is repainted.
In your particular case, you don't need to move your entire block of code to the OnPaint event handler. Instead, all you need is:
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
PaintBox1.Canvas.Draw(0,0,buffer.Picture.Bitmap)
end;

Detect "whitespace" click in a listbox

I have a tall listbox with a variable number of items. It won't always be full. I know when a user doesn't select an item by this code:
if ( lstbox.ItemIndex = -1 ) then
ShowMessage('here');
But this does not work when I an Item is selected and then I click in the 'whitespace' of the listbox. How do I detect that sort of situation?
You can do this in a number of ways. One would be in the OnMouseDown event using the X and Y parameters to that event, which are the client co-ordinates in the listbox at which the user clicked:
procedure TMyForm.ListBox1MouseDown(Sender: TObject;
Button: TMouseButton;
Shift: TShiftState;
X, Y: Integer);
begin
if TListbox(Sender).ItemAtPos(Point(X, Y), TRUE) <> -1 then
// item was clicked
else
// 'whitespace' was clicked
end;
But this won't affect the behaviour in any OnClick event. If you need to perform this test in OnClick then you need to obtain the mouse position and convert it to the listbox client co-ordinates before doing the same test:
procedure TMyForm.ListBox1Click(Sender: TObject);
var
msgMousePos: TSmallPoint;
mousePos: TPoint;
begin
// Obtain screen co-ords of mouse at time of originating message
//
// Note that GetMessagePos returns a TSmallPoint which we need to convert to a TPoint
// in order to make further use of it
msgMousePos := TSmallPoint(GetMessagePos);
mousePos := SmallPointToPoint(msgMousePos);
mousePos := TListbox(Sender).ScreenToClient(mousePos);
if TListbox(Sender).ItemAtPos(mousePos, TRUE) <> -1 then
// item clicked
else
// 'whitespace' clicked
end;
NOTE: GetMessagePos() obtains the mouse position at the time of the most recently observed mouse message (which should in this case be the message that originated the Click event). However, if your Click handler is invoked directly then the mouse position returned by GetMessagePos() is likely to have little or no relevance to the processing in the handler. If any such direct invocation might sensibly utilise the current mouse position, then this may be obtained using GetCursorPos().
GetCursorPos() is also much more straightforward to use as it obtains the mouse position in a TPoint value directly, avoiding the need to convert from TSmallPoint:
GetCursorPos(mousePos);
Either way, the fact that your handler is dependent upon mouse position in any way makes directly invoking this event handler problematic so if this is a consideration then you might wish to isolate any position independent response in the event handler into a method that can be explicitly invoked if/when required, independently of the mouse interaction with the control.

How do I get the absolute mouse co-ordinates when mouse is over a control

I want to get the absolute co-ordinates of the mouse when the mouse is over a control which has been placed on a host control. E.g. Host control is a panel a button is placed on the panel. I want to get the mouse co-ordinates relative to the panel when the mouse is over the button.
I have tried the obvious just to see what I get:
procedure TfmWorkingScreen.pnlScreenAreaMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
begin
StatusBar1.SimpleText := 'Left ' + IntToStr(X) + ' Right ' + IntToStr(Y);
end;
Clearly this will only work when the mouse is over the panel control. Is there a way to get the required co-ordinates?
Add an OnMouseMove event handler to the child control, the button in your example. In that OnMouseMove event handler you receive the X and Y cursor coordinates with respect to the child control's client area. If the host control is the immediate parent of the control whose OnMouseMove event has fired you use the control's ClientToParent method:
var
PosRelParent: TPoint:
....
PosRelParent := (Sender as TControl).ClientToParent(Point(X, Y));
If the parent control may be further up the parent/child relationship you can pass the parent control to ClientToParent:
PosRelParent := (Sender as TControl).ClientToParent(Point(X, Y), TheParent);
If you wish to express the position relative to some arbitrary controls client area then you can do so by converting via screen coordinates, a global frame of reference.
var
PosRelScreen, PosRelOtherControl: TPoint:
OtherControl: TWinControl;
....
PosRelScreen := (Sender as TControl).ClientToScreen(Point(X, Y));
PosRelOtherControl := OtherControl.ScreenToClient(PosRelScreen);
As one final offering, you can use GetMessagePos to obtain the screen relative coordinates of the mouse for the last message retrieved by a call to GetMessage.
var
MsgPos: TPoint;
....
MsgPos := TSmallPoint(GetMessagePos());
At this point you can use SomeControl.ScreenToClient(MsgPos) to get the coordinates of the cursor relative to that control's client area. Obviously it only makes sense to call GetMessagePos if you are in an event handler triggered by a queued mouse message.
If you want to listen to your mouse outside your program aswell you need to create a mouse hook.
I'ver earlier summitted a article and source code to About.com : http://delphi.about.com/od/windowsshellapi/a/delphi-hooks.htm
If this have you interest I would love to go deeper on then subject.

How do I get the coordinates of the mouse when a control is clicked?

In a TImage's OnClick event, I would like to extract the x,y coordinates of the mouse. I would prefer them in relation to the image, but in relation to the form or window is just as good.
Mouse.CursorPos contains the TPoint, which in turn contains the X and Y position. This value is in global coordinates, so you can translate to your form by using the ScreenToClient routine which will translate screen coordinates to window coordinates.
According to the Delphi help file, Windows.GetCursorPos can fail, Mouse.CursorPos wraps this to raise an EOsException if it fails.
var
pt : tPoint;
begin
pt := Mouse.CursorPos;
// now have SCREEN position
Label1.Caption := 'X = '+IntToStr(pt.x)+', Y = '+IntToStr(pt.y);
pt := ScreenToClient(pt);
// now have FORM position
Label2.Caption := 'X = '+IntToStr(pt.x)+', Y = '+IntToStr(pt.y);
end;
The Mouse.CursorPos property will tell you the current position of the mouse. If the computer is running sluggishly, or if your program is slow to respond to messages, then it might not be the same as the position the mouse had when the OnClick event first occurred. To get the position of the mouse at the time the mouse button was clicked, use GetMessagePos. It reports screen coordinates; translate to client coordinates with TImage.ScreenToClient.
The alternative is to handle the OnMouseDown and OnMouseUp events yourself; their parameters include the coordinates. Remember that both events need to occur in order for a click to occur. You may also want to detect drag operations, since you probably wouldn't want to consider a drag to count as a click.
As others have said, you can use Mouse.CursorPos or the GetCursorPos function, but you can also just handle the OnMouseDown or OnMouseUp event instead of OnClick. This way you get your X and Y values as parameters to your event handler, without having to make any extra function calls.
How about this?
procedure TForm1.Button1Click(Sender: TObject);
var
MausPos: TPoint;
begin
GetCursorPos(MausPos);
label1.Caption := IntToStr(MausPos.x);
label2.Caption := IntToStr(MausPos.y);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
SetCursorPos(600, 600);
end;
Found this online somewhere once and saved it in my codesnippet DB :)
This page will probably solve all your questions however... There appear to be functions to go from client to screen coordinates and back etc..
Good luck!
To Firemonkey (FMX):
var
p: TPointF;
begin
p := Screen.MousePos;
end;

How to reduce CPU usage when moving a component in a OnMouseMove event in Delphi 7?

In a Delphi 7 application, I want to move a component following the mouse. I'm doing something like this:
procedure MyComponent.MouseMove(Sender: TObject;Shift: TShiftState; X, Y: Integer);
begin
AnotherComponent.Top := X;
AnotherComponent.Left := Y;
end;
When I move the mouse the CPU usage for the main core goes up to 100% on a recent PC.
Any idea or tick to reduce the CPU usage in this case ?
You could create a TTimer that polls the current mouse position every 0.10 seconds or so, then positions "AnotherComponent" according to the current mouse position.
Then you wouldn't fire your event for every pixel of mouse movement- you won't need any OnMouseMove event on your controlling component at all.
On my computer, this basically has no performance impact at all.
procedure TForm1.Timer1Timer(Sender: TObject);
var
pt: TPoint;
begin
//Is the cursor inside the controlling component? if so, position some
//other control based on that mouse position.
GetCursorPos(pt);
if MouseWithin(pt.x,pt.y,MyComponent,Form1.Left,Form1.Top) then begin
//replace with whatever real positioning logic you want
AnotherComponent.Top := pt.y;
AnotherComponent.Left := pt.x;
end;
end;
function TForm1.MouseWithin(mouseX, mouseY: integer;
const comp: TWinControl; const ParentWindowLeft: integer;
const ParentWindowTop: integer): boolean;
var
absoluteCtrlX, absoluteCtrlY: integer;
begin
//take a control, and the current mouse position.
//tell me whether the cursor is inside the control.
//i could infer the parent window left & top by using ParentwindowHandle
//but I'll just ask the caller to pass them in, instead.
//get the absolute X & Y positions of the control on the screen
//needed for easy comparison to mouse position, which will be absolute
absoluteCtrlX := comp.Left + ParentWindowLeft;
absoluteCtrlY := comp.Top + ParentWindowTop +
GetSystemMetrics(SM_CYCAPTION);
Result := (mouseX >= absoluteCtrlX)
and (mouseX < absoluteCtrlX + comp.Width)
and (mouseY >= absoluteCtrlY)
and (mouseY <= absoluteCtrlY + comp.Height);
end;
Finally I've change my code for this one:
procedure MyComponent.MouseMove(Sender: TObject;Shift: TShiftState; X, Y: Integer);
begin
if GetTickCount-LastMoveTick>50 then begin
AnotherComponent.Top := Y;
AnotherComponent.Left := X;
LastMoveTick := GetTickCount;
end;
end;
Really easy to implement (2 lines added), no timer, works well for me...
It has nothing to do with the Mouse Move itself.
Unless it's what you intended, you are mismatching X, Y with Top, Left. Top is the Y coord and Left the X one.
The problem is the actual moving of AnotherComponent.
To try and understand it, I suggest that you write a TestMove routine that moves your AnotherComponent automatically with adjustable repetition/delays to monitor the CPU.
I bet it triggers a costly repaint or some other CPU intensive calculation.
So Examine closely if you have any event handler on this component first, then go with the inherited behavior...
Maybe, instead of moving the component itself you move a 'shadow' and only move the component once the user lets the mousebutton go. Sort of like drag&drop.
It can't be the move itself that needs so much cpu power, most probably the move causes the component to redraw itself somehow.
Can you avoid that AnotherComponent is redrawn on each move? It should not be necessary, unless it is a movie container.
Anything tied to the mouse move event will be called very frequently as mice are a high resolution input device. I wouldn't worry about the cpu usage though because your handler only gets fired as fast as possible based on how busy the system is. In other words, it's only maxing the CPU because nothing else is.
From MSDN:
The mouse generates an input event
when the user moves the mouse, or
presses or releases a mouse button.
The system converts mouse input events
into messages and posts them to the
appropriate thread's message queue.
When mouse messages are posted faster
than a thread can process them, the
system discards all but the most
recent mouse message.
Now there may be some exceptions to this. You could do some testing to be sure by running some other processing intensive activity and see how much the mouse move stuff impacts it.

Resources