Dialog shifted to the left and upward in Windows 8 - delphi

This dialog shows exactly under button, but in Windows 8 dialog is shifted to the left and upward. How to get the same results in all Windows versions?
procedure TForm1.Button3Click(Sender: TObject);
var p: TPoint;
begin
p := Button3.ClientToScreen(Point(0, Button3.Height));
MessageDlgPos('', mtInformation, [mbOK], 0, p.X, p.Y);
end;
update:
In case we open Form instead of Dialog, and if that Form has BorderStyle bsSizeable or bsSizeToolWin, then everything is OK. Otherwise (bsDialog, bsSingle, bsToolWindow), Form opens shifted as Dialog from the example above.

Running the exact code you have shown on Windows 7, I am not able to reproduce the same dialog positioning you have shown in your Windows 7 screnshot. The MessageDlgPos window is offset up and to the left in the same manner as your Windows 8 screenshot:
That being said, I notice you are positioning your MessageDlg window relative to the button's client area:
If you want the dialog positioned relative to its actual bottom edge, you need to call ClientToScreen() on the button's Parent rather than on the button itself:
p := Button3.Parent.ClientToScreen(Point(Button3.Left, Button3.Top+Button3.Height));
The end result is about the same, though:
Now, why is the overlap occurring in the first place? Because the window is being positioned such that the top-left corner of its non-client area falls at the specified coordinates:
You can adjust the window coordinates to account for that:
p := Button3.Parent.ClientToScreen(Point(Button3.Left, Button3.Top + Button3.Height));
Inc(p.X, GetSystemMetrics(SM_CXFIXEDFRAME) + GetSystemMetrics(SM_CXBORDER));
Inc(p.Y, GetSystemMetrics(SM_CYFIXEDFRAME) + GetSystemMetrics(SM_CYBORDER));
Which gets you much closer to the desired position:
Note that Aero "tweaks" system metrics a bit, so you might need to use things like DwmGetWindowAttribute(DWMWA_EXTENDED_FRAME_BOUNDS) and/or GetThemeSysSize() to get more accurate metrics.

After your answers and comments and some additional research, I came to this solution. Tested on Windows 8, 7 with Aero, 7 without Aero and XP. I was hoping for something more simple and stable but ...
uses DwmApi;
type
TNonClientMetricsX = packed record
cbSize: UINT;
iBorderWidth: Integer; iScrollWidth: Integer;
iScrollHeight: Integer; iCaptionWidth: Integer;
iCaptionHeight: Integer; lfCaptionFont: TLogFontA;
iSmCaptionWidth: Integer; iSmCaptionHeight: Integer;
lfSmCaptionFont: TLogFontA; iMenuWidth: Integer;
iMenuHeight: Integer; lfMenuFont: TLogFontA;
lfStatusFont: TLogFontA; lfMessageFont: TLogFontA;
iPaddedBorderWidth: Integer; // not defined in Delphi 2007
end;
function GetExtendedFrameOffset(BorderStyle: TFormBorderStyle): integer;
var
IsEnabled: BOOL;
NCM: TNonClientMetricsX;
begin
Result := 0;
if (DwmIsCompositionEnabled(IsEnabled) = S_OK) and IsEnabled and
(BorderStyle in [bsdialog, bsSingle, bsToolWindow]) then
begin
NCM.cbSize := SizeOf(NCM);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SizeOf(NCM), #NCM, 0);
Result := NCM.iBorderWidth + NCM.iPaddedBorderWidth;
end;
end;
procedure TForm1.Button3Click(Sender: TObject);
var p: TPoint; offset: integer;
begin
p := Button3.ClientToScreen(Point(0, Button3.Height));
offset := GetExtendedFrameOffset(bsDialog);
MessageDlgPos('', mtInformation, [mbOK], 0, p.X + offset, p.Y + offset);
end;
update: D2007 includes DwmApi, so no need for complications with LoadLibrary

Related

How to save and then restore vertical scroll position in RichEdit

I am trying to save and then restore the vertical scroll position in RichEdit.
A global var to store scroll pos:
SI: TScrollInfo;
This code saves the scroll position:
FillChar( SI, SizeOf(SI), #0 );
SI.cbSize := SizeOf(SI);
SI.fMask := SIF_POS;
GetScrollInfo( RichEdit1.Handle, SB_VERT, SI );
This code tries to restore it:
RichEdit1.Perform( WM_VSCROLL, MakeLong(SB_THUMBTRACK, SI.nPos), 0 );
The text in RichEdit restores its older position OK. The problem is the vertical scrollbar won't jump to the older location.
My system: Win 7 64, Delphi 2009
What am I doing wrong?
Option 1
In many ways, the "cleanest" solution would be to use the EM_GETSCROLLPOS and EM_SETSCROLLPOS messages:
const
EM_GETSCROLLPOS = $04DD;
EM_SETSCROLLPOS = $04DE;
var
P: TPoint;
procedure TForm1.btnSaveClick(Sender: TObject);
begin
RichEdit1.Perform(EM_GETSCROLLPOS, 0, #P)
end;
procedure TForm1.btnRestoreClick(Sender: TObject);
begin
RichEdit1.Perform(EM_SETSCROLLPOS, 0, #P)
end;
However, beware of the 16-bit limitation described in the documentation, which limits the vertical range you are able to represent using these messages. If you display large RTF documents, this might be an issue (a showstopper, really).
Option 2
Actually, your initial approach seems (to my surprise) not to suffer from this limitation. You will lose in precision, not range. The problem you are observing with the scrollbar can be fixed by using SB_THUMBPOSITION instead of SB_THUMBTRACK.
Option 3
var
Y: Integer;
procedure TForm1.btnSaveClick(Sender: TObject);
begin
y := RichEdit1.Perform(EM_GETFIRSTVISIBLELINE, 0, 0);
end;
procedure TForm1.btnRestoreClick(Sender: TObject);
var
NewY: Integer;
begin
NewY := RichEdit1.Perform(EM_GETFIRSTVISIBLELINE, 0, 0);
RichEdit1.Perform(EM_LINESCROLL, 0, Y - NewY);
end;
might be a viable option.

Multi-monitor screenshot - positioning mouse cursor

I have a procedure which takes a screenshot of a monitor and optionally includes the mouse cursor in the snapshot. The original function was only for one monitor. When drawing the mouse cursor, it currently shows properly only on the Main Monitor. But, I can't figure out how to position it on any other monitor. See the comments towards the end of this procedure.
procedure ScreenShot(var Bitmap: TBitmap; const MonitorNum: Integer;
const DrawCursor: Boolean; const Quality: TPixelFormat);
var
DC: HDC;
C: TCanvas;
R: TRect;
CursorInfo: TCursorInfo;
Icon: TIcon;
IconInfo: TIconInfo;
M: TMonitor;
CP: TPoint;
begin
M:= Screen.Monitors[MonitorNum];
DC:= GetDC(GetDesktopWindow);
try
C:= TCanvas.Create;
try
C.Handle:= DC;
R:= M.BoundsRect;
Bitmap.Width:= R.Width;
Bitmap.Height:= R.Height;
Bitmap.PixelFormat:= Quality;
Bitmap.Canvas.CopyRect(Rect(0,0,R.Width,R.Height), C, R);
finally
C.Free;
end;
finally
ReleaseDC(GetDesktopWindow, DC);
end;
if DrawCursor then begin
R:= Bitmap.Canvas.ClipRect;
Icon:= TIcon.Create;
try
CursorInfo.cbSize:= SizeOf(CursorInfo);
if GetCursorInfo(CursorInfo) then
if CursorInfo.Flags = CURSOR_SHOWING then
begin
Icon.Handle:= CopyIcon(CursorInfo.hCursor);
if GetIconInfo(Icon.Handle, IconInfo) then
begin
CP:= CursorInfo.ptScreenPos;
//Transition mouse position...?
CP.X:= CP.X + M.Left;
CP.Y:= CP.Y + M.Top; //No difference?
Bitmap.Canvas.Draw(
CP.X - Integer(IconInfo.xHotspot) - R.Left,
CP.Y - Integer(IconInfo.yHotspot) - R.Top,
Icon);
end;
end;
finally
Icon.Free;
end;
end;
end;
How do I transition the mouse position properly depending on which monitor I'm using?
You are mapping screen coord MonitorRect.Left to bitmap coord 0. And likewise, MonitorRect.Top to 0. So, if the cursor's screen position is CursorPos then you map that to CursorPos.X - MonitorRect.Left and CursorPos.Y - MonitorRect.Top. And then you also need to account for the hot spot, but you already seem to know how to do that.
The mapping above applies equally to all monitors.
Note that I used my own notation because I found your single letter variables mis-leading. Not to mention that fact that the meaning of these variables changes during the function. I'm look at you, R. That's always a recipe for pain.
Also, don't you need to delete the bitmap handles that are handed to you when you call GetIconInfo? And some error checking wouldn't go amiss.

How do I get another application's window handle passed to Delphi via a mouse click

How can I get the handle of a window to be passed to Delphi by the user selecting the window (could be any other aplication's window) by clicking with the mouse on it. In my Delphi app I could have a button the user clicks that starts this detection process as well as a label displaying the clicked on window's title in the Delphi app. When the user is satisfied he selected the correct window he could click the button in my Delphi app (which will be modal) to stop the selection process and let my app start doing to the other window what it needs to do...
if you know what text is in the title of the window, this code will do the trick for you:
var
WindowList: TList;
function GetHandle (windowtitle: string): HWND;
var
h, TopWindow: HWND;
Dest: array[0..80] of char;
i: integer;
s: string;
function getWindows(Handle: HWND; Info: Pointer): BOOL; stdcall;
begin
Result:= True;
WindowList.Add(Pointer(Handle));
end;
begin
result:= 0;
try
WindowList:= TList.Create;
TopWindow:= Application.Handle;
EnumWindows(#getWindows, Longint(#TopWindow));
i:= 0;
while (i < WindowList.Count) and (result = 0) do
begin
GetWindowText(HWND(WindowList[i]), Dest, sizeof(Dest) - 1);
s:= dest;
if length(s) > 0 then
begin
if (Pos(UpperCase(Windowtitle), UpperCase(s)) >= 1) then
begin
h:= HWND(WindowList[i]);
if IsWindow(h) then
result:= h
end
end;
inc(i)
end
finally
WindowList.Free;
end;
end;
Usage in your example (notepad puts the name of the opened file in the window caption):
h:= getHandle('text.txt');
if (h = 0)
// Oops not found
else
begin
// you got the handle!
end;
I used this code to check if my application was already up and running. But it can be used on any launched application.
The approach that user STATUS_ACCESS_DENIED outlined in the comment is likely the simplest way to go here. I'd recommend using mouse capture over hooking, as it's somewhat simpler to implement.
Here's a slightly more detailed outline of what's involved:
The first thing to change the way that the selection process works. Instead of having the user click a button on your app to start the process, and then click the target window, and finally click again to confirm; it's a lot easier to implement if you have the user click a specific area on your app, then drag to the target window, and then let go of the mouse button while over the target. This is because windows considers a click on another app to belong to that app, and you have to do extra work to intercept it. But there's a simple way - called mouse capture - to get information about a drag/release if it starts off as a click on your own app.
This is also the approach that the Windows SDK Spy++ tool uses; so by doing it this way, you're also being consistent with a well-known tool. (Pic of Spy++ here - note the crosshair Finder Tool in the dialog - that's what you click and drag to the target. Would highly recommend downloading the Windows SDK and playing with this tool if you haven't done so before; it's also a very useful way of seeing how other applications are constructed so great as a Windows API learning tool.)
Steps involved:
Have some control in your app that response to mouse-down events (WM_LBUTTONDOWN in Win32/C, OnMouseDown in delphi). You might want to draw a crosshairs icon or similar here so the user knows where to click.
When you get a mouse down, use SetCapture to 'capture' the mouse. This means that the control will receive all the mouse messages while the mouse is moving - until the user releases the button - even if it moves outside the control.
Set the icon to look like a crosshairs so that the user knows they are in dragging mode
As the user moves the mouse, you'll get WM_MOUSEMOVE message (OnMouseMove in Delphi) that has the pointer coordinates. You'll need to use ClientToScreen to convert these to screen coordinates, then WindowFromPoint to find the window at that point. (Note that this finds the innermost window at that point, you could use ChildWindowFromPoint starting from the desktop window to just get the top-level window if you want that.) It's up to you to decide whether you want to update your UI at every mouse move throughout the drag, or just when the user releases the mouse button.
When the user releases the mouse button, you'll get a WM_LBUTTONUP/OnMouseUp; at that stage, wrap things up by calling ReleaseCapture and putting the cursor back to normal shape.
Note that you'll get mouse move events both during the drag, and also if the user just happens to move the mouse pointer across your control, perhaps on the way to some other control. The simplest way to tell these two cases apart is to use a flag in your control that you set when you get the mouse down, and clear when you get the mouse up, and only process mouse move events if that flag is set.
The above describes the process in terms of plain Win32 APIs that you'd call from C/C++; but it looks like Delphi provides direct support for most or all of them.
edit: Possible Delphi implementation:
type
TForm1 = class(TForm)
Label1: TLabel;
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure FormPaint(Sender: TObject);
procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
FCacheWnd: HWND;
FCaptured: Boolean;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
const // the first item, the place where the crosshair is
ClickRect: TRect = (Left: 10; Top: 10; Right: 44; Bottom: 44);
procedure TForm1.FormPaint(Sender: TObject);
begin
// draw the control and the crosshair if no capturing
if GetCapture <> Handle then begin
DrawFrameControl(Canvas.Handle, ClickRect, 0, DFCS_BUTTONPUSH);
DrawIcon(Canvas.Handle, ClickRect.Left, ClickRect.Top,
Screen.Cursors[crCross]);
end;
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if (Button = mbLeft) and (Shift = [ssLeft])
and PtInRect(ClickRect, Point(X, Y)) then begin
// the second item, draw the control pressed,
// set the flag and the capture. FCacheWnd is used not to get
// window information for every mouse move - if the window under the
// mouse is not changed.
DrawFrameControl(Canvas.Handle, ClickRect, 0, DFCS_PUSHED);
FCacheWnd := 0;
FCaptured := True;
SetCapture(Handle);
Screen.Cursor := crCross; // the third item, set the cursor to crosshair.
end;
end;
function GetWndFromClientPoint(ClientWnd: HWND; Pt: TPoint): HWND;
begin
MapWindowPoints(ClientWnd, GetDesktopWindow, Pt, 1);
Result := WindowFromPoint(Pt);
end;
function GetWndInfo(Wnd: HWND): string;
var
ClassName: array [0..256] of Char;
begin
Result := '';
if IsWindow(Wnd) then begin
GetClassName(Wnd, ClassName, 256);
Result := Format('Window: %x [%s]', [Wnd, ClassName]);
if (GetWindowLong(Wnd, GWL_STYLE) and WS_CHILD) = WS_CHILD then begin
Wnd := GetAncestor(Wnd, GA_ROOT);
GetClassName(Wnd, ClassName, 256);
Result := Format(Result + sLineBreak + 'Top level: %x [%s]', [Wnd, ClassName]);
end;
end;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
Wnd: HWND;
begin
if FCaptured then begin
// fourth item, convert coordinates and find the window under the cursor
Wnd := GetWndFromClientPoint(Handle, Point(X, Y));
if Wnd <> FCacheWnd then
Label1.Caption := GetWndInfo(Wnd);
FCacheWnd := Wnd;
end;
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if FCaptured then begin
// fifth item
FCaptured := False;
ReleaseCapture;
InvalidateRect(Handle, #ClickRect, False); // invalidate pressed look
Screen.Cursor := crDefault;
end;
end;
Edit: It's gone, but you used to be able to download Delphi Window Spy by Eddie Shipman, from delphipages.com, which has turned into a festering heap of useless linkbait.

How to launch an external application in Delphi and get the window handle of the newly started app? [duplicate]

How can I get the handle of a window to be passed to Delphi by the user selecting the window (could be any other aplication's window) by clicking with the mouse on it. In my Delphi app I could have a button the user clicks that starts this detection process as well as a label displaying the clicked on window's title in the Delphi app. When the user is satisfied he selected the correct window he could click the button in my Delphi app (which will be modal) to stop the selection process and let my app start doing to the other window what it needs to do...
if you know what text is in the title of the window, this code will do the trick for you:
var
WindowList: TList;
function GetHandle (windowtitle: string): HWND;
var
h, TopWindow: HWND;
Dest: array[0..80] of char;
i: integer;
s: string;
function getWindows(Handle: HWND; Info: Pointer): BOOL; stdcall;
begin
Result:= True;
WindowList.Add(Pointer(Handle));
end;
begin
result:= 0;
try
WindowList:= TList.Create;
TopWindow:= Application.Handle;
EnumWindows(#getWindows, Longint(#TopWindow));
i:= 0;
while (i < WindowList.Count) and (result = 0) do
begin
GetWindowText(HWND(WindowList[i]), Dest, sizeof(Dest) - 1);
s:= dest;
if length(s) > 0 then
begin
if (Pos(UpperCase(Windowtitle), UpperCase(s)) >= 1) then
begin
h:= HWND(WindowList[i]);
if IsWindow(h) then
result:= h
end
end;
inc(i)
end
finally
WindowList.Free;
end;
end;
Usage in your example (notepad puts the name of the opened file in the window caption):
h:= getHandle('text.txt');
if (h = 0)
// Oops not found
else
begin
// you got the handle!
end;
I used this code to check if my application was already up and running. But it can be used on any launched application.
The approach that user STATUS_ACCESS_DENIED outlined in the comment is likely the simplest way to go here. I'd recommend using mouse capture over hooking, as it's somewhat simpler to implement.
Here's a slightly more detailed outline of what's involved:
The first thing to change the way that the selection process works. Instead of having the user click a button on your app to start the process, and then click the target window, and finally click again to confirm; it's a lot easier to implement if you have the user click a specific area on your app, then drag to the target window, and then let go of the mouse button while over the target. This is because windows considers a click on another app to belong to that app, and you have to do extra work to intercept it. But there's a simple way - called mouse capture - to get information about a drag/release if it starts off as a click on your own app.
This is also the approach that the Windows SDK Spy++ tool uses; so by doing it this way, you're also being consistent with a well-known tool. (Pic of Spy++ here - note the crosshair Finder Tool in the dialog - that's what you click and drag to the target. Would highly recommend downloading the Windows SDK and playing with this tool if you haven't done so before; it's also a very useful way of seeing how other applications are constructed so great as a Windows API learning tool.)
Steps involved:
Have some control in your app that response to mouse-down events (WM_LBUTTONDOWN in Win32/C, OnMouseDown in delphi). You might want to draw a crosshairs icon or similar here so the user knows where to click.
When you get a mouse down, use SetCapture to 'capture' the mouse. This means that the control will receive all the mouse messages while the mouse is moving - until the user releases the button - even if it moves outside the control.
Set the icon to look like a crosshairs so that the user knows they are in dragging mode
As the user moves the mouse, you'll get WM_MOUSEMOVE message (OnMouseMove in Delphi) that has the pointer coordinates. You'll need to use ClientToScreen to convert these to screen coordinates, then WindowFromPoint to find the window at that point. (Note that this finds the innermost window at that point, you could use ChildWindowFromPoint starting from the desktop window to just get the top-level window if you want that.) It's up to you to decide whether you want to update your UI at every mouse move throughout the drag, or just when the user releases the mouse button.
When the user releases the mouse button, you'll get a WM_LBUTTONUP/OnMouseUp; at that stage, wrap things up by calling ReleaseCapture and putting the cursor back to normal shape.
Note that you'll get mouse move events both during the drag, and also if the user just happens to move the mouse pointer across your control, perhaps on the way to some other control. The simplest way to tell these two cases apart is to use a flag in your control that you set when you get the mouse down, and clear when you get the mouse up, and only process mouse move events if that flag is set.
The above describes the process in terms of plain Win32 APIs that you'd call from C/C++; but it looks like Delphi provides direct support for most or all of them.
edit: Possible Delphi implementation:
type
TForm1 = class(TForm)
Label1: TLabel;
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure FormPaint(Sender: TObject);
procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
FCacheWnd: HWND;
FCaptured: Boolean;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
const // the first item, the place where the crosshair is
ClickRect: TRect = (Left: 10; Top: 10; Right: 44; Bottom: 44);
procedure TForm1.FormPaint(Sender: TObject);
begin
// draw the control and the crosshair if no capturing
if GetCapture <> Handle then begin
DrawFrameControl(Canvas.Handle, ClickRect, 0, DFCS_BUTTONPUSH);
DrawIcon(Canvas.Handle, ClickRect.Left, ClickRect.Top,
Screen.Cursors[crCross]);
end;
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if (Button = mbLeft) and (Shift = [ssLeft])
and PtInRect(ClickRect, Point(X, Y)) then begin
// the second item, draw the control pressed,
// set the flag and the capture. FCacheWnd is used not to get
// window information for every mouse move - if the window under the
// mouse is not changed.
DrawFrameControl(Canvas.Handle, ClickRect, 0, DFCS_PUSHED);
FCacheWnd := 0;
FCaptured := True;
SetCapture(Handle);
Screen.Cursor := crCross; // the third item, set the cursor to crosshair.
end;
end;
function GetWndFromClientPoint(ClientWnd: HWND; Pt: TPoint): HWND;
begin
MapWindowPoints(ClientWnd, GetDesktopWindow, Pt, 1);
Result := WindowFromPoint(Pt);
end;
function GetWndInfo(Wnd: HWND): string;
var
ClassName: array [0..256] of Char;
begin
Result := '';
if IsWindow(Wnd) then begin
GetClassName(Wnd, ClassName, 256);
Result := Format('Window: %x [%s]', [Wnd, ClassName]);
if (GetWindowLong(Wnd, GWL_STYLE) and WS_CHILD) = WS_CHILD then begin
Wnd := GetAncestor(Wnd, GA_ROOT);
GetClassName(Wnd, ClassName, 256);
Result := Format(Result + sLineBreak + 'Top level: %x [%s]', [Wnd, ClassName]);
end;
end;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
Wnd: HWND;
begin
if FCaptured then begin
// fourth item, convert coordinates and find the window under the cursor
Wnd := GetWndFromClientPoint(Handle, Point(X, Y));
if Wnd <> FCacheWnd then
Label1.Caption := GetWndInfo(Wnd);
FCacheWnd := Wnd;
end;
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if FCaptured then begin
// fifth item
FCaptured := False;
ReleaseCapture;
InvalidateRect(Handle, #ClickRect, False); // invalidate pressed look
Screen.Cursor := crDefault;
end;
end;
Edit: It's gone, but you used to be able to download Delphi Window Spy by Eddie Shipman, from delphipages.com, which has turned into a festering heap of useless linkbait.

How to determine if the mouse cursor is inside a control

I'm adding support for mouse wheel movement to a TScrollBox (using the FormMouseWheel procedure) and I need to determine if the mouse is inside the component.
Basically I need to determine if the mouse is inside the TScrollBox so that I then handle the scrolling code accordingly.
Any idea on how to do this?
EDIT: Here's the code (including the answer to this question) as it might help others:
procedure TForm1.FormMouseWheel(Sender: TObject;
Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint;
var Handled: Boolean);
var
Msg: Cardinal;
Code: Cardinal;
I, ScrollLines: Integer;
ScrollBoxCursosPos: TPoint;
begin
//position of the mouse cursor related to TScrollBox
ScrollBoxCursosPos := ScrollBox1.ScreenToClient(Mouse.CursorPos);
if (PtInRect(ScrollBox1.ClientRect, ScrollBoxCursosPos)) then
begin
Handled := True;
If ssShift In Shift Then
msg := WM_HSCROLL
Else
msg := WM_VSCROLL;
If WheelDelta < 0 Then
code := SB_LINEDOWN
Else
code := SB_LINEUP;
ScrollLines:= Mouse.WheelScrollLines * 3;
for I:= 1 to ScrollLines do
ScrollBox1.Perform(Msg, Code, 0);
ScrollBox1.Perform(Msg, SB_ENDSCROLL, 0);
end;
end;
Mouse.CursorPos returns the mouse position in screen coordinates. You can convert this to "client" coordinates, ie coordinates relative to the control, by calling the control's ScreenToClient method.
So you'll have code something like this:
var
MyPoint : TPoint;
begin
MyPoint := ScrollBox1.ScreenToClient(Mouse.CursorPos);
if PtInRect(ScrollBox1.ClientRect, MyPoint) then
begin
// Mouse is inside the control, do something here
end;
end;
That will let you know if it's inside the control.
From the look of it you're implementing scrolling with the mousewheel? If so don't forget to call SystemParametersInfo with SPI_GETWHEELSCROLLLINES or possibly, if it's in your version of Delphi, Mouse.WheelScrollLines to find out how many lines to scroll per mousewheel increment. What that means to your app probably depends on what you've got in the scrollbox.
If you're planning to also implement middle-click-and-drag scrolling (I'm speculating here, this is well past what you asked about) you might want to get mouse events after the mouse has left the control or form until the user lets go the button, for example. If so, have a look at SetCapture and ReleaseCapture and this article. (That article uses those to see if the mouse is over a control (there, a form) although I think the code I wrote above is a better solution to that specific problem - point is they're handy for getting mouse information even when the mouse is not over your form or control.)
(Edit: I just noticed that Delphi 2010's TMouse has properties that wrap these API calls, WheelScrollLines and Capture. I'm not sure how recently they were added - I might just not have noticed them before - but on the assumption they're new-ish and because you don't say what version of Delphi you're using I'm leaving the above text and WinAPI references. If you're using a recent version have a look at the TMouse documentation.)
I am using the same method to scroll my scrollboxes using the mouse.
This is the event handler for the MouseWheel event of the form. It will scroll horizontally if you press shift key while scrolling:
procedure TForm1.FormMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
var
Msg: Cardinal;
Code: Cardinal;
I, ScrollLines: Integer;
begin
if IsCoordinateOverControl(MousePos, ScrollBox1) then
begin
Handled := True;
If ssShift In Shift Then
Msg := WM_HSCROLL
Else
Msg := WM_VSCROLL;
If WheelDelta < 0 Then
Code := SB_LINEDOWN
Else
Code := SB_LINEUP;
ScrollLines := Mouse.WheelScrollLines * 3;
for I := 1 to ScrollLines do
ScrollBox1.Perform(Msg, Code, 0);
ScrollBox1.Perform(Msg, SB_ENDSCROLL, 0);
end;
end;
You can use this function to check if the screen coordinate of the mouse is over your control:
function IsCoordinateOverControl(screenCoordinate: TPoint; control: TControl): Boolean;
var
p: TPoint;
r: TRect;
begin
Result := False;
p := control.ScreenToClient(screenCoordinate);
r := Rect(0, 0, control.Width, control.Height);
if PtInRect(r, p) then
Result := True;
end;
My Delphi knowledge is a bit rusty, but shouldn't there be MouseEnter, MouseLeave events? A quick google showed this. Does that help you?

Resources