عناصر JavaFX والتخطيطات وFXML

مقدمة إلى FXML

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

مقدمة إلى FXML

حتى الآن في هذا البرنامج التعليمي، كنتَ تبني كل واجهة مستخدم بلغة Java الخالصة: تنشئ العقد، تضبط الخصائص، تضيف العناصر الفرعية، وتربط المستمعين — كل ذلك داخل نفس الفئة. هذا النهج يصلح للأمثلة الصغيرة، لكنه يصبح سريعًا غير قابل للإدارة. دالة start() التي تمتد 200 سطر وتخلط منطق التخطيط مع منطق الأعمال يصعب قراءتها، ولا يمكن تسليمها لمصمم، وتكاد تكون كابوسًا في الاختبار.

FXML هو حل JavaFX لهذه المشكلة. إنه لغة ترميز قائمة على XML تتيح لك الإعلان عن واجهة المستخدم في ملف منفصل تمامًا مستقل عن كود Java الذي يديرها. فكّر فيه على أنه مكافئ JavaFX لـHTML في صفحات الويب: الترميز يصف البنية، وفئة Java تتولى السلوك.

لماذا FXML موجود

ثمة ثلاث فوائد ملموسة تدفع كل مشروع JavaFX احترافي إلى استخدام FXML:

  1. فصل الاهتمامات — ملف التخطيط (.fxml) هيكلي بحت. يمكن لمصمم أو أداة مساعدة تعديله دون لمس أي فئة Java.
  2. دعم الأدوات — برنامج Scene Builder، مصمم واجهة المستخدم بالسحب والإفلات، يقرأ FXML ويكتبه بشكل أصيل. تسحب زرًا Button على اللوحة ويكتب Scene Builder XML المقابل نيابةً عنك.
  3. سهولة القراءة — تخطيط متداخل يستغرق 80 سطرًا من Java يتحول إلى 30 سطرًا من XML منسّق، والتسلسل الهرمي واضح بصريًا.
FXML لا يُترجَم (compile). يُحمَّل في وقت التشغيل بواسطة FXMLLoader الذي يُحلّل XML، ويُنشئ العقد، ويضبط قيم الخصائص، ويحقنها في فئة التحكم التي تُزوّده بها. هذا يعني أنك تستطيع تحديث التخطيط دون إعادة تصريف كود Java — مفيد جدًا خلال مرحلة تكرار تصميم واجهة المستخدم.

تشريح ملف FXML

ملف FXML هو مستند XML قياسي. العنصر الجذري هو دائمًا تقريبًا جزء تخطيط أو عقدة حاوية. الخصائص (attributes) تُعيَّن مباشرة إلى خصائص عقدة JavaFX. إليك أبسط مثال ممكن — VBox يحتوي على Label وButton:

<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.Button?> <VBox spacing="12" xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.HelloController"> <Label text="Hello, FXML!" /> <Button text="Click Me" onAction="#handleClick" /> </VBox>

لاحظ عدة أشياء:

  • <?import ...?> تعليمات معالجة تعمل تمامًا مثل عبارات الاستيراد في Java. كل فئة تُشير إليها في الترميز يجب استيرادها بهذه الطريقة.
  • xmlns:fx="http://javafx.com/fxml" يُعلن عن مساحة الاسم fx: التي تتيح خصائص FXML الخاصة مثل fx:id وfx:controller.
  • fx:controller يُخبر FXMLLoader بفئة Java التي هي وحدة تحكم هذا الملف. يُنشئها المحمّل تلقائيًا.
  • onAction="#handleClick" — البادئة # تعني "ابحث عن دالة اسمها handleClick في وحدة التحكم".

تحميل FXML باستخدام FXMLLoader

الجسر بين ملف FXML وتطبيقك المُشغَّل هو javafx.fxml.FXMLLoader. تستدعيه مرة واحدة في Application.start():

import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class MainApp extends Application { @Override public void start(Stage primaryStage) throws Exception { // getResource() يحدد موقع الملف نسبةً إلى هذه الفئة على classpath FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/hello.fxml")); Parent root = loader.load(); // يُحلّل XML ويبني شجرة العقد ويحقن وحدة التحكم primaryStage.setScene(new Scene(root, 400, 250)); primaryStage.setTitle("FXML Demo"); primaryStage.show(); } public static void main(String[] args) { launch(args); } }

تُعيد loader.load() العقدة الجذرية من رسم بياني المشهد المُعلَن في ملف FXML — في هذه الحالة VBox. النوع المُعاد يُلقى عادةً إلى Parent أو النوع الملموس حسب حاجتك.

ضع ملفات FXML تحت src/main/resources (بنية Maven/Gradle)، في حزمة تعكس شجرة مصدر Java. مثلًا إذا كانت وحدة التحكم هي com.example.HelloController ضع FXML في src/main/resources/fxml/hello.fxml وحمّله بـgetClass().getResource("/fxml/hello.fxml"). وضعها على classpath — لا على نظام الملفات مباشرةً — يضمن عمل المسار بشكل متطابق في بيئة التطوير وفي JAR مجمّع وفي صورة أصيلة (native image).

ضبط الخصائص في FXML

يمكن ضبط أي خاصية JavaFX لها setter عام كخاصية XML. الأنواع البدائية تُحوَّل تلقائيًا؛ الألوان والتعدادات تُستخرج من تمثيلاتها النصية. الكائنات المعقدة تُعبَّر عنها كعناصر فرعية متداخلة:

<?import javafx.scene.layout.VBox?> <?import javafx.scene.control.TextField?> <?import javafx.geometry.Insets?> <VBox spacing="10" xmlns:fx="http://javafx.com/fxml"> <!-- صيغة عنصر الخاصية المتداخل للحشو --> <VBox.margin> <Insets top="10" right="10" bottom="10" left="10" /> </VBox.margin> <TextField fx:id="nameField" promptText="Enter your name" prefWidth="250" /> </VBox>

الخاصية fx:id خاصة: تُخبر FXMLLoader بحقن هذه العقدة في حقل مُعلَّق بـ@FXML في وحدة التحكم. هذا الربط موضوع الدرس التالي؛ في الوقت الحالي اعرف فقط اتفاقية التسمية: يجب أن يطابق fx:id اسم الحقل في Java تمامًا.

مساحة اسم FXML والخصائص الخاصة

توفر مساحة الاسم fx: مجموعة صغيرة من الخصائص القوية:

  • fx:id — يُسمّي عقدة لتتمكن وحدة التحكم من الإشارة إليها.
  • fx:controller — يُحدد فئة وحدة التحكم (يُضبط مرة واحدة على العنصر الجذري).
  • fx:include — يُضمّن ملف FXML آخر مما يُتيح تخطيطات معيارية.
  • fx:define — يُعلن كائنات خارج رسم بياني المشهد (مفيد للموارد المشتركة).
  • fx:root — يُتيح نمط المكون المخصص حيث تكون الفئة هي نفسها عقدة الجذر ووحدة التحكم.

صيغة الخاصية المباشرة مقابل صيغة العنصر

يدعم JavaFX FXML صيغتين لضبط القيم. صيغة الخاصية (attribute) موجزة وتصلح للسلاسل النصية والأنواع البسيطة:

<Button text="Save" prefWidth="100" />

صيغة عنصر الخاصية تُستخدم للقيم التي هي كائنات بحد ذاتها كـInsets وFont وObservableList:

<Button> <font> <Font name="Arial Bold" size="14" /> </font> </Button>

ستجد الصيغتين معًا في ملفات FXML الحقيقية. افضّل صيغة الخاصية حين تكون قابلة للقراءة بوضوح؛ وارجع إلى صيغة العنصر لرسوم بيانية الكائنات المعقدة.

انتبه إلى مسار getResource(). إذا بدأ المسار بـ/ فهو مطلق من جذر classpath. بدون الشرطة المائلة الأولى يكون نسبيًا إلى حزمة الفئة المُستدعِية. مسار خاطئ يجعل FXMLLoader.load() يرمي NullPointerException في وقت التشغيل — ليس خطأ تصريف — لذا تحقق دائمًا من أن الملف موجود على classpath وأن سلسلة المسار صحيحة.

بنية مشروع عملي بسيط

مشروع Maven نموذجي يستخدم FXML يبدو هكذا:

src/ main/ java/ com/example/ MainApp.java HelloController.java resources/ fxml/ hello.fxml

بناء Maven ينسخ كل شيء تحت resources/ إلى جذر classpath، لذا يُحلَّل getClass().getResource("/fxml/hello.fxml") بشكل صحيح في وقت التشغيل.

الخلاصة

يفصل FXML إعلان واجهة المستخدم عن منطق التطبيق بالتعبير عن رسم بياني المشهد بلغة XML. تستورد الفئات بـ<?import?>، وتربط وحدة تحكم بـfx:controller، وتُسمّي العقد بـfx:id، وتربط معالجات الأحداث بـ#methodName. يربط FXMLLoader الترميز وبيئة Java: يُحلّل الملف، يبني شجرة العقد، ويُسلّم العقدة الجذرية جاهزةً للوضع في Scene. هذا الفصل النظيف هو أساس كل تطبيق JavaFX احترافي — وبرنامج Scene Builder، المحرر المرئي بالسحب والإفلات، يعمل بالكامل فوقه.