ورود

View Full Version : سوال: تشخیص کلیک موس خارج از فرم برنامه



farshid_82
چهارشنبه 25 مرداد 1391, 13:31 عصر
سلام
دوستان قبل هر چیز بگم که hook جستجو کردم ولی جوابی پیدا نکردم.من یک فرم دارم که می خوام هروقت کاربر خارج از این فرم چه تو برنامه و چه خارج از برنامه خودم کلیک کرد متوجه بشم.لطفا راهنمائی کنید

Mask
چهارشنبه 25 مرداد 1391, 15:46 عصر
کلیک موس یا کیبورد؟

farshid_82
چهارشنبه 25 مرداد 1391, 18:09 عصر
کلیک موس

Ananas
چهارشنبه 25 مرداد 1391, 20:18 عصر
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if (GetAsyncKeyState(VK_LBUTTON) and $8000 = 0) then
Self.Caption := 'Up'
else
Self.Caption := 'Down';
end;

farshid_82
پنج شنبه 26 مرداد 1391, 01:02 صبح
ممنون از جوابتون ولی کد شما تو همون فرم هم عمل میکنه.برای مثال اگه بجای نوشتن down تو caption فرمان close بزنید و تو همون فرم کلیک کنید بسته میشه ولی من می خواهم فقط وقتی خارج از فرم کلیک شد فرم بسته بشه.تشکر

Ananas
پنج شنبه 26 مرداد 1391, 05:18 صبح
آها . پس شما کاری با بیرون نداری فقط می خوای با بیرون رفتن (کلیک کردن) موس برنامه رو ببندی. یه راهش اینه که از ShowModal استفاده کنی، یک راهش هم اینه که از OnMouseLeave استفاده کنی که بدون کلیک کردن بیرون از فرم اجرا میشه یه راه دیگه هم اینه که داخل همون تایمر ببینی الان رو ویندوز رو کدوم پنجره فکوسه مثلا :

procedure TForm1.Timer1Timer(Sender: TObject);
begin
if not Self.Focused then
Close;
end;

اشکالش اینه که اگه روی دکمه ها فکوس کنی بازم بسته میشه.
یک راه بهتر اینه که ببینی موس داخل rect پنجره ی برنامت هست یا نه بعد اگه نبود و موس هم کلیک شده بود برنامه رو ببندی. ولی اشکالش اینه که اگه کاربر با کیبورد با Alt+Tab بره روی برنامه ی دیگه، با این روش برنامهی ی شما تشخیص نمبده :

procedure TForm1.Timer1Timer(Sender: TObject);
var
p : TPoint;
r : TRect;
begin
GetCursorPos(p);
Winapi.Windows.GetWindowRect(Self.Handle, r);
if (r.Left <= p.X ) and
(p.X <= r.Right ) and
(r.Top <= p.Y ) and
(p.Y <= r.Bottom) then
Self.Caption := 'In Rect'
else
begin
Self.Caption := 'Out of Rect';
if (GetAsyncKeyState(VK_LBUTTON) and $8000 <> 0) then
Close;
end;
end;

Mask
پنج شنبه 26 مرداد 1391, 12:07 عصر
با استفاده از این دستور ، راست کلیک یا چپ کلیک بیرون برنامه رو میتونید متوجه بشوید.

procedure TForm1.Timer2Timer(Sender: TObject);
var i : integer;
asci : string;
begin
For i := 1 To 1000 Do
Begin
if GetAsyncKeyState(i)=-32767 then
begin
if Ord(i) in [1 .. 255] then
Begin
If (i=1)Then
Memo2.Lines.Append(' Left Click Mouse ')
else
If (i=2)Then
Memo2.Lines.Append(' Right Click Mouse ')
else
If (i=4)Then
Memo2.Lines.Append(' Middle Click Mouse ');
End;
end;
End;
end;

Ananas
پنج شنبه 26 مرداد 1391, 13:50 عصر
آره درست کار میکنه ولی چرا اینقدر پیچیدش کردی؟ حلقه ی 1000 تایی برای چی؟ اینطوری هم میشه خیلی راحت به جواب رسید:

procedure TForm1.Timer1Timer(Sender: TObject);
var
s : string;
begin
(Sender as TTimer).Interval := 1;
s := '';
if (GetAsyncKeyState(VK_LBUTTON) and $8000 <> 0) then
s := ' Left ';
if (GetAsyncKeyState(VK_MBUTTON) and $8000 <> 0) then
s := s + ' Middle ';
if (GetAsyncKeyState(VK_RBUTTON) and $8000 <> 0) then
s := s + ' Right ';
Self.Caption := s;
end;

ورودی تابع GetAsyncKeyState رو از ثابت هایی که با VK_ شروع میشن انتخاب کنید بهتره.

Mask
شنبه 11 آذر 1391, 12:36 عصر
آره درست کار میکنه ولی چرا اینقدر پیچیدش کردی؟ حلقه ی 1000 تایی برای چی؟ اینطوری هم میشه خیلی راحت به جواب رسید:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
procedure TForm1.Timer1Timer(Sender: TObject);
var
s : string;
begin
(Sender as TTimer).Interval := 1;
s := '';
if (GetAsyncKeyState(VK_LBUTTON) and $8000 <> 0) then
s := ' Left ';
if (GetAsyncKeyState(VK_MBUTTON) and $8000 <> 0) then
s := s + ' Middle ';
if (GetAsyncKeyState(VK_RBUTTON) and $8000 <> 0) then
s := s + ' Right ';
Self.Caption := s;
end;

ورودی تابع GetAsyncKeyState رو از ثابت هایی که با VK_ شروع میشن انتخاب کنید بهتره.
من 3 تا سوال دارم :
1 - اون $8000 چیه قضیش؟
2- آیا این روش فقط واسه تشخیص موس هست .؟ چطوری با همین روش باید کلید های کیبورد رو هم بررسی کنیم؟(مثل کلیدهی abcdefgh)
3- چرا وقتی یکبار روی جپ کلیک موس کلیک میکنم . تا مادامی که دستم رو چپ کلیکه . برنامه ما هی داره Left چاپ میکنه؟ در صورتی که من میخام وقتی رو چپ کلیک کلید کردم یکبار گزارش بشه که چپ کلیک شده و وقتی دستمو رو برداشتم و دوباره کلیک کردم ، دوباره یه Left دیگه گزارش بشه؟
ممنون از راهنماییتون.

om67av
شنبه 11 آذر 1391, 13:35 عصر
سلام دوست عزیز
اگه فقط منظورت خارج شدن از فرمه و با مختصات محل کلیک موس کاری نداری از رویداد ondeactive فرم استفاده کن مثلا:
procedure TForm2.FormDeactivate(Sender: TObject);
begin
Self.Close;
end;

با این کد هر وقت فوکوس از زوی فرم خارح بشه فرم بسته میشه.

Mask
شنبه 11 آذر 1391, 13:50 عصر
سلام دوست عزیز
اگه فقط منظورت خارج شدن از فرمه و با مختصات محل کلیک موس کاری نداری از رویداد ondeactive فرم استفاده کن مثلا:
procedure TForm2.FormDeactivate(Sender: TObject);
begin
Self.Close;
end;
با این کد هر وقت فوکوس از زوی فرم خارح بشه فرم بسته میشه.
اصلا سوال منو خوندید؟

Ananas
شنبه 11 آذر 1391, 14:18 عصر
اه! کی شلوغش کرده بود صفحه ارور میدا؟ کلی نوشته بودم همش شوت شد.:عصبانی++::لبخند:.
__________________________________________________ _____

- اون $8000 چیه قضیش؟
گشتم تا یک مرجع کامل پیدا کنم نتونستم. ولی چیزی که می دونم اینه که خروجی این تابع هر بیتش نشون دهنده ی چیز خاصی هست که قرار دادیه (مربوط به سخت افزاره و وضعیت دکمه رو تو اون لحظه مشخص میکنه) و تا جایی که می دونم بعضی بیتهاشم ممکنه معنی خاصی نداشته باشه و به اصطلاح reserve هست. اما بیت شانزدهم نشون دهنده ی فشرده بودن دکمه هست که ما با عمل and بیتی با عدد $8000 روی خروجی تابع ماسک می گذاریم تا ببینیم بیت 16 هم فعاله یا نه و اگه فعال باشه کلید فشرده هست.

2- آیا این روش فقط واسه تشخیص موس هست .؟ چطوری با همین روش باید کلید های کیبورد رو هم بررسی کنیم؟(مثل کلیدهی abcdefgh)
کلید های موش و کیبورد کد خاصی دارن که میشه با فرستادن به تابع GetAsyncKeyState وضعیتشون رو بررسی کرد. برای کلید های خاص مثل شیفت و کنترل و آلت و f ها ... که اسم دارن، ثوابتی تعریف شده که با VK_... شروع میشن و برای کلیدهای حروف هم میتونید از تابع VkKeyScan استفاده کنید که این تابع کد مجازی کلید رو برمیگردونه و شما اون مقدار رو به تابع GetAsyncKeyState می فرستید تا وضعیت کلید مشخص بشه. می تونید هم به طور مستقیم از کد اسکی حروف بزرگ استفاده کنید مثال برای کلید m :
if ((GetAsyncKeyState(Integer('M')) and $8000) <> 0) then
و یا :
if ((GetAsyncKeyState(VkKeyScan('m')) and $8000) <> 0) then
به بزرگی و کوچکی حروف تو دو مثال توجه کنید.

چرا وقتی یکبار روی جپ کلیک موس کلیک میکنم . تا مادامی که دستم رو چپ کلیکه . برنامه ما هی داره Left چاپ میکنه؟ در صورتی که من میخام وقتی رو چپ کلیک کلید کردم یکبار گزارش بشه که چپ کلیک شده و وقتی دستمو رو برداشتم و دوباره کلیک کردم ، دوباره یه Left دیگه گزارش بشه؟
دلیلش رو که بالا عرض کردم چون بیت 16 هم فقط فشرده بودن رو مشخص میکنه و اینکه قبلا فشرده نبوده و الان تازه فشرده شده فکر میکنم مربوط به بیت های دیگه باشه ولی نمی دونم دقیقا کدوم ها و با چه ترتیبی. ولی می تونید خودتون یک متغیر bool تعریف کنید و دستی آخرین وضعین فشرده بودن کلید رو ذخیره کنید.

Mask
شنبه 11 آذر 1391, 14:32 عصر
خیلی خیلی ممنون.

کلید های موش و کیبورد کد خاصی دارن که میشه با فرستادن به تابع GetAsyncKeyState وضعیتشون رو بررسی کرد. برای کلید های خاص مثل شیفت و کنترل و آلت و f ها ... که اسم دارن، ثوابتی تعریف شده که با VK_... شروع میشن و برای کلیدهای حروف هم میتونید از تابع VkKeyScan استفاده کنید که این تابع کد مجازی کلید رو برمیگردونه و شما اون مقدار رو به تابع GetAsyncKeyState می فرستید تا وضعیت کلید مشخص بشه. می تونید هم به طور مستقیم از کد اسکی حروف بزرگ استفاده کنید مثال برای کلید m :
بیشتر میخاستم بدونم میشه با یه خط کد فشار داده شدن هر کلید رو فهمید(منظورم کلیدهایی هست که VK ندارند مثل حروف)؟

دلیلش رو که بالا عرض کردم چون بیت 16 هم فقط فشرده بودن رو مشخص میکنه و اینکه قبلا فشرده نبوده و الان تازه فشرده شده فکر میکنم مربوط به بیت های دیگه باشه ولی نمی دونم دقیقا کدوم ها و با چه ترتیبی. ولی می تونید خودتون یک متغیر bool تعریف کنید و دستی آخرین وضعین فشرده بودن کلید رو ذخیره کنید.
این کاررو کردم :

if (s<>'')and(s<>Memo1.Lines[Memo1.Lines.Count-1]) then
Memo.Lines.Add(s);
اما مشکلی که پیش اومد این بود که اگه 2 بار Left رو پشت سر هم بزنم ، چون گفتم تکراری نباشه ، نمیتونه دوباره ثبت کنه. اینو باید چیکار کرد؟

Ananas
شنبه 11 آذر 1391, 15:05 عصر
بیشتر میخاستم بدونم میشه با یه خط کد فشار داده شدن هر کلید رو فهمید(منظورم کلیدهایی هست که VK ندارند مثل حروف)؟
بله به جواب رسیدید دیگه انشاالله؟

این کاررو کردم :
1
2
if (s<>'')and(s<>Memo1.Lines[Memo1.Lines.Count-1]) then
Memo.Lines.Add(s);




اما مشکلی که پیش اومد این بود که اگه 2 بار Left رو پشت سر هم بزنم ، چون گفتم تکراری نباشه ، نمیتونه دوباره ثبت کنه. اینو باید چیکار کرد؟
متوجه نشدم که می خواید چه کار کنید. ولی منظورم این بود که برای هر کلید یک پارامتر به عنوان آخرین وضعیت ذخیره کنید. مثلا تابع GetKeyboardState همین کار رو میکنه و شما هر بار که این تابع رو انجام میدید لیست وضعیت کلید ها رو که تو یک آرایه ی 256 تایی هستن رو Update میکنید و میبینید اگه آخرین وضعیت فلان دکمه فشرده شده بود ازش میگذرید و اگه نبود کاری که می خواید انجام میدید. تابع GetKeyboardState یک TKeyboardState ازتون می خواد که آرایه ای 256 تایی از Byte هست.

Mask
شنبه 11 آذر 1391, 15:13 عصر
بله به جواب رسیدید دیگه انشاالله؟
ممنون داداش. جملم بیشتر سوالی بود. میخاستم ببینم با یه خط کد میشه یا باید برای هر کلید یه if تعریف کنیم؟
ببینید ، کل کد من اینه :

procedure TForm1.Timer1Timer(Sender: TObject);
var s : string;
begin
(Sender as TTimer).Interval := 1;
s := '';
if (GetAsyncKeyState(VK_LBUTTON) and $8000 <> 0) then
s := ' Left ';
if (GetAsyncKeyState(VK_MBUTTON) and $8000 <> 0) then
s := ' Middle ';
if (GetAsyncKeyState(VK_RBUTTON) and $8000 <> 0) then
s := ' Right ';
if (s<>'')then//and(s<>M1.Lines[M1.Lines.Count-1]) then
M1.Lines.Add(s);
end;
من چیکار باید بکنم ؟ که وقتی رو چپ کلیک یه بار کلیک کردم ، تو ممو یکبار چاپ بشه Left نه 10 تا.

Ananas
شنبه 11 آذر 1391, 15:33 عصر
آها. دشمنت شرمنده. خوب ببین همون متغیر bool برای فقط یک دکمه کافیه. شما یک متغیر عمومی از نوع TKeyboardState تعریف کن مثلا توی فرم یا بیرون (نمی دونم، هرکجا) بعد اینطوری بنویس :

var
ks : TKeyboardState;



procedure TForm1.Timer1Timer(Sender: TObject);
var
ksLocal : TKeyboardState;
begin
(Sender as TTimer).Interval := 1;

GetKeyboardState(ksLocal);
if ((ksLocal[VK_LBUTTON] and $80) <> 0) and
((ks[ VK_LBUTTON] and $80) = 0) then
M1.SetSelText(' Left ');

if ((ksLocal[VK_MBUTTON] and $80) <> 0) and
((ks[ VK_MBUTTON] and $80) = 0) then
M1.SetSelText(' Middle ');

if ((ksLocal[VK_RBUTTON] and $80) <> 0) and
((ks[ VK_RBUTTON] and $80) = 0) then
M1.SetSelText(' Right ');

CopyMemory(@ks[0], @ksLocal[0], SizeOf(ks));
end;

منظورت همینه؟

Ananas
شنبه 11 آذر 1391, 15:48 عصر
و یا می تونی با تعریف یک تابع ساده ترش کنی مثلا :

var
Form1: TForm1;
ks , ksLocal : TKeyboardState;

implementation

{$R *.dfm}

procedure TForm1.Timer1Timer(Sender: TObject);
procedure CheckMyKey(vk_val : Integer; str : string);
begin
if ((ksLocal[vk_val] and $80) <> 0) and
((ks[ vk_val] and $80) = 0) then
M1.SetSelText(str);
end;
begin
(Sender as TTimer).Interval := 1;

GetKeyboardState(ksLocal);

CheckMyKey(VK_LBUTTON, ' Left ');
CheckMyKey(VK_MBUTTON, ' Middle ');
CheckMyKey(VK_RBUTTON, ' Right ');

CopyMemory(@ks[0], @ksLocal[0], SizeOf(ks));
end;

Ananas
شنبه 11 آذر 1391, 15:57 عصر
برای حروف هم می تونی این قسمت رو بهش اضافه کنی :

M1.Enabled := False;
for i := Integer('A') to Integer('Z') do
CheckMyKey(i, ' ' + WideChar(i) + ' ');

Mask
شنبه 11 آذر 1391, 15:59 عصر
ممنون. خیلی مهندسی شد:لبخندساده:
جرا کد بیرون برنامه کار نمیکنه.؟ من میخام اگه بیرون چپ کلیک زدم. برنامه من بفهمه.

Ananas
شنبه 11 آذر 1391, 16:10 عصر
آره راست میگی بیرون فرم کار نمیکنه! خوب تابع GetKeyboardState اینطوریه. میشه اونم به شکل یک تابع نوشت که تو یک حلقه از GetAsyncKeyState استفاده میکنه و لیست رو آپدیت میکنه. البته GetAsyncKeyState با $8000 باید and بشه و میشه برای اینکه تو یک بایت جا بشه اون رو 8 تا به راست شیفت کرد. مثلا :

procedure CheckAllKeyState(var ks_ : TKeyboardState);
var
i: Integer;
begin
for i := 0 to High(ks_) do
begin
ks_[i] := GetAsyncKeyState(i) shr 8;
end;
end;

Mask
شنبه 11 آذر 1391, 18:05 عصر
آره راست میگی بیرون فرم کار نمیکنه! خوب تابع GetKeyboardState اینطوریه. میشه اونم به شکل یک تابع نوشت که تو یک حلقه از GetAsyncKeyState استفاده میکنه و لیست رو آپدیت میکنه. البته GetAsyncKeyState با $8000 باید and بشه و میشه برای اینکه تو یک بایت جا بشه اون رو 8 تا به راست شیفت کرد. مثلا :

procedure CheckAllKeyState(var ks_ : TKeyboardState);
var
i: Integer;
begin
for i := 0 to High(ks_) do
begin
ks_[i] := GetAsyncKeyState(i) shr 8;
end;
end;


شکل کلی برنامه چه شکلی میشه؟

Ananas
شنبه 11 آذر 1391, 18:13 عصر
به جای GetKeyboardState از این استفاده کن.

Mask
شنبه 11 آذر 1391, 20:49 عصر
ممنون. خیلی جالب بود.
اما خیلی مهندسی شد.
من زیاد سر در نیاوردم.
میشه یه توضیحی در مورد کد بدید. شرمندتون شدم.

procedure TForm1.Timer1Timer(Sender: TObject);
procedure CheckMyKey(vk_val : Integer; str : string);
begin
if ((ksLocal[vk_val] and $80) <> 0) and
((ks[ vk_val] and $80) = 0) then
M11.SetSelText(str);
end;
procedure CheckAllKeyState(var ks_ : TKeyboardState);
var
i: Integer;
begin
for i := 0 to High(ks_) do
begin
ks_[i] := GetAsyncKeyState(i) shr 8;
end;
end;
var i:Integer;
begin
(Sender as TTimer).Interval := 1;

CheckAllKeyState(ksLocal);

CheckMyKey(VK_LBUTTON, ' Left ');
CheckMyKey(VK_MBUTTON, ' Middle ');
CheckMyKey(VK_RBUTTON, ' Right ');


for i := Integer('A') to Integer('Z') do
CheckMyKey(i, ' ' + WideChar(i) + ' ');

CopyMemory(@ks[0], @ksLocal[0], SizeOf(ks));
end;
در ضمن چطوری باید بقیه دکمه ها رو تو برنامه add کنم.؟

Ananas
شنبه 11 آذر 1391, 22:04 عصر
ممنون. خیلی جالب بود.
متشکر.

اما خیلی مهندسی شد.
از چه نظر؟

من زیاد سر در نیاوردم.
عه! چرا؟ چند تا for که دیگه چیزی نداره!

میشه یه توضیحی در مورد کد بدید.
نمی دونم کجا رو می خوای بگم ولی فکر کنم قسمت هگزادسیمال رو آشنا نباشی چون بقیش چیز خاصی نداره.
ببین من کد رو به شکل شبه کد برات می نویسم مرحله به مرحله :
------------------------------------------------------------
1 - یک لیست اصلی و یک لیست فرعی، به شکل سراسری (دسترسی در همه جای برنامه) از وضعیت تمام کلیدها بساز (برای هر کلید یک عدد یک بایتی)
2 - داخل تابع تایمر انجام بده :
{
3 - توقف تایمر رو حداقل کن (Interval = 1)
4 - لیست فرعی رو به روز کن. (وضعیت فعلی تمام کلید ها رو چک کن نتیجه رو بنویس داخل لیست فرعی)
5 - به ازای کلیدهای خواسته شده انجام بده :
{
6 - اگر وضعیت کلید در لیست فرعی فعال بود ولی در لیست اصلی فعال نبود انجام بده:
// یعنی آخرین بار فشرده نشده بود و الان تازه فشرده شده
//یعنی اولین بار بعد از فشردن جدید این کلید رو تو memo می نویسیم
{
7 - اسم کلید رو داخل memo بنویس.
}
}
8 - محتویات لیست فرعی رو به عنوان آخرین وضعیت، داخل لیست اصلی کپی کن.
}
------------------------------------------------
و در مورد عملیات بیتی هم توضیح اینکه :
همونطور که واضحه عدد صحیح که ما باهاش کار میکنیم از مجموعه ای از بیت ها ی کنار هم ساخته شده که ما اگه بخوایم or یا and رو روی تک تک بیتهای دو تا عدد صحیح اعمال کنیم خیلی ساده بینشون or یا and می گذاریم. مثل دو تا boolean که true یا false دارن اینجا هم 0 و 1 داریم که هر بیت عدد اول با متناظر خودش تو عدد دوم محاسبه میشه.
حالا اگه یه عدد داشته باشیم که بخوایم همه ی بیت های اون 0 بشن غیر از یک بیت خاص، باید چه کار کرد؟ باید اون رو با یک عدد دیگه که فقط اون بیتش 1 هست ، and کنیم. خوب ما بیت 16 هم رو می خوایم پس باید عدد $8000 رو بگذاریم که میشه از شیفت دادن عدد 1 به سمت چپ به مقدار 15 تا این عدد رو بدست بیاریم. و یا 2 به توان 15. بعد که عدد رو با $8000 and کردیم میبینیم حاصل صفر میشه یا عددی غیر از صفر ($8000) . عدد صفر همه ی بیت هاش صفره پس اگه حاصل and صفر باشه یعنی اون یک بیت هم صفره ولی اگه عدد صفر نباشه یعنی اون یک بیت به تنهایی عدد رو غیر از صفر کرده. and استفاده کردیم تا بیت های دیگه باعث صفر نشدن عدد نشن فقط بیت 16 هم آزمایش بشه.
حالا شیفت به راست هم که به تعداد 8 تا انجام دادم برای اینه که یک بایت فقط 8 تا بیت داره، خوب اگه بخوایم بیت 16 هم رو باهاش کار کنیم باید چه کار کنیم؟ ما 8 تا بیت اول رو نمی خوایم و فقط 8 تا بیت دوم و اونم فقط بیت 16 هم رو می خوایم. خوب میایم 8 تا ره راست شیفت میکنیم تا 8 تا بیت اول از سمت راست خارج بشن و 8 تای دوم بیان جای 8 تای اول. بعد این عدد رو باید بجای $8000 با $80 and کنیم. یعنی بیت 8 ام.