معالجة الأحداث بعمق
معالجة الأحداث بعمق
تتدفق تفاعلات المستخدم في تطبيق JavaFX عبر نظام أحداث منظَّم. فالنقر على زر أو الضغط على مفتاح أو سحب الفأرة أو تمرير قائمة — كل ذلك ينتج كائنات من النوع Event تسلك رحلةً ذات مرحلتين محددتين عبر شجرة المشهد. إنّ فهم هذه الرحلة — والتمييز بين مرشحات الأحداث ومعالجاتها — هو ما يُفرّق بين المطوّر الذي يستجيب للأحداث والمطوّر الذي يتحكّم فيها فعلًا.
تسلسل أنواع الأحداث
كل حدث في JavaFX هو نسخة من javafx.event.Event، غير أن النظام يستخدم تسلسلًا هرميًا غنيًا من الأنواع لتصنيفها:
InputEvent— جذر أحداث مدخلات المستخدم.MouseEvent— يغطيMOUSE_PRESSEDوMOUSE_RELEASEDوMOUSE_CLICKEDوMOUSE_MOVEDوMOUSE_DRAGGEDوMOUSE_ENTEREDوMOUSE_EXITEDوغيرها.KeyEvent—KEY_PRESSEDوKEY_RELEASEDوKEY_TYPED. استخدمKEY_TYPEDلإدخال الأحرف وKEY_PRESSEDلمفاتيح التحكم والاختصارات.ScrollEvent— حركات التمرير بعجلة الفأرة أو لوحة التتبع.DragEvent— دورة حياة السحب والإفلات:DRAG_DETECTEDوDRAG_OVERوDRAG_DROPPEDوDRAG_DONE.ActionEvent— تطلقه عناصر التحكم مثلButtonوMenuItemوCheckBoxعند تفعيلها (نقر أو Enter أو مسافة).WindowEvent—WINDOW_SHOWINGوWINDOW_HIDDENوWINDOW_CLOSE_REQUEST.
تُشكّل أنواع الأحداث بدورها تسلسلًا هرميًا. فـMouseEvent.MOUSE_CLICKED ابن لـMouseEvent.ANY، الذي هو ابن لـInputEvent.ANY، الذي هو ابن لـEvent.ANY. وتسجيل معالج لنوع أب يعني أنه سيستقبل جميع أحداث الأنواع الفرعية أيضًا.
دورة إيفاء الحدث: الالتقاط والفقاعة
حين يُطلَق حدث على عنصر ما، لا يُسلّمه JavaFX مباشرةً. بل يسير عبر سلسلة إيفاء الحدث — قائمة عناصر تبدأ من جذر شجرة المشهد (Stage → Scene → العنصر الجذر) نزولًا حتى العنصر المستهدف، ثم صعودًا مجددًا:
- مرحلة الالتقاط (من الأعلى إلى الهدف): يسير الحدث نزولًا في السلسلة. بإمكان أي مرشّح أحداث مسجَّل في الطريق فحص الحدث أو استهلاكه قبل أن يصل إلى هدفه.
- مرحلة الفقاعة (من الهدف إلى الأعلى): يرتفع الحدث مجددًا عبر السلسلة. يستقبله أي معالج أحداث مسجَّل في الطريق بترتيب عكسي.
تسجيل معالجات الأحداث
أكثر طريقة شائعة للاستجابة للحدث هي استخدام addEventHandler(EventType, EventHandler). تستقبل دالة Lambda كائن الحدث المكتوب بنوعه.
حين تنقر على الزر ستجد أن معالجه يطبع أولًا ثم يطبع معالج المشهد. هذه هي مرحلة الفقاعة في العمل.
addEventHandler: تعرض عناصر التحكم دوال ضبط مختصرة مثل btn.setOnAction(...) وbtn.setOnMouseClicked(...). هذه أغلفة تسجّل معالجًا واحدًا. تُجزئ للحالات البسيطة، لكن addEventHandler يتيح لك إرفاق معالجات مستقلة متعددة لنوع الحدث نفسه — مفيد حين تحتاج أجزاء مختلفة من التطبيق الاستجابة للعنصر ذاته.
تسجيل مرشّحات الأحداث
تُسجَّل المرشّحات بـaddEventFilter(EventType, EventHandler) على عنصر سلفي (أب أو جد). تُطلَق أثناء مرحلة الالتقاط، قبل أن يصل الحدث إلى هدفه. ما يجعلها مثالية لـ:
- التحقق من المدخلات (حظر المفاتيح غير الرقمية في حقل نصي).
- تسجيل أو تدقيق جميع التفاعلات من نوع معين داخل شجرة فرعية.
- تعطيل منطقة كاملة من واجهة المستخدم مؤقتًا دون تعديل كل عنصر على حدة.
استهلاك الأحداث
استدعاء event.consume() يوقف رحلة الحدث عبر سلسلة الإيفاء — فلن تستقبله أي مرشّحات أو معالجات إضافية على العناصر الأب أو الفرعية. هذه هي آلية حظر السلوك الافتراضي.
MouseEvent في مرشّح على اللوحة الجذرية، لن تُطلق أي زر بداخلها نداءات onAction الخاصة به أبدًا. اجعل نطاق مرشّحاتك ضيّقًا قدر الإمكان، ولا تستهلك إلا حين يكون لديك سبب ملموس.
KeyEvent في التطبيق العملي
تحمل أحداث لوحة المفاتيح كائن KeyCode (المفتاح الفعلي) وسلسلة حرفية. للاختصارات، استخدم KEY_PRESSED وتحقق من الكود وأعلام المُعدِّلات معًا:
لإدخال النصوص الحرة، استمع إلى KEY_TYPED. تُعيد دالة getCharacter() الخاصة به الحرف القابل للطباعة بعد معالجة منهجية إدخال المنصة، فتتعامل بشكل صحيح مع لوحات المفاتيح الدولية.
إزالة المعالجات والمرشّحات
لكل استدعاء addEventHandler وaddEventFilter نظير متماثل removeEventHandler / removeEventFilter. لإزالة مستمع يجب الاحتفاظ بمرجع لكائن EventHandler الأصلي — دالة Lambda مجهولة مُمرَّرة إلى add لا يمكن تمريرها لاحقًا إلى remove.
الخلاصة
تسلك أحداث JavaFX سلسلة إيفاء ذات مرحلتين: الالتقاط (من الأعلى إلى الهدف، تعالجه المرشّحات) ثم الفقاعة (من الهدف إلى الأعلى، تعالجه المعالجات). تسجيل معالج بـaddEventHandler هو الأداة اليومية للاستجابة لمدخلات المستخدم. وتسجيل مرشّح بـaddEventFilter على عنصر أب يمنحك أفضلية البادئ — يمكنك فحص الحدث أو إعادة توجيهه أو استهلاكه قبل أن يراه أي عنصر فرعي. واستدعاء event.consume() هو الإيقاف الجراحي الذي يُنهي الرحلة. إن جمعت هذه الآليات الثلاث بشكل صحيح أمكنك بناء منطق تفاعل بالغ التعقيد وقابل للتركيب دون أن تُشابك متحكّماتك ببعضها.