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

لوحات التخطيط: StackPane وFlowPane وAnchorPane

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

لوحات التخطيط: StackPane وFlowPane وAnchorPane

قدّمت الدروس السابقة كلًّا من HBox وVBox وBorderPane وGridPane — وهي تخطيطات ترتّب العناصر على محاور أو في مناطق مسمّاة. يتناول هذا الدرس ثلاث لوحات أخرى تحلّ مشكلات مختلفة تمامًا: تُكدّس StackPane العقد فوق بعضها، وتلفّ FlowPane المحتوى كالنص في فقرة، وتثبّت AnchorPane العناصر في حواف اللوحة بإزاحات دقيقة بالبكسل. كل تطبيق JavaFX حقيقي يستخدم واحدة على الأقل من هذه اللوحات.

StackPane — تكديس العقد

StackPane هي أبسط مُركِّب في JavaFX. ترسم جميع عناصرها الأبناء في نفس المستطيل بالترتيب: يُرسم الابن الأول في الأسفل، ويُرسم الابن الأخير في الأعلى. تُحجَّم اللوحة نفسها وفقًا للحجم المفضّل لأكبر ابن.

حالة الاستخدام الكلاسيكية هي وضع تسمية أو أيقونة فوق شكل أو صورة خلفية:

import javafx.application.Application; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; public class StackPaneDemo extends Application { @Override public void start(Stage stage) { // الطبقة الأولى (السفلى): مستطيل ملوّن بحواف مستديرة Rectangle card = new Rectangle(200, 100); card.setArcWidth(20); card.setArcHeight(20); card.setFill(Color.STEELBLUE); // الطبقة الثانية (العليا): تسمية في وسط البطاقة Label label = new Label("Click Me"); label.setStyle("-fx-text-fill: white; -fx-font-size: 16px; -fx-font-weight: bold;"); StackPane stack = new StackPane(card, label); // card أولًا = الأسفل stage.setScene(new Scene(stack, 300, 200)); stage.setTitle("StackPane Demo"); stage.show(); } }

يمكنك تجاوز المحاذاة المركزية الافتراضية لكل ابن باستخدام StackPane.setAlignment(node, Pos.BOTTOM_RIGHT). يتيح لك هذا مثلًا وضع شارة إشعار صغيرة في زاوية صورة المستخدم مع إبقاء الصورة في المركز.

// الشارة مثبّتة في الزاوية السفلية اليمنى Label badge = new Label("3"); badge.setStyle("-fx-background-color: red; -fx-text-fill: white; " + "-fx-background-radius: 8; -fx-padding: 0 5 0 5;"); StackPane.setAlignment(badge, Pos.BOTTOM_RIGHT); StackPane.setMargin(badge, new javafx.geometry.Insets(0, 4, 4, 0));
StackPane وأحداث الفأرة: الابن الأعلى (المُضاف أخيرًا) يستقبل أحداث الفأرة أولًا. إذا أضفت Region شفافة كطبقة علوية فستحجب النقرات على العناصر تحتها ما لم تستدعِ overlay.setMouseTransparent(true).

FlowPane — التخطيط بالتفاف

تُرتّب FlowPane عناصرها الأبناء في صف أفقي (الوضع الافتراضي)، وعندما يمتلئ الصف تلتفّ إلى الصف التالي — تمامًا كالكلمات في فقرة. تحدّد عرضًا مفضّلًا للالتفاف وتقرّر اللوحة أين تقع الفواصل أثناء التشغيل.

import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.scene.control.Button; import javafx.scene.layout.FlowPane; FlowPane flow = new FlowPane(); flow.setHgap(8); // الفجوة الأفقية بين الأبناء flow.setVgap(8); // الفجوة الرأسية بين الصفوف flow.setPadding(new Insets(12)); flow.setPrefWrapLength(400); // بدء الالتفاف بعد ~400 بكسل for (int i = 1; i <= 12; i++) { flow.getChildren().add(new Button("Item " + i)); }

يمكنك تبديل FlowPane إلى اتجاه رأسي: يملأ الأبناء عمودًا ثم يلتفّون إلى العمود التالي عندما تنتهي ارتفاع العمود.

FlowPane verticalFlow = new FlowPane(Orientation.VERTICAL); verticalFlow.setPrefWrapLength(200); // الالتفاف بعد 200 بكسل ارتفاع

نمط شائع هو إقران FlowPane مع ScrollPane ليتمكّن المستخدم من التمرير عبر سحابة وسوم كبيرة أو معرض صور:

ScrollPane scroll = new ScrollPane(flow); scroll.setFitToWidth(true); // يملأ flow عرض منفذ عرض التمرير
فضّل FlowPane لسحابات الوسوم ومجموعات الرقائق وصناديق الأدوات عندما يكون عدد العناصر ديناميكيًا أو غير معروف في وقت التصميم. للشبكات الثابتة ذات الخلايا المتساوية الحجم فضّل TilePane (التي تُطبّق أحجام خلايا موحّدة) أو GridPane.

AnchorPane — تثبيت الحواف بدقة

AnchorPane هي إجابة JavaFX على التموضع المطلق — لكن بذكاء. بدلًا من إعطاء كل ابن إحداثيات ثابتة (x, y)، تثبّت حواف الابن إلى الحواف المقابلة للوحة. عندما تُعاد تحجيم اللوحة يُحافَظ على المسافة إلى الحافة المثبّتة، فيتحرّك الابن أو يتمدّد للحفاظ على تلك الفجوات.

تُعيّن المثبّتات بأربع دوال ثابتة مساعدة:

import javafx.scene.control.Button; import javafx.scene.control.TextArea; import javafx.scene.layout.AnchorPane; AnchorPane anchor = new AnchorPane(); // زر شريط أدوات مثبّت في الزاوية العلوية اليمنى Button saveBtn = new Button("Save"); AnchorPane.setTopAnchor(saveBtn, 8.0); AnchorPane.setRightAnchor(saveBtn, 8.0); // منطقة نص تملأ اللوحة مع فجوة 40 بكسل في الأعلى TextArea editor = new TextArea(); AnchorPane.setTopAnchor(editor, 40.0); AnchorPane.setBottomAnchor(editor, 0.0); AnchorPane.setLeftAnchor(editor, 0.0); AnchorPane.setRightAnchor(editor, 0.0); // تتمدّد أفقيًا anchor.getChildren().addAll(saveBtn, editor);

عندما تُعيّن كلًّا من مثبّت اليسار ومثبّت اليمين على نفس الابن، فإن عرض الابن لا يكون ثابتًا — بل يتمدّد وينكمش مع تغيير حجم اللوحة لإبقاء الفجوتين ثابتتين. تنطبق نفس المنطق على مثبّتي الأعلى والأسفل اللذين يتحكّمان في الارتفاع. هذه هي الطريقة الصحيحة لبناء لوحة محرّر قابلة للتغيير في نافذة.

المثبّتات ليست هوامش. إذا عيّنت مثبّت اليسار فقط، فإن الحافة اليمنى للابن تطفو بحرية — لا تلتصق بالحافة اليمنى للوحة. يجب أن تعيّن كلًّا من مثبّتي اليسار واليمين لجعل الابن يتمدّد أفقيًا.

دمج اللوحات عمليًا

تعشش واجهات المستخدم الحقيقية هذه اللوحات. نمط شائع لشاشة تدوين ملاحظات بسيطة يجمع الثلاثة معًا:

import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.stage.Stage; import javafx.geometry.Insets; public class CombinedLayoutDemo extends Application { @Override public void start(Stage stage) { // --- رأس الصفحة: أيقونة صورة عبر StackPane --- Circle avatar = new Circle(20, Color.CORNFLOWERBLUE); Label initials = new Label("ES"); initials.setStyle("-fx-text-fill: white; -fx-font-weight: bold;"); StackPane avatarStack = new StackPane(avatar, initials); // --- صف الوسوم: FlowPane --- FlowPane tags = new FlowPane(); tags.setHgap(6); tags.setVgap(4); tags.setPadding(new Insets(4, 0, 4, 0)); for (String tag : new String[]{"JavaFX", "UI", "Layouts", "FXML", "Panes"}) { Label chip = new Label(tag); chip.setStyle("-fx-background-color: #e0e7ff; -fx-padding: 2 8 2 8; " + "-fx-background-radius: 12;"); tags.getChildren().add(chip); } // --- منطقة المحرّر تملأ المساحة المتبقية عبر AnchorPane --- AnchorPane editorPane = new AnchorPane(); TextArea area = new TextArea(); area.setPromptText("Start typing your note..."); AnchorPane.setTopAnchor(area, 0.0); AnchorPane.setBottomAnchor(area, 0.0); AnchorPane.setLeftAnchor(area, 0.0); AnchorPane.setRightAnchor(area, 0.0); editorPane.getChildren().add(area); // --- تكوين الشاشة الكاملة --- VBox root = new VBox(10, avatarStack, tags, editorPane); root.setPadding(new Insets(12)); VBox.setVgrow(editorPane, Priority.ALWAYS); // المحرّر يأخذ الارتفاع المتبقي stage.setScene(new Scene(root, 500, 400)); stage.setTitle("Combined Layout Demo"); stage.show(); } }
VBox.setVgrow يخبر VBox بإعطاء المساحة الرأسية المتبقية لابن محدّد. بدونها تنكمش AnchorPane إلى حجمها المفضّل وتبقى منطقة النص صغيرة. هذا النمط — رأس صفحة بارتفاع ثابت + جسم متنامٍ — يظهر في تقريبًا كل تطبيق سطح مكتب.

اختيار اللوحة المناسبة

  • StackPane: التراكبات، والبطاقات ذات الشارات، ومؤشرات التحميل فوق المحتوى، والنوافذ المنبثقة على غرار التلميحات.
  • FlowPane: سحابات الوسوم، ومجموعات الرقائق، وصناديق الأدوات الديناميكية، والصور المصغّرة الملتفّة، وأي مجموعة يتغيّر عدد عناصرها أثناء التشغيل.
  • AnchorPane: تثبيت عناصر التحكّم في حواف النافذة، وبناء تخطيطات مقسّمة قابلة للتغيير في FXML، ووضع زر إجراء عائم في زاوية لوحة.

الخلاصة

أصبح لديك الآن المجموعة الكاملة من لوحات JavaFX اليومية. تكدّس StackPane الأبناء في ترتيب Z، مما يتيح لك بناء عناصر واجهة مستخدم مُركّبة بلوحة واحدة. تلفّ FlowPane الأبناء كالنص، وتتكيّف مع العرض أو الارتفاع المتاح أثناء التشغيل. تثبّت AnchorPane الأبناء في حواف اللوحة فيتحرّكون ويتغيّر حجمهم مع النافذة. في الدرس التالي ستتخلّى عن التخطيطات البرمجية الخالصة وستتعرّف على FXML — تنسيق XML تعريفي يتيح لـ Scene Builder إنشاء هذا الهيكل كله بصريًا.