我正在创建一个自定义控件,它可以识别鼠标何时拖动,特别是使用 messages WM_LBUTTONDOWN
、WM_LBUTTONUP
和WM_MOUSEMOVE
. 当鼠标向下时,我捕获控件上的位置,然后当鼠标移动时,如果鼠标左键向下,我会进行更多处理(计算起点和终点之间)。
问题是,我希望鼠标脱离控件,甚至脱离窗体,但是当鼠标离开控件时,它不再捕获鼠标事件。有没有一种方法可以在没有鼠标悬停在控件上的情况下专门处理WM_MOUSEMOVE
and消息?WM_LBUTTONUP
我正在创建一个自定义控件,它可以识别鼠标何时拖动,特别是使用 messages WM_LBUTTONDOWN
、WM_LBUTTONUP
和WM_MOUSEMOVE
. 当鼠标向下时,我捕获控件上的位置,然后当鼠标移动时,如果鼠标左键向下,我会进行更多处理(计算起点和终点之间)。
问题是,我希望鼠标脱离控件,甚至脱离窗体,但是当鼠标离开控件时,它不再捕获鼠标事件。有没有一种方法可以在没有鼠标悬停在控件上的情况下专门处理WM_MOUSEMOVE
and消息?WM_LBUTTONUP
当光标移出控件时,您可以使用SetCapture/ReleaseCapture
Windows API 继续获取鼠标事件。
Releasecapture 将适用于 Wincontrols,另一种方式可能是 Mousehook。那只是一个演示......
unit MouseHook;
// 2012 by Thomas Wassermann
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm3 = class(TForm)
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form3: TForm3;
implementation
var
HookHandle: Cardinal;
Type
tagMSLLHOOKSTRUCT = record
POINT: TPoint;
mouseData: DWORD;
flags: DWORD;
time: DWORD;
dwExtraInfo: DWORD;
end;
TMSLLHOOKSTRUCT = tagMSLLHOOKSTRUCT;
PMSLLHOOKSTRUCT = ^TMSLLHOOKSTRUCT;
{$R *.dfm}
function LowLevelMouseProc(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; stdcall;
var
Delta:Smallint;
begin
if (nCode >= 0) then
begin
Form3.Caption := Format('X: %d Y: %d ', [PMSLLHOOKSTRUCT(lParam)^.Point.X, PMSLLHOOKSTRUCT(lParam)^.Point.Y]);
if wParam = WM_LButtonDOWN then Form3.Caption := Form3.Caption + ' LD';
if wParam = WM_LButtonUP then Form3.Caption := Form3.Caption + ' LU';
if wParam = WM_RButtonDOWN then Form3.Caption := Form3.Caption + ' RD';
if wParam = WM_RButtonUP then Form3.Caption := Form3.Caption + ' RU';
if wParam = WM_MOUSEMOVE then Form3.Caption := Form3.Caption + ' Move';
Delta := PMSLLHOOKSTRUCT(lParam)^.mouseData shr 16;
if wParam = WM_MOUSEWHEEL then
begin
Form3.Caption := Form3.Caption + ' Wheel ' ;
if Delta=120 then Form3.Caption := Form3.Caption + ' KLICK'
else if Delta > 0 then Form3.Caption := Form3.Caption +' UP'
else if Delta < 0 then Form3.Caption := Form3.Caption +' DOWN'
end;
if wParam = WM_MOUSEHWHEEL then
begin
Form3.Caption := Form3.Caption + ' HWheel';
if Delta=120 then Form3.Caption := Form3.Caption + ' KLICK'
else if Delta > 0 then Form3.Caption := Form3.Caption +' UP'
else if Delta < 0 then Form3.Caption := Form3.Caption +' DOWN'
end;
Form3.Caption := Form3.Caption +' >> '+ IntToStr(Delta)
end;
Result := CallNextHookEx(HookHandle, nCode, wParam, lParam);
end;
function InstallMouseHook: Boolean;
begin
Result := False;
if HookHandle = 0 then
begin
HookHandle := SetWindowsHookEx(WH_MOUSE_LL, @LowLevelMouseProc, hInstance, 0);
Result := HookHandle <> 0;
end;
end;
procedure TForm3.FormCreate(Sender: TObject);
begin
InstallMouseHook;
end;
procedure TForm3.FormDestroy(Sender: TObject);
begin
if HookHandle <> 0 then
UnhookWindowsHookEx(HookHandle);
end;
end.
我已经接受了上面的答案,但是我对这个实现的最终版本是完全不同的。我想我会分享我的想法,因为多次实现一个独特的鼠标钩有点棘手。
现在提供的演示bummi
已固定并内置到表单的单元中。我创建了一个新单元并将所有东西都包装在那里。棘手的部分是该函数LowLevelMouseProc
不能成为类的一部分。然而,在这个函数中,它会调用特定于钩子句柄 ( Result := CallNextHookEx(HookHandle, nCode, wParam, lParam);
) 的调用。所以我所做的是创建了一个桶 ( TList
),我在其中转储了我的鼠标对象的每个实例。当调用此函数时,它会遍历此存储桶并触发每个实例的相应事件。该模型还包括内置的线程安全保护(未经测试)。
这是完整的单元:
JD.Mouse.pas
unit JD.Mouse;
interface
uses
Windows, Classes, SysUtils, Messages, Controls;
type
TJDMouseButtonPoints = Array[TMouseButton] of TPoint;
TJDMouseButtonStates = Array[TMouseButton] of Boolean;
TJDMouse = class(TComponent)
private
FOnButtonUp: TMouseEvent;
FOnMove: TMouseMoveEvent;
FOnButtonDown: TMouseEvent;
FButtonPoints: TJDMouseButtonPoints;
FButtonStates: TJDMouseButtonStates;
procedure SetCursorPos(const Value: TPoint);
function GetCursorPos: TPoint;
procedure DoButtonDown(const IsDown: Boolean; const Button: TMouseButton;
const Shift: TShiftState; const X, Y: Integer);
procedure DoMove(const Shift: TShiftState; const X, Y: Integer);
public
constructor Create(AOwner: TComponent);
destructor Destroy; override;
published
property CursorPos: TPoint read GetCursorPos write SetCursorPos;
property OnButtonDown: TMouseEvent read FOnButtonDown write FOnButtonDown;
property OnButtonUp: TMouseEvent read FOnButtonUp write FOnButtonUp;
property OnMove: TMouseMoveEvent read FOnMove write FOnMove;
end;
implementation
var
_Hook: Cardinal;
_Bucket: TList;
_Lock: TRTLCriticalSection;
procedure LockMouse;
begin
EnterCriticalSection(_Lock);
end;
procedure UnlockMouse;
begin
LeaveCriticalSection(_Lock);
end;
type
tagMSLLHOOKSTRUCT = record
POINT: TPoint;
mouseData: DWORD;
flags: DWORD;
time: DWORD;
dwExtraInfo: DWORD;
end;
TMSLLHOOKSTRUCT = tagMSLLHOOKSTRUCT;
PMSLLHOOKSTRUCT = ^TMSLLHOOKSTRUCT;
function LowLevelMouseProc(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; stdcall;
var
X: Integer;
Delta: Smallint;
M: TJDMouse;
P: TPoint;
Shift: TShiftState;
begin
if (nCode >= 0) then begin
LockMouse;
try
Delta := PMSLLHOOKSTRUCT(lParam)^.mouseData shr 16;
try
for X := 0 to _Bucket.Count - 1 do begin
try
M:= TJDMouse(_Bucket[X]);
P:= Controls.Mouse.CursorPos;
//Shift:= .....; //TODO
case wParam of
WM_LBUTTONDOWN: begin
M.DoButtonDown(True, mbLeft, Shift, P.X, P.Y);
end;
WM_LBUTTONUP: begin
M.DoButtonDown(False, mbLeft, Shift, P.X, P.Y);
end;
WM_RBUTTONDOWN: begin
M.DoButtonDown(True, mbRight, Shift, P.X, P.Y);
end;
WM_RBUTTONUP: begin
M.DoButtonDown(False, mbRight, Shift, P.X, P.Y);
end;
WM_MBUTTONDOWN: begin
M.DoButtonDown(True, mbMiddle, Shift, P.X, P.Y);
end;
WM_MBUTTONUP: begin
M.DoButtonDown(False, mbMiddle, Shift, P.X, P.Y);
end;
WM_MOUSEMOVE: begin
M.DoMove(Shift, P.X, P.Y);
end;
WM_MOUSEWHEEL: begin
//TODO
end;
WM_MOUSEHWHEEL: begin
//TODO
end;
end;
except
on e: exception do begin
//TODO
end;
end;
end;
except
on e: exception do begin
//TODO
end;
end;
finally
UnlockMouse;
end;
end;
Result:= CallNextHookEx(_Hook, nCode, wParam, lParam);
end;
{ TJDMouse }
constructor TJDMouse.Create(AOwner: TComponent);
begin
LockMouse;
try
_Bucket.Add(Self); //Add self to bucket, registering to get events
finally
UnlockMouse;
end;
end;
destructor TJDMouse.Destroy;
begin
LockMouse;
try
_Bucket.Delete(_Bucket.IndexOf(Self)); //Remove self from bucket
finally
UnlockMouse;
end;
inherited;
end;
procedure TJDMouse.DoButtonDown(const IsDown: Boolean;
const Button: TMouseButton; const Shift: TShiftState; const X, Y: Integer);
begin
//Do not use lock, this is called from the lock already
if IsDown then begin
if assigned(FOnButtonDown) then
FOnButtonDown(Self, Button, Shift, X, Y);
end else begin
if assigned(FOnButtonUp) then
FOnButtonUp(Self, Button, Shift, X, Y);
end;
end;
procedure TJDMouse.DoMove(const Shift: TShiftState; const X, Y: Integer);
begin
//Do not use lock, this is called from the lock already
if assigned(FOnMove) then
FOnMove(Self, Shift, X, Y);
end;
function TJDMouse.GetCursorPos: TPoint;
begin
LockMouse;
try
Result:= Controls.Mouse.CursorPos;
finally
UnlockMouse;
end;
end;
procedure TJDMouse.SetCursorPos(const Value: TPoint);
begin
LockMouse;
try
Controls.Mouse.CursorPos:= Value;
finally
UnlockMouse;
end;
end;
initialization
InitializeCriticalSection(_Lock);
_Bucket:= TList.Create;
_Hook:= SetWindowsHookEx(WH_MOUSE_LL, @LowLevelMouseProc, hInstance, 0);
finalization
UnhookWindowsHookEx(_Hook);
_Bucket.Free;
DeleteCriticalSection(_Lock);
end.
以下是它的实现方式:
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FMouse: TJDMouse;
procedure MouseButtonDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure MouseButtonUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure MouseMoved(Sender: TObject; Shift: TShiftState; X, Y: Integer);
end;
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
FMouse:= TJDMouse.Create(nil);
FMouse.OnButtonDown:= MouseButtonDown;
FMouse.OnButtonUp:= MouseButtonUp;
FMouse.OnMove:= MouseMoved;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FMouse.Free;
end;
procedure TForm1.MouseButtonDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
end;
procedure TForm1.MouseButtonUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
end;
procedure TForm1.MouseMoved(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
end;
end.
TControlStyle.csCaptureMouse
如果您使用 VCL 控件,则可以使用该标志。我不确定是否有 FMX 对应物。相关文档在这里。
我在我csCaptureMouse
的许多自定义控件中使用它并且效果很好。