PDA

View Full Version : سوال: چگونگی تعیین شرط بازای رخ ندادن یک رویداد



Ms.lemon
شنبه 14 آبان 1390, 23:09 عصر
سلام دوستان، من دارم با دلفی یه برنامه "Car Racing" می نویسم، که قراره در یک جاده یک ماشین، توسط کاربر کنترل بشه. برای کنترل ماشین هم از دکمه های جهتی استفاده کردم،
UP: به ماشین شتاب میده
Down: ترمز
Left: حرکت به چپ
Right: حرکت به راست
از رویداد On key down استفاده کردم. اما وقتی کلیدی فشرده نشه، بایستی ماشین با سرعت قبلی به حرکت خودش ادامه بده. اگه بخوام این قسمتو بنویسم (رویداد on key down رخ ندهد)، از چه رویدادی باید استفاده کنم؟! تقریباً تمامی رویدادهای فرمو چک کردم.

ممنون میشم کمکم کنید

SAASTN
یک شنبه 15 آبان 1390, 02:28 صبح
کلا برای همچین مواردی تکیه به یه رویداد کار رو راه نمیندازه. مثلا وقتی که کاربر بالا و راست رو با هم می گیره OnKeyDown جداگانه به ازای هر کدوم از کلیدا فراخونی میشه. یه راه حل نگه داشتن کلیدهای فشرده شده توی یه آرایه است، مثل:
TForm1 = class(TForm)
Edit1: TEdit;
Image1: TImage;
procedure Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure Edit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
private
{ Private declarations }
public
KeyDowns: array [VK_LBUTTON..VK_OEM_CLEAR] of Boolean;
procedure ChangeState(Key: Word; Down: Boolean);
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ChangeState(Key: Word; Down: Boolean);
const
BrushColor: array [Boolean] of TColor = (clWhite, clRed);
begin
if Key in [VK_LBUTTON..VK_OEM_CLEAR] then
begin
KeyDowns[Key] := Down;
Image1.Canvas.Brush.Color := BrushColor[Down];
Image1.Canvas.FillRect(Rect(Key * 2, 0, Key * 2 + 2, Image1.Height));
end;
if
KeyDowns[VK_LEFT] or KeyDowns[VK_RIGHT] or
KeyDowns[VK_UP] or KeyDowns[VK_DOWN] or Application.Terminated
then
Color := BrushColor[False]
else
begin
// all arrows are up
Color := BrushColor[True];
Sleep(10);
Application.ProcessMessages;
ChangeState(MAXWORD, False);
end;
end;

procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
ChangeState(Key, True);
end;

procedure TForm1.Edit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
ChangeState(Key, False);
end;

برای اجرا یه Edit رو فرم قرار بدید و OnKeyDown و OnKeyUpش رو با Edit1KeyDown و Edit1KeyUp ست کنید. یه Image هم رو صفحه قرار بدید و عرضش رو بیشتر از 512 بذارید.

به این ترتیب میشه توی ChangeState بررسی کرد که در صورتی که هیچ کدوم از کلید های جهت نما فشرده نشده باشن یه عملیات خاصی صورت بگیره. اون قضیه Sleep و ProcessMessages و اینا هم برای شبیه سازی اجرای متوالی ChangeState هست. چون مثلا وقتی کلید پائین رو نگه می داریم KeyDown مرتبا فراخونی میشه، اگه روشی که برنامه رو پیاده می کنید به این توالی متکی نیست (که بهتره نباشه) اون بخش رو می تونید حذف کنید.
بعضی وقتا هم لازمه که KeyDowns به جای Boolean از نوع Integer تعریف بشه تا مدت زمانی که کلید پائین بوده هم مشخص بشه، اینطوری مثلا می تونی اگه کاربر پائین رو 2-3 صدم ثانیه نگه داشت یه نیش ترمز بزنی ولی اگه بیشتر شد دستی و بکشی. :چشمک:

Ms.lemon
دوشنبه 16 آبان 1390, 10:11 صبح
بابت لطفتون متشکرم. کد و توضیحاتتون را مطالعه کردم، ممنون میشم اگه این دو سئوالو هم جواب بدید:

- اگه اشتباه نکنم آرایه [VK_LBUTTON..VK_OEM_CLEAR]، آرایه ای از کلیدهای فشرده شده است، مقادیر VK_LBUTTON و VK_OEM_CLEAR چه مقادیری اند؟

- دستور Application.ProcessMessages; دقیقاً چیکار میکنه؟:متفکر:

BORHAN TEC
دوشنبه 16 آبان 1390, 13:41 عصر
- دستور Application.ProcessMessages; دقیقاً چیکار میکنه؟
پردازش پیام هایی که در صف قرار دارند را انجام می دهد و بیشتر در مواردی به کار می ره که نمی خواهیم حالت هنگ به برنامه دست بده(البته این یکی از کاربردهایش است!!!). این روش در بعضی از موارد ساده خوب جواب میده ولی در موارد پیچیده تر بهتره که از روش های دیگری استفاده بشه.

nsco_nsco
دوشنبه 16 آبان 1390, 21:02 عصر
سلام به نظر من اگر بیاید و یک حلقه اصلی برای برنامتون بنویسین و بعد این کلید ها را در زیر برنامه ها استفاده کنید و سپس اگر شرطی انجام شد که هیچ و اگر نشد بره تو حلقه اصلی یعنی چند ثانیه بعد از نبود شرط این طور میشه فهمید موفق باشید :قلب:

Ms.lemon
دوشنبه 16 آبان 1390, 21:12 عصر
الان همین کارو کردم، اما Stack overflow میده

nsco_nsco
دوشنبه 16 آبان 1390, 22:43 عصر
سلام این ارور رو نمی دونم اما فکر کنم در چرخش مشکل براش پیش میاد بهتره در دلفی چرخه را یک تایمر بگیری موفق باشی:قلب:

Felony
دوشنبه 16 آبان 1390, 22:52 عصر
سلام دوستان، من دارم با دلفی یه برنامه "Car Racing" می نویسم، که قراره در یک جاده یک ماشین، توسط کاربر کنترل بشه. برای کنترل ماشین هم از دکمه های جهتی استفاده کردم،
UP: به ماشین شتاب میده
Down: ترمز
Left: حرکت به چپ
Right: حرکت به راست
از رویداد On key down استفاده کردم. اما وقتی کلیدی فشرده نشه، بایستی ماشین با سرعت قبلی به حرکت خودش ادامه بده. اگه بخوام این قسمتو بنویسم (رویداد on key down رخ ندهد)، از چه رویدادی باید استفاده کنم؟! تقریباً تمامی رویدادهای فرمو چک کردم.

ممنون میشم کمکم کنید
کدوم قسمتو بنویسی ؟ اینکه ماشین با سرعت قبلی خودش حرکت کنه ؟
خوب فرض کن یک متغییر با نام Speed در بخش private برنامه داریم ، حالا با هر بار زدن دکمه Up یک واحد به سرعت اضافه میشه و با هر بار زدن دکمه Down یک واحد از Speed کم میشه ، وقتی هم که هیچ دکمه ای رو نزنی مقدار Speed آخرین سرعتی هست که درون Speed ذخیره شده بوده !

من درست متوجه شدم ؟ مشکلتون همین بود ؟!

Ms.lemon
دوشنبه 16 آبان 1390, 23:05 عصر
کدوم قسمتو بنویسی ؟ اینکه ماشین با سرعت قبلی خودش حرکت کنه ؟
خوب فرض کن یک متغییر با نام Speed در بخش private برنامه داریم ، حالا با هر بار زدن دکمه Up یک واحد به سرعت اضافه میشه و با هر بار زدن دکمه Down یک واحد از Speed کم میشه ، وقتی هم که هیچ دکمه ای رو نزنی مقدار Speed آخرین سرعتی هست که درون Speed ذخیره شده بوده !

من درست متوجه شدم ؟ مشکلتون همین بود ؟!

بله آقای تاجیک! مشکل موقعیه که هیچ دکمه ای زده نمیشه، از رویداد onkeydown برای دکمه های جهتی استفاده کردم، و از appliction.processmessage ، اما بعد از زدن چند دکمه stackoverflow میده

Ms.lemon
دوشنبه 16 آبان 1390, 23:07 عصر
سلام این ارور رو نمی دونم اما فکر کنم در چرخش مشکل براش پیش میاد بهتره در دلفی چرخه را یک تایمر بگیری موفق باشی:قلب:

فعلاً چرخش براش در نظر نگرفتم؛ فقط مستقیم حرکت می کنه و چپ و راست میره، یا سرعتشو کم می کنه!

SAASTN
دوشنبه 16 آبان 1390, 23:41 عصر
اگه اشتباه نکنم آرایه [VK_LBUTTON..VK_OEM_CLEAR]، آرایه ای از کلیدهای فشرده شده است، مقادیر VK_LBUTTON و VK_OEM_CLEAR چه مقادیری اند؟
خوب ما برای تعین یه آرایه Static باید یه Subrange معرفی کنیم. حالا ما می خوایم یه آرایه تعریف کنیم به تعداد کلیدهایی که ممکنه فشرده بشن. توی یونیت Windows کد کلیدهای مجازی لیست شدن، این لیست از VK_LBUTTON یا 1 تا VK_OEM_CLEAR یا 254 تعریف شدن. پس درواقع Subrange ما برای تعریف اون آرایه 1..254 هست. از طرفی پارامتر Key توی رویدادهای OnKeyDown و OnKeyUp هم حاوی همین کد کلید مجازی مربوط به کلیدی هست که فشرده شده.
اما اینکه هر کدوم از اون دوتا ثابت دقیقا معادل کدوم کلید ها هستند، VK_LBUTTON کد مربوط به کلید چپ موس هست، البته باکلیک کلید چپ موس، رویداد OnKeyDown فراخونی نمی شه. شاید توی لپ تاپ ها با فشردن کلیک چپ موس Touch Pad رویداد OnKeyDown رو با این مقدار فراخونی کنه. در مورد VK_OEM_CLEAR توی راهنما فقط نوشته کلید Clear. البته یه سری کلید های دیگه هم هستند که توشون OEM وجود داره که گویا مربوط به یه سری کلیدهای ترکیبی توی ویندوزهای XP و 2000 هستند.

دستور Application.ProcessMessages; دقیقاً چیکار میکنه؟
در مورد عملکرد کلی این دستور که آقای عشیری توضیح دادن، ولی اینکه اون دستور برای چی اونجا استفاده شده: همونطور که قبلا هم گفتم می خواستم به نوعی توالی تکرار یه متد رو در صورتی که هیچ OnKeyDown یا OnkeyUpی فراخونی نشه پیاده سازی کنم. اگر Sleep رو قرار نمی دادم ChangeState خوش رو بی نهایت بار در یک آن صدا می کرد که منجر به Stack Overflow میشد. من اون Sleep رو قرار دادم تا تعداد فراخونی کاهش پیدا کنه. از طرف دیگه وقتی اون تابع متوالیا خودش رو فراخونی می کنه در واقع Process رو دائما درگیر خودش نگه می داره و حالتی شبیه به هنگ کردن برنامه بوجود میاد. Application.ProcessMessages باعث میشه تا قبل از هر بار فراخونی ChangeState چک بشه که در صورتی Messageی برای Rpaint فرم یا کنترل ها ارسال شده باشه، یا کاربر موس رو فشرده باشه یا کلییدی رو زده باشه، اول اون Message ها Handle بشن و بعد ChangeState فراخونی بشه.

الان همین کارو کردم، اما Stack overflow میده
احتمالا مشکلی شبیه به چیزی که بالا توضیح دادم پیش میاد، یا حلقه بی نهایت تولید کردید یا شرایطی پیش میاد که یه تابع بازگشتی بدون شرط شکست خودش رو فراخونی کنه. اگه می خوای یه کار اصولی انجام بدی بهتره یه تحقیق در زمینه Multi-Threading انجام بدی و محاسباتت رو ببری توی یه ترد دیگه و ترد اصلی رو فقط به گرفتن ورودی های کاربر و نمایش تصویر اختصاص بدی. کار دیگه ای هم که میشه کرد (و من در زمان نوشتن اون برنامه بالایی به یادش نبودم) اینه که از تب Additional یه کنترل ApplicationEvents روی فرم قرار بدین و فرا خونی متد اصلی تون رو توی رویداد OnIdle قرار بدین. این رویداد زمانی فراخونی میشه که ترد اصلی برنامه بی کار میشه.

بهتره در دلفی چرخه را یک تایمر بگیری
این کار هم مشکل رو حل می کنه اما مشکلش اینه که Interval تایمر یه مقدار ثابته و معمولا به یه مقدار تخمینی ست میشه. حالا اگر پروسس مربوط به OnTimer بیشتر از مقدار Interval طول بکشه، همون شرایط شبه هنگ بوجود میاد و اگر هم کمتر طول بکشه Performance برنامه کمتر از حداکثر توان برنامه میشه که خصوصا تو بازیا یه جور فحش خیلی خیلی بد به حساب میاد. از طرف دیگه میزان محاسبات ما ممکنه در طول برنامه با توجه به ایجاد شرایط مختلف کمتر یا بیشتر طول بکشه. اگر هم بخوایم این مقدار رو محاسباتی کنیم، باید مدت زمان هر بار پردازشمون رو چک کنیم و هربار Interval رو به مقدار جدیدی ست کنیم، شاید سربار ناشی از این محاسبه خیلی کم باشه و خیلی به چشم نیاد ولی وقتی میشه از روش های قبلی استفاده کرد، همین میزان سربار هم قابل حذفه.

Felony
دوشنبه 16 آبان 1390, 23:56 عصر
بله آقای تاجیک! مشکل موقعیه که هیچ دکمه ای زده نمیشه، از رویداد onkeydown برای دکمه های جهتی استفاده کردم، و از appliction.processmessage ، اما بعد از زدن چند دکمه stackoverflow میده
همون سناریویی که گفتم رو پیاده سازی کنید ، یک متغییر Speed برای نگهداری سرعت و یک متغییر LR برای نگهداری جهت در قسمت Private فرم بازیتون تعریف کنید و در رویداد OnKeyDOwn مقدار دهیشون کنید ؛ حالا یک تایمر روی فرم بزارید و Inetrval ش رو مثلا بزارید روی 10 ؛ در داخل تایمر مقدار Speed و LR رو چک کنید و ماشین رو حرکت بدید ، با این روش وقتی دکمه ای زده نمیشه ماشین با سرعت قبلی ( آخرین سرعتی که تو Speed ذخیره شده ) حرکت میکنه .

در مورد اون Stack overflow هم باید کد رو کامل دید ولی گاهی استفاده از همون Application.ProcessMessage باعث اینطور اتفاقات میشه ، ProcessMessage متدی نیست که همینطور بدون بررسی هر جا خواستید صداش بزنید ، این متد به Main Thread برنامه دستور میده کار فعلی خودش رو رها کنه و به پیغام های داخل صف برنامه ( Messsage Qeue ) رسیدگی کنه و این کار رو تا پایان پردازش تمام پیغام های داخل صف ادامه بده ، بعد از پایان کار پردازش پیغام ها و خالی شدن صف پیغام ها برنامه روند عادی خودش رو ادامه میده ؛ در کل در اکثر مواقع ( 100 % مواقع ! ) این نوع مشکلات با طراحی درست سناریو ها و استفاده از راه حل ها و ابزارهای مناسب ( مثلا پیاده سازی برنامه به صورت Multi Thread ) حل میشند و نیازی به استفاده از این راه کارها و به جان خریدن دردسرهای بعدش نیست .

Ms.lemon
سه شنبه 17 آبان 1390, 01:15 صبح
خیلی ممنون دوستان بابت راهنمایی تون، مشکلم برطرف شد:لبخندساده: