أساسيات JavaFX ورسم المشهد

معالجة حدث بسيط

18 دقيقة الدرس 8 من 12

معالجة حدث بسيط

تدور كل واجهة رسومية تفاعلية حول فكرة محورية واحدة: يحدث شيء ما — ينقر المستخدم زرًا، يضغط مفتاحًا، يحرّك الفأرة — ويتفاعل كودك مع ذلك. في JavaFX تُرسى هذه الفكرة من خلال نظام الأحداث. يركّز هذا الدرس تحديدًا على الحالة الأكثر شيوعًا التي ستكتبها عشرات المرات: الاستجابة لنقرة Button. بمجرد فهمك لآلية عملها، يتبع كل نوع آخر من الأحداث النمط ذاته.

كيف يعمل نظام الأحداث في JavaFX

حين ينقر المستخدم زرًا، تبني JavaFX كائن ActionEvent وتسلّمه إلى أي معالج أحداث مسجَّل. معالج الأحداث هو كائن ينفّذ الواجهة EventHandler<T extends Event>، وهي واجهة وظيفية بمنهج واحد فقط:

package javafx.event; @FunctionalInterface public interface EventHandler<T extends Event> { void handle(T event); }

بما أن EventHandler واجهة وظيفية، يمكنك تمرير المعالج كـتعبير lambda بدلًا من كتابة فئة مجهولة. هذا هو الأسلوب الاصطلاحي في JavaFX الحديثة.

تسجيل معالج بـ setOnAction

تُتيح Button (وكل عنصر تحكم آخر يُطلق أحداث فعل) المنهجَ المختصر setOnAction(EventHandler<ActionEvent> handler). تمرّر lambda الخاصة بك هناك، فتستدعيها JavaFX في كل مرة يُنشَّط الزر — سواء بنقرة الفأرة أو لوحة المفاتيح (مسافة/Enter) أو اللمس.

import javafx.application.Application; import javafx.event.ActionEvent; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.VBox; import javafx.stage.Stage; public class ClickCounter extends Application { private int count = 0; @Override public void start(Stage primaryStage) { Label label = new Label("Clicks: 0"); Button button = new Button("Click me"); // معالج lambda: لا حاجة لفئة مجهولة معقّدة button.setOnAction((ActionEvent event) -> { count++; label.setText("Clicks: " + count); }); VBox root = new VBox(12, label, button); root.setStyle("-fx-padding: 20; -fx-alignment: center;"); primaryStage.setScene(new Scene(root, 260, 130)); primaryStage.setTitle("Click Counter"); primaryStage.show(); } public static void main(String[] args) { launch(args); } }

لنتأمل النقاط المهمة:

  • تلتقط lambda المتغيرَين count وlabel من النطاق المحيط. بما أن count حقل (لا متغير محلي)، فالتعديل داخل lambda مسموح به تمامًا.
  • المعامل ActionEvent متاح داخل lambda لكنه غير مستخدم هنا — يفيد حين يكون معالج واحد مشتركًا بين عدة عناصر تحكم وتحتاج معرفة أيها أطلق الحدث.
  • تحديث label.setText(...) داخل المعالج آمن لأن معالجات setOnAction تعمل دائمًا على خيط تطبيق JavaFX.
قاعدة خيط تطبيق JavaFX: يجب أن تحدث جميع التغييرات على الرسم البياني للمشهد (scene graph) في خيط تطبيق JavaFX (المعروف أيضًا بخيط واجهة المستخدم). معالجات الأحداث المُطلَقة بتفاعلات المستخدم تعمل بالفعل على ذلك الخيط، لذا يمكنك قراءة العقد وتعديلها مباشرةً. لن تحتاج التفكير في الخيوط إلا حين تبدأ عملًا في الخلفية — وهو ما يتناوله الدرس التالي.

صياغة أقصر: حذف نوع المعامل

يمكن لـ Java استنتاج نوع المعامل من السياق، فتختصر lambda إلى:

button.setOnAction(event -> { count++; label.setText("Clicks: " + count); });

إذا كان الجسم تعبيرًا واحدًا، يمكن حذف الأقواس المعقوفة والفاصلة المنقوطة كليًا:

// جسم من تعبير واحد: بدون أقواس، بدون فاصلة منقوطة button.setOnAction(event -> label.setText("Hello!"));

استخدام مرجع المنهج

حين تكون منطق المعالج كافيًا ليستحق منهجه الخاص — أو حين تُعاد الاستفادة من المنطق ذاته من أماكن متعددة — استخرجه كمنهج نسخة ومرّر مرجع منهج:

import javafx.application.Application; import javafx.event.ActionEvent; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.VBox; import javafx.stage.Stage; public class MethodRefDemo extends Application { private int count = 0; private Label label; @Override public void start(Stage primaryStage) { label = new Label("Clicks: 0"); Button button = new Button("Click me"); button.setOnAction(this::handleClick); // مرجع منهج VBox root = new VBox(12, label, button); primaryStage.setScene(new Scene(root, 260, 130)); primaryStage.setTitle("Method Reference Demo"); primaryStage.show(); } private void handleClick(ActionEvent event) { count++; label.setText("Clicks: " + count); } public static void main(String[] args) { launch(args); } }
فضّل مراجع المناهج للمعالجات غير التافهة. تعبير lambda يتجاوز ثلاثة أو أربعة أسطر يصبح صعب القراءة داخل منهج start() الذي يبني الرسم البياني للمشهد أصلًا. كما أن استخراجه إلى منهج مسمّى يجعله قابلًا للاختبار باستقلالية تامة.

معالج واحد لأزرار متعددة

حين تشترك عدة أزرار في منطق استجابة واحد، سجّل المعالج ذاته لكل منها واستخدم مصدر الحدث للتمييز بين الأزرار:

Button btnAdd = new Button("Add"); Button btnSubtract = new Button("Subtract"); Label result = new Label("0"); int[] value = { 0 }; // خدعة المصفوفة: مرجع فعليًا نهائي، محتوياته قابلة للتعديل javafx.event.EventHandler<ActionEvent> handler = event -> { Button src = (Button) event.getSource(); if ("Add".equals(src.getText())) { value[0]++; } else { value[0]--; } result.setText(String.valueOf(value[0])); }; btnAdd.setOnAction(handler); btnSubtract.setOnAction(handler);

تُعيد event.getSource() الكائن Object الذي أطلق الحدث — حوّله إلى Button وافحص نصه، أو بياناته المستخدَمة (setUserData / getUserData)، أو معرّفه (setId / getId) لتفريع منطقك بصورة نظيفة.

لا تقارن نص الزر لتفريع المنطق في التطبيقات الحقيقية. يتغير النص عند تدويل الواجهة وسيُعطّل شرطك في صمت. استخدم button.getId() أو قيمة enum مُخزَّنة عبر setUserData() بدلًا من ذلك. استُخدمت مقارنة النص أعلاه للإيجاز فقط.

addEventHandler مقابل setOnAction

يسجّل اختصار setOnAction معالجًا واحدًا بالضبط؛ استدعاء ثانٍ يستبدل الأول. حين تحتاج معالجات مستقلة متعددة على عنصر التحكم ذاته، استخدم الواجهة البرمجية الأدنى مستوى:

button.addEventHandler(ActionEvent.ACTION, event -> System.out.println("Handler 1")); button.addEventHandler(ActionEvent.ACTION, event -> System.out.println("Handler 2")); // كلاهما يُطلَق عند كل نقرة؛ لا يُلغي أيٌّ منهما الآخر.

لإزالة معالج محدد لاحقًا، احتفظ بمرجع للـ lambda (لا يمكن إزالة lambda مجهولة)، أو استخدم مرجع منهج مسمّى:

EventHandler<ActionEvent> logger = event -> System.out.println("Logging click"); button.addEventHandler(ActionEvent.ACTION, logger); // ... لاحقًا: button.removeEventHandler(ActionEvent.ACTION, logger);

الخلاصة

يتبع التعامل مع نقرة زر في JavaFX نمطًا واضحًا من ثلاث خطوات: إنشاء الزر، تسجيل معالج عبر setOnAction (أو addEventHandler)، وتعديل الرسم البياني للمشهد داخل ذلك المعالج. استخدم lambdas للمنطق المختصر المضمّن ومراجع المناهج حين يستحق المعالج اسمًا. بما أن المعالجات تُطلَق على خيط تطبيق JavaFX، يمكنك تحديث الواجهة مباشرةً دون أي تزامن. في الدرس التالي ستكتشف ما يحدث حين يستغرق عمل المعالج وقتًا طويلًا وكيف تنقله إلى خيط خلفي دون تجميد الواجهة.