線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:3467
推到 Plurk!
推到 Facebook!

如何判斷滑鼠離開Form

答題得分者是:TWY
老大仔
尊榮會員


發表:78
回覆:837
積分:1088
註冊:2006-07-06

發送簡訊給我
#1 引用回覆 回覆 發表時間:2009-09-10 09:07:28 IP:59.120.xxx.xxx 未訂閱
我想要判斷滑鼠是否有移開form或有在form之中
有找了一些文章
而我在以下的文章中找到了答案
http://delphi.ktop.com.tw/board.php?cid=168&fid=914&tid=31817
但就猶如文章所提到的
當滑鼠移動太快時
這個效果就會失效

所以想請問站上的大大們
是否有解決的方法呢?

PS : Delphi7 win2000
TWY
高階會員


發表:2
回覆:133
積分:152
註冊:2009-09-02

發送簡訊給我
#2 引用回覆 回覆 發表時間:2009-09-11 09:39:34 IP:211.21.xxx.xxx 訂閱
我剛試了兩三種過去看到的不同寫法,但都脫離不了偵測Message的方式,一樣速度太快就失效,似乎VCL Control對於滑鼠太快移動的Message會忽略...!?
放個 ApplicationEvents.OnMessage 來抓出所有 Message 觀察,發現其實處理 Message 的頻率並不是很"高速"的,MouseEnter MouseLeave Message 太快速移動根本不會出現,VCL 當然抓不到...(我在想是否因為 OS 效能考量而作此設計與限制呢,因為所有 Message 都要 eat 的話應該會很忙吧)

抱歉我無法提供你想要的答案,只能分享小弟測試結果與看法大家討論討論。
若Message方式無法突破,可能要考慮是否可以接受其他(可能較笨)的方式了...
(例如 Timer 偵測滑鼠位置是否在 Form 範圍內...<<有點笨又不夠技巧的方式 不過正確性應沒問題>>)
編輯記錄
TWY 重新編輯於 2009-09-11 09:58:55, 註解 無‧
TWY
高階會員


發表:2
回覆:133
積分:152
註冊:2009-09-02

發送簡訊給我
#3 引用回覆 回覆 發表時間:2009-09-11 10:35:56 IP:211.21.xxx.xxx 訂閱
有啦,剛突然想到 Hook 可以攔截到更底層的 Message,剛實際試了一下滑鼠再快速移動都可以抓到!
(約四年多前某外國網站看到的,該網站已關閉...)

↓先將這個 HookMouse.dpr 編譯成 HookMouse.DLL

[code delphi]
library HookMouse;

{Demo de Hook de Rat鏮 a nivel de sistema, Radikal.}

uses
Windows,
Messages;

const
CM_MANDA_DATOS = WM_USER $1000;

type
TCompartido = record
Receptor,
wHitTestCode,
x,y,
Ventana : hwnd;
end;
PCompartido =^TCompartido;


var
HookDeMouse : HHook;
FicheroM : THandle;
Compartido : PCompartido;


function CallBackDelHook( Code : Integer;
wParam : WPARAM;
lParam : LPARAM
) : LRESULT; stdcall;

var
DatosMouse : PMouseHookStruct;
Intentos : integer;

{Esta es la funcion CallBack a la cual llamar?el hook.}
{This is the CallBack function called by he Hook}
begin
{Si hay un nuevo evento de raton...}
{if there is a new mouse event...}
if code=HC_ACTION then
begin
{Miramos si existe el fichero}
{if the mapfile exists}
FicheroM:=OpenFileMapping(FILE_MAP_WRITE,False,'ElReceptor');
{Si no existe, no enviamos nada a la aplicacion receptora}
{If dont, send nothing to receiver application}
if FicheroM<>0 then
begin
Compartido:=MapViewOfFile(FicheroM,FILE_MAP_WRITE,0,0,0);

{Apuntamos hacia los datos del evento del raton}
DatosMouse:=Pointer(lparam);

{Los guardamos en el fichero de memoria}
Compartido^.Ventana:=DatosMouse^.hwnd;
Compartido^.x:=DatosMouse^.pt.x;
Compartido^.y:=DatosMouse^.pt.y;

{Avisamos al receptor para que atienda el nuevo evento}
{Say to receiver that there is a new event}
PostMessage(Compartido^.Receptor,CM_MANDA_DATOS,wParam,lParam);

UnmapViewOfFile(Compartido);
CloseHandle(FicheroM);
end;
end;
{Llamamos al siguiente hook de la cadena}
{call to next hook of the chain}
Result := CallNextHookEx(HookDeMouse, Code, wParam, lParam)
end;

procedure HookOn; stdcall;
{Procedure que instala el hook}
{procedure for install the hook}
begin
HookDeMouse:=SetWindowsHookEx(WH_MOUSE, @CallBackDelHook, HInstance , 0);
end;

// stops this type of watch
procedure HookOff; stdcall;
begin
{procedure para desinstalar el hook}
{procedure to uninstall the hook}
UnhookWindowsHookEx(HookDeMouse);
end;

exports
{Exportamos las procedures...}
{Export the procedures}
HookOn,
HookOff;

begin

end.

[/code]

↓這是引用該 DLL 的測試 Form (放了三個元件 Memo1 , Label1 , Label2 )

[code delphi]
unit unHook_CaptureAllMouseMsg;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;


const
NombreDLL = 'HookMouse.dll';
CM_MANDA_DATOS = WM_USER $1000;


type
TCompartido = record
Receptor,
wHitTestCode,
x,y,
Ventana : hwnd;
end;
PCompartido =^TCompartido;
THookMouse=procedure; stdcall;



type
TfrmHook_CaptureAllMouseMsg = class(TForm)
Memo1: TMemo;
Label1: TLabel;
Label2: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
FicheroM : THandle;
Compartido : PCompartido;
HandleDLL : THandle;
HookOn,
HookOff : THookMouse;
procedure LlegaDelHook(var message: TMessage); message CM_MANDA_DATOS;
public
{ Public declarations }
end;

var
frmHook_CaptureAllMouseMsg: TfrmHook_CaptureAllMouseMsg;

implementation

{$R *.dfm}

{ TfrmHook_CaptureAllMouseMsg }

procedure TfrmHook_CaptureAllMouseMsg.LlegaDelHook(var message: TMessage);
var
DatosMouse : PMouseHookStruct;
NombreVentana : array [0..200] of char;
Accion : string;
begin
with Compartido^ do
begin
{Coordenadas del raton}
{Mouse coordinates}
Label1.caption:='[' IntToStr(x) ':' IntToStr(y) ']';
end;
{Nombre de la ventana donde esta el raton}
{Window Name}
GetWindowText(Compartido^.Ventana,@NombreVentana,200);
Label2.Caption:=NombreVentana;

case Message.wParam of
WM_LBUTTONDBLCLK : Accion:='WM_LBUTTONDBLCLK ';
WM_LBUTTONDOWN : Accion:='WM_LBUTTONDOWN ';
WM_LBUTTONUP : Accion:='WM_LBUTTONUP ';
WM_MBUTTONDBLCLK : Accion:='WM_MBUTTONDBLCLK ';
WM_MBUTTONDOWN : Accion:='WM_MBUTTONDOWN ';
WM_MBUTTONUP : Accion:='WM_MBUTTONUP ';
WM_MOUSEMOVE : Accion:='WM_MOUSEMOVE ';
WM_NCHITTEST : Accion:='WM_NCHITTEST ';
WM_NCLBUTTONDBLCLK : Accion:='WM_NCLBUTTONDBLCLK';
WM_NCLBUTTONDOWN : Accion:='WM_NCLBUTTONDOWN ';
WM_NCLBUTTONUP : Accion:='WM_NCLBUTTONUP ';
WM_NCMBUTTONDBLCLK : Accion:='WM_NCMBUTTONDBLCLK';
WM_NCMBUTTONDOWN : Accion:='WM_NCMBUTTONDOWN ';
WM_NCMBUTTONUP : Accion:='WM_NCMBUTTONUP ';
WM_NCMOUSEMOVE : Accion:='WM_NCMOUSEMOVE ';
WM_NCRBUTTONDBLCLK : Accion:='WM_NCRBUTTONDBLCLK';
WM_NCRBUTTONDOWN : Accion:='WM_NCRBUTTONDOWN ';
WM_NCRBUTTONUP : Accion:='WM_NCRBUTTONUP ';
WM_RBUTTONDBLCLK : Accion:='WM_RBUTTONDBLCLK ';
WM_RBUTTONDOWN : Accion:='WM_RBUTTONDOWN ';
WM_RBUTTONUP : Accion:='WM_RBUTTONUP ';
end;
Memo1.Lines.Append(Accion);

end;


procedure TfrmHook_CaptureAllMouseMsg.FormCreate(Sender: TObject);
begin
HandleDLL:=LoadLibrary( PChar(ExtractFilePath(Application.Exename)
NombreDLL ) );
if HandleDLL = 0 then raise Exception.Create('No se pudo cargar la DLL');

@HookOn :=GetProcAddress(HandleDLL, 'HookOn');
@HookOff:=GetProcAddress(HandleDLL, 'HookOff');

if not assigned(HookOn) or
not assigned(HookOff) then
raise Exception.Create('No se encontraron las funciones en la DLL' #13
'Cannot find the required DLL functions');

{Creamos el fichero de memoria}
FicheroM:=CreateFileMapping( $FFFFFFFF,
nil,
PAGE_READWRITE,
0,
SizeOf(Compartido),
'ElReceptor');

{Si no se cre?el fichero, error}
if FicheroM=0 then
raise Exception.Create( 'Error al crear el fichero'
'/Error while create file');

{Direccionamos nuestra estructura al fichero de memoria}
Compartido:=MapViewOfFile(FicheroM,FILE_MAP_WRITE,0,0,0);

{Escribimos datos en el fichero de memoria}
Compartido^.Receptor:=Handle;
HookOn;

end;

procedure TfrmHook_CaptureAllMouseMsg.FormDestroy(Sender: TObject);
begin
{Desactivamos el Hook}
{Uninstall the Hook}
if Assigned(HookOff) then HookOff;

{Liberamos la DLL}
{Free the DLL}
if HandleDLL<>0 then
FreeLibrary(HandleDLL);

{Cerramos la vista del fichero y el fichero}
{Close the memfile and the View}
if FicheroM<>0 then
begin
UnmapViewOfFile(Compartido);
CloseHandle(FicheroM);
end;

end;

end.

[/code]



老大仔
尊榮會員


發表:78
回覆:837
積分:1088
註冊:2006-07-06

發送簡訊給我
#4 引用回覆 回覆 發表時間:2009-09-11 13:58:44 IP:59.120.xxx.xxx 未訂閱
to TMY大大 : 
我剛開始也找了些方法
後來才用我貼的那個網址去試
只是無奈就是有那樣的缺點@@
所以有點苦惱...

而您提供的方法
在小弟試用過後確實可行
而且假如還真的以"高速"的方法移動
也都真的抓的到呢@@
真是太神奇了~

不過~至於您文章提到的
用Timer去偵測位置
我可以請問一下要怎麼去偵測嗎?
想說多學一招也好
TWY
高階會員


發表:2
回覆:133
積分:152
註冊:2009-09-02

發送簡訊給我
#5 引用回覆 回覆 發表時間:2009-09-11 14:56:36 IP:211.21.xxx.xxx 訂閱
    我原本的意思是假設 Form 最左上角座標(X=10 Y=10),右上(X=20 Y=10),左下(X=10 Y=20),右下(X=20 Y=20)
放一顆 Timer 元件每 0.1 秒去抓取游標目前 X/Y 座標值,若不在 Form 的範圍內表示游標離開 Form 了...
其實這個方法並不是很理想,Timer設頻率太高性能不好,太低判讀精準度不好,所以只是個粗略想法參考罷了。


===================引 用 老大仔 文 章===================
不過~至於您文章提到的
用Timer去偵測位置
我可以請問一下要怎麼去偵測嗎?
想說多學一招也好
老大仔
尊榮會員


發表:78
回覆:837
積分:1088
註冊:2006-07-06

發送簡訊給我
#6 引用回覆 回覆 發表時間:2009-09-11 17:28:14 IP:59.120.xxx.xxx 未訂閱
謝謝TWY大大的解說~
其實...我在稍早時也自行去try看看
雖然timer也是可以弄
但就猶如TWY大大所講的
效果也有差
所以最後還是用HookMouse的方法來實作
而且準度還好到沒話說呢~

最後還是謝謝TWY大大提供的方法~
RootKit
資深會員


發表:16
回覆:358
積分:419
註冊:2008-01-02

發送簡訊給我
#7 引用回覆 回覆 發表時間:2009-09-11 23:23:51 IP:122.126.xxx.xxx 訂閱
實在不懂為什麼有那麼多人喜歡用 HOOK 方式解決問題。
不會感覺大材小用嗎?越瞭解 HOOK 的人就越不敢亂用是非常謹慎的。

[code delphi]

type
TForm1 = class(TForm)
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
private
procedure WMMOUSELEAVE(var Msg: TMessage);message WM_MOUSELEAVE;
public
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,Y: Integer);
var
EventTrack : TTrackMouseEvent;
begin
With EventTrack do
begin
cbSize := SizeOf(EventTrack);
hwndTrack := Handle;
dwFlags := TME_LEAVE;
dwHoverTime := 0;
end;
TrackMouseEvent(EventTrack);
end;

procedure TForm1.WMMOUSELEAVE(var Msg :TMessage);
begin
Msg.Result := 0;
end;


[/code]

TrackMouseEvent API 可查閱 MSDN。

參考

===================引 用 老大仔 文 章===================
謝謝TWY大大的解說~
其實...我在稍早時也自行去try看看
雖然timer也是可以弄
但就猶如TWY大大所講的
效果也有差
所以最後還是用HookMouse的方法來實作
而且準度還好到沒話說呢~

最後還是謝謝TWY大大提供的方法~
TWY
高階會員


發表:2
回覆:133
積分:152
註冊:2009-09-02

發送簡訊給我
#8 引用回覆 回覆 發表時間:2009-09-12 07:10:18 IP:220.132.xxx.xxx 訂閱
謝謝 Rootkit 大大的忠告,的確 HOOK 控制到 System Level 很接近底層核心了,權限越高危險越大...
有時候用 HOOK 是因為找不到其他更好解決方式的不得已,但最好還是要對欲使用的部份做更進一步了解才好。

我之前貼的範例是某已關閉的國外網站看到的,功能為跨 Process 攔截出所有 Mouse Message(正因功能強大所以吸引我保存下來),我自己沒實際用到所以也沒有深入去研究過,用在 老大仔 的問題上還真是殺雞用了牛刀了。

話說回來,TrackMouseEvent API 的確小巧且可符合這個問題需求,我又多學到一招了,謝謝 Rootkit 大大的分享。



老大仔
尊榮會員


發表:78
回覆:837
積分:1088
註冊:2006-07-06

發送簡訊給我
#9 引用回覆 回覆 發表時間:2009-09-14 08:30:17 IP:59.120.xxx.xxx 未訂閱
嗯嗯~也謝謝RootKit大大的分享
您的方法確實符合我的要求
而且也比較簡變一點
就猶如TWY大大講的~殺雞用了牛刀@@
這方法果然比較有好處
且也用不到HOOK的方法就能達成
真是好用呢~

Sorry~假日沒上網
所以現在才回文
請別見怪^^
系統時間:2024-04-20 2:38:08
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!