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

المرحلة والمشهد وشجرة العقد

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

المرحلة والمشهد وشجرة العقد

يُبنى كل تطبيق JavaFX مرئي من ثلاثة مفاهيم متشابكة: المرحلة (Stage)، والمشهد (Scene)، وشجرة العقد (Scene Graph). إن فهم ماهية كل منها بدقة — وكيفية ارتباطها ببعضها — يُشكّل النموذج الذهني الذي يجعل كل قرار تخطيط وتنسيق وأحداث يقع في مكانه الطبيعي.

المرحلة (Stage)

المرحلة Stage هي نافذة من أعلى المستوى. يُنشئ وقت تشغيل JavaFX واحدة لك — المرحلة الأساسية — ويمررها إلى دالة start(Stage stage) الخاصة بك. يمكنك أيضًا إنشاء مزيد من كائنات Stage عند الحاجة إلى نافذة ثانية (حوار، لوحة أدوات، عرض على شاشة ثانوية).

تُغلّف فئة Stage نافذة نظام التشغيل الأصلية. وأكثر الخصائص التي تُهيَّأ شيوعًا هي:

  • stage.setTitle("My App") — نص شريط عنوان النافذة.
  • stage.setWidth(800) / stage.setHeight(600) — الأبعاد الأولية بالبكسل.
  • stage.setResizable(false) — تعطيل تغيير حجم النافذة من قِبَل المستخدم.
  • stage.initStyle(StageStyle.UNDECORATED) — إزالة واجهة نظام التشغيل كليًا (مفيد لشاشات التحميل).
  • stage.initModality(Modality.APPLICATION_MODAL) — حجب الإدخال عن النوافذ الأخرى (نمط الحوار المشروط).
  • stage.show() — إظهار النافذة؛ لن يظهر شيء على الشاشة حتى يُستدعى هذا الأسلوب.
ملكية المرحلة الأساسية: تُنشأ المرحلة الأساسية وتملكها بيئة تشغيل JavaFX. لا تستدعِ new Stage() لاستبدالها — بل هيّئ الكائن الذي يُسلَّم إليك وأظهره.

المشهد (Scene)

المشهد Scene هو حاوية المحتوى التي تعيش داخل المرحلة. يكون مشهد واحد نشطًا في أي لحظة؛ يمكنك تبديل المشاهد لتطبيق التنقل بأسلوب الصفحات دون فتح نوافذ جديدة. يُنشأ المشهد بعقدة جذر وأبعاد مفضّلة اختيارية:

VBox root = new VBox(10); // 10 بكسل تباعد بين العناصر الفرعية Scene scene = new Scene(root, 800, 600); stage.setScene(scene);

الخصائص الرئيسية لـ Scene:

  • عقدة الجذر — حاوية التخطيط الوحيدة في المستوى الأعلى التي تملأ مساحة المشهد بأكملها.
  • أوراق الأنماطscene.getStylesheets().add(...) يضيف ملفات CSS خارجية تُنسّق الشجرة بأكملها.
  • التعبئةscene.setFill(Color.DARKGRAY) يضبط لون خلفية المشهد (خلف عقدة الجذر).
  • مؤشر الماوسscene.setCursor(Cursor.WAIT) يغيّر مؤشر الماوس للنافذة بأكملها.
استبدال الجذر لتبديل العروض: بدلًا من فتح نافذة جديدة لشاشة "الإعدادات"، استدعِ stage.getScene().setRoot(settingsPane). هذا سريع، ويحتفظ بالمرحلة والمشهد نفسيهما، ويُبقي على أي أوراق أنماط أضفتها إلى المشهد مسبقًا.

شجرة العقد (Scene Graph)

شجرة العقد هي شجرة من كائنات Node جذرها هو العقدة الجذر للمشهد. كل عنصر مرئي أو تفاعلي في JavaFX — زر، تسمية، صورة، مستطيل، جزء تخطيط، مشغّل وسائط — هو Node. وتُرتَّب في هرمية أب-ابن:

  • العقد الأبوية (الفئات الفرعية من Parent) يمكنها امتلاك عناصر فرعية: VBox، HBox، BorderPane، StackPane، Group، إلخ.
  • العقد الورقية لا يمكنها امتلاك عناصر فرعية: Button، Label، Rectangle، ImageView، إلخ.

إليك شجرة عقد صغيرة في الكود:

// شجرة العقد: BorderPane → (أعلى: HBox → [Label, Button], وسط: TextArea) HBox toolbar = new HBox(8); Label titleLabel = new Label("Notes"); Button saveButton = new Button("Save"); toolbar.getChildren().addAll(titleLabel, saveButton); TextArea editor = new TextArea(); BorderPane root = new BorderPane(); root.setTop(toolbar); root.setCenter(editor); Scene scene = new Scene(root, 900, 600); stage.setScene(scene); stage.setTitle("Notes App"); stage.show();

تبدو الشجرة الناتجة كما يلي:

Scene └─ BorderPane (الجذر) ├─ HBox (أعلى) │ ├─ Label "Notes" │ └─ Button "Save" └─ TextArea (وسط)

لماذا شجرة؟ تتالي الخصائص

بنية الشجرة ليست اعتباطية — بل تُشغّل ثلاثة سلوكيات قوية في المنصة:

  1. تتالي CSS: تنطبق قاعدة الأنماط المطبّقة على أحد الآباء تلقائيًا على كل المنحدرين، تمامًا كـ CSS على الويب. اضبط خطًا على BorderPane وكل Label فرعي سيرثه ما لم يُستبدَل.
  2. فضاء الإحداثيات: يُعبَّر عن موضع كل عقدة نسبةً إلى أبيها. حرّك الأب وكل الأبناء يتحركون معه — دون الحاجة إلى حسابات.
  3. انتشار الأحداث: تنتقل نقرات الماوس وضغطات المفاتيح نزولًا عبر الشجرة (مرحلة الالتقاط) ثم صعودًا (مرحلة الفقاعة)، مما يتيح لمستمع واحد على حاوية اعتراض أحداث أي منحدر.
لا يمكن للعقدة أن يكون لها أكثر من أب. إذا أضفت عقدة موجودة في الشجرة إلى حاوية ثانية، سيُزيلها JavaFX بصمت من الأولى. محاولة مشاركة عقدة بين أبوَين هي خطأ تخطيط شائع — انسخها بدلًا من ذلك.

اجتياز الشجرة والفحص

نادرًا ما تحتاج إلى السير في الشجرة يدويًا، لكن يفيدك معرفة أن الواجهة البرمجية موجودة. تعرض كل Node ما يلي:

  • node.getParent() — يعيد الأب المباشر، أو null للجذر.
  • node.getScene() — يعيد Scene التي تنتمي إليها هذه العقدة، أو null إذا لم تُضَف بعد.
  • parent.getChildrenUnmodifiable() — عرض للقراءة فقط لقائمة أبناء الأب.
  • scene.lookup("#myId") — البحث عن أي عقدة بمعرّف CSS الخاص بها، مثل document.getElementById في المتصفح.
  • scene.lookupAll(".btn-primary") — العثور على جميع العقد المطابقة لمحدد فئة CSS.
// تعيين المعرّفات في الكود (أو في FXML) للبحث لاحقًا saveButton.setId("saveBtn"); // لاحقًا، من أي كود يملك مرجعًا للمشهد: Button found = (Button) scene.lookup("#saveBtn");

العقد الحية مقابل غير المرتبطة

العقدة التي أُضيفت إلى شجرة العقد وهي جزء من مرحلة مُظهَرة تُسمى حية. بمجرد أن تصبح حية، تُنسّقها محرك CSS، ويقيسها محرك التخطيط ويضعها في موضعها، ويرسمها محرك التصيير في كل إطار. أما العقدة التي أنشأتها لكن لم تضفها بعد فهي غير مرتبطة — توجد في الذاكرة لكنها غير مرئية وغير منسّقة. يهم هذا التمييز عندما تحاول قياس حجم عقدة قبل تخطيطها: استدعِ node.applyCss() وnode.layout() أولًا إذا احتجت إلى الأبعاد قبل نبضة التصيير الأولى.

تجميع كل شيء معًا

النموذج الذهني هو ثلاث طبقات متسلسلة:

  • Stage — إطار نافذة نظام التشغيل (شريط العنوان، والزخارف، والحالة المشروطة).
  • Scene — لوحة المحتوى داخل المرحلة (الحجم، ولون خلفية التعبئة، وأوراق أنماط CSS).
  • Scene Graph — شجرة كائنات Node التي تحدد ما يُعرض ويتفاعل فعليًا.

كل ميزة تبنيها في JavaFX — عناصر تحكم مخصصة، وتحريكات، وربط بيانات، وسحب وإفلات — تعمل على هذه الشجرة. إن معرفة كيفية بنائها والتنقل فيها والتفكير في علاقات الأب والابن هو الأساس الذي يقوم عليه كل شيء آخر.