وحدات التحكم والتعليق @FXML
وحدات التحكم والتعليق @FXML
في الدرس السابق تعلّمت أن ملف FXML هو وصف تصريحي لرسم بياني للمشهد. غير أن مشهدًا ثابتًا بمفرده لا يُنجز شيئًا — لا نقرات على الأزرار، ولا تحقق من البيانات، ولا تنقل بين الشاشات. هذا السلوك التفاعلي يقطن في فئة وحدة التحكم: وهي فئة Java عادية مرتبطة بملف FXML عبر الـ FXMLLoader. يغطي هذا الدرس كل ما تحتاجه لبناء وحدة تحكم وربطها بأسلوب احترافي في تطبيقات JavaFX.
دور وحدة التحكم
وحدة التحكم هي الحرف "C" في نمط MVC. تحتفظ بمراجع للعناصر المعلنة في FXML، وتستجيب لأحداث المستخدم، وتحدّث النموذج. لا تحتوي على أي كود تخطيط — ذلك عمل ملف FXML (واختياريًا Scene Builder).
هذا الفصل مقصود وذو قيمة: يستطيع المصمم تحرير FXML دون لمس Java، ويستطيع المطور اختبار منطق وحدة التحكم باستقلالية تامة عن الواجهة.
إعلان وحدة التحكم في FXML
تربط وحدة التحكم بملف FXML عبر سمة fx:controller في العنصر الجذري:
سمتان تتولّيان عملية الربط هنا:
fx:id— يمنح عنصر واجهة المستخدم اسمًا سيحقنه FXMLLoader في الحقل المطابق في وحدة التحكم.onAction="#handleLogin"— البادئة#تعني "استدعِ هذا التابع على وحدة التحكم". وهي تعمل مع أي سمة حدث (onKeyPressedوonMouseClickedوغيرها).
كتابة فئة وحدة التحكم
وحدة التحكم فئة Java عادية. الحقول التي تطابق قيم fx:id يجب أن تُوسَم بـ @FXML. يستخدم FXMLLoader الانعكاس (reflection) لحقنها — فيتجاوز محددات الوصول، لذا يمكن أن تكون الحقول private (وينبغي أن تكون كذلك).
initialize() وليس المنشئ؟ عندما ينفذ المنشئ، لم يحقن FXMLLoader أي حقول بعد. تُستدعى initialize() بعد ملء جميع حقول @FXML، لذا فهي المكان الصحيح لضبط الحالة الابتدائية وربط الخصائص أو جلب البيانات الأولية.
تحميل FXML من Java
نقطة دخول التطبيق (أو مساعد التبديل بين المشاهد) تستخدم FXMLLoader لتحليل الملف والحصول على العقدة الجذرية:
بعد عودة loader.load() يمكنك استدعاء loader.getController() للحصول على نسخة وحدة التحكم. هذا مفيد عندما تحتاج إلى تمرير بيانات إلى وحدة التحكم قبل عرض المشهد — وهو نمط شائع لتمرير كائن نموذج إلى شاشة التفاصيل.
تمرير البيانات بين وحدات التحكم
لا يوجد "موجّه" مدمج في JavaFX. الأنماط الشائعة لنقل البيانات عند التنقل بين الشاشات:
- حقن عبر الضبط (Setter injection) (كما هو موضح أعلاه) — يستدعي الكود المُستدعي ضابطًا عامًا على وحدة التحكم التالية بعد
load()وقبلshow(). - نموذج مشترك / خدمة singleton — تحتفظ وحدتا التحكم بمرجع لنفس الخدمة أو كائن النموذج القابل للمراقبة؛ تقرأه وحدة التحكم الثانية في
initialize(). - حقن عبر المنشئ باستخدام مصنع وحدات التحكم — تمرير تعبير lambda كمعامل ثانٍ لمنشئ
FXMLLoaderلبناء وحدات التحكم بنفسك، مما يُتيح حقن التبعيات الحقيقي.
معالجات الأحداث: توابع @FXML مقابل التسجيل باستخدام Lambda
صياغة onAction="#methodName" في FXML واستدعاء button.setOnAction(e -> ...) في الكود متكافئتان في النتيجة. استخدم مراجع أحداث FXML للمعالجات التي تُشكّل جزءًا طبيعيًا من التخطيط المُعلن. استخدم تسجيل lambda في initialize() عندما تحتاج إلى التقاط متغير محلي أو عندما يُربط المعالج ديناميكيًا.
وحدات التحكم المتداخلة مع fx:include
كثيرًا ما تُقسَّم واجهات المستخدم الكبيرة إلى أجزاء FXML قابلة لإعادة الاستخدام يتم تحميلها بـ fx:include. يمكن لكل FXML مُضمَّن أن يملك وحدة تحكمه الخاصة. تستطيع وحدة التحكم الأصلية الوصول إلى وحدة التحكم الفرعية بالإعلان عن حقل @FXML يحمل الاسم <fx:id of the include>Controller:
@FXML من المنشئ. تكون قيمتها null وقت بناء الكائن. أي كود يلمس عنصر واجهة يجب أن يكون في initialize() أو في معالج حدث — وليس أبدًا في المنشئ أو في مُهيِّئ ثابت.
الخلاصة
التعليق @FXML هو الغراء الذي يربط ملف FXML التصريحي بوحدة التحكم Java الأمرية. تذكّر ثلاث نقاط محورية: أعلن عن fx:controller في العنصر الجذري لـ FXML؛ وسِّم الحقول المطابقة بـ @FXML (يمكن أن تكون خاصة)؛ وضع جميع منطق عناصر الواجهة الأولي في initialize() لا في المنشئ. في الدرس القادم ستستخدم Scene Builder لتوليد ملفات FXML بصريًا وصيانتها، وسترى كيف يُبقي سمات fx:id وfx:controller متزامنة تلقائيًا.