القوائم وأشرطة الأدوات ومربعات الحوار
تُقدّم معظم تطبيقات سطح المكتب ثلاث طبقات لعرض الأوامر للمستخدم: شريط قوائم يعرض المجموعة الكاملة من الميزات، وشريط أدوات يُقرّب الإجراءات الأكثر استخدامًا بنقرة واحدة، ومربعات حوار تطلب التأكيد قبل إجراء تدميري أو تعرض نتيجة ما. يشحن JavaFX مع عناصر تحكم متكاملة للطبقات الثلاث. يتناول هذا الدرس كل طبقة بعمق عملي.
شريط القوائم
يتكوّن شريط القوائم في JavaFX من ثلاث فئات تعكس التسلسل الهرمي المرئي:
MenuBar — الشريط الأفقي الذي يجلس في أعلى النافذة.
Menu — تسمية على المستوى الأعلى (ملف، تحرير، مساعدة…) تفتح قائمة منسدلة عند النقر عليها.
MenuItem — إدخال واحد قابل للنقر داخل تلك القائمة المنسدلة.
تبني الشجرة في الكود، وتُرفق معالجات setOnAction بكل MenuItem، ثم تضع MenuBar في لوح تخطيط — عادةً الحيز العلوي لـ BorderPane.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MenuDemo extends Application {
@Override
public void start(Stage stage) {
// ---- قائمة ملف ----
MenuItem newItem = new MenuItem("جديد");
MenuItem openItem = new MenuItem("فتح…");
MenuItem saveItem = new MenuItem("حفظ");
MenuItem exitItem = new MenuItem("خروج");
newItem.setOnAction(e -> System.out.println("جديد"));
openItem.setOnAction(e -> System.out.println("فتح"));
saveItem.setOnAction(e -> System.out.println("حفظ"));
exitItem.setOnAction(e -> stage.close());
Menu fileMenu = new Menu("ملف");
fileMenu.getItems().addAll(newItem, openItem, saveItem,
new SeparatorMenuItem(), exitItem);
// ---- قائمة تحرير ----
MenuItem cutItem = new MenuItem("قص");
MenuItem copyItem = new MenuItem("نسخ");
MenuItem pasteItem = new MenuItem("لصق");
Menu editMenu = new Menu("تحرير");
editMenu.getItems().addAll(cutItem, copyItem, pasteItem);
// ---- التجميع ----
MenuBar menuBar = new MenuBar();
menuBar.getMenus().addAll(fileMenu, editMenu);
BorderPane root = new BorderPane();
root.setTop(menuBar);
stage.setScene(new Scene(root, 600, 400));
stage.setTitle("عرض القوائم");
stage.show();
}
}
يُرسَم SeparatorMenuItem كخط أفقي رفيع يُجمّع العناصر ذات الصلة بصريًا دون إضافة إدخال قابل للنقر.
اختصارات لوحة المفاتيح
يتوقع المستخدمون أن Ctrl+S يحفظ وأن Ctrl+N يُنشئ مستندًا جديدًا. ارفق KeyCombination بأي MenuItem عبر setAccelerator(). يُطلق JavaFX معالج إجراء العنصر عند الضغط على مجموعة المفاتيح بغض النظر عن كون القائمة مفتوحة أم لا.
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
saveItem.setAccelerator(
new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN)
);
استخدم SHORTCUT_DOWN بدلًا من CONTROL_DOWN. على macOS، يُعيَّن SHORTCUT_DOWN على مفتاح Command؛ وعلى Windows وLinux يُعيَّن على Ctrl. استخدامه يجعل تطبيقك يبدو طبيعيًا على كل منصة دون أي كود شرطي.
CheckMenuItem و RadioMenuItem
إلى جانب العناصر العادية، يوفر JavaFX نوعين ذوَي حالة شائعَين في قوائم عرض أو خيارات:
CheckMenuItem — يعرض علامة اختيار عند التحديد. بدّل الحالة بـ setSelected()؛ اقرأ الحالة بـ isSelected().
RadioMenuItem — جزء من ToggleGroup بحيث لا يُختار إلا عنصر واحد في وقت واحد، تمامًا كأزرار الاختيار.
CheckMenuItem wordWrapItem = new CheckMenuItem("التفاف النص");
wordWrapItem.setSelected(true); // مفعّل افتراضيًا
wordWrapItem.setOnAction(e -> applyWordWrap(wordWrapItem.isSelected()));
ToggleGroup themeGroup = new ToggleGroup();
RadioMenuItem lightItem = new RadioMenuItem("سمة فاتحة");
RadioMenuItem darkItem = new RadioMenuItem("سمة داكنة");
lightItem.setToggleGroup(themeGroup);
darkItem.setToggleGroup(themeGroup);
lightItem.setSelected(true);
Menu viewMenu = new Menu("عرض");
viewMenu.getItems().addAll(wordWrapItem, new SeparatorMenuItem(),
lightItem, darkItem);
شريط الأدوات
شريط الأدوات ToolBar هو شريط أفقي (أو رأسي) من الأزرار يمنح المستخدمين وصولًا بنقرة واحدة للإجراءات الأكثر شيوعًا. كشريط القوائم، ينتمي إلى المنطقة العلوية لـ BorderPane، ويُوضع أسفل شريط القوائم مباشرةً باستخدام غلاف VBox.
import javafx.scene.control.Button;
import javafx.scene.control.Separator;
import javafx.scene.control.ToolBar;
Button newBtn = new Button("جديد");
Button openBtn = new Button("فتح");
Button saveBtn = new Button("حفظ");
// في تطبيق حقيقي ستستخدم أيقونات ImageView بدلًا من تسميات نصية
newBtn.setOnAction(e -> System.out.println("جديد"));
saveBtn.setOnAction(e -> System.out.println("حفظ"));
ToolBar toolBar = new ToolBar(newBtn, openBtn, saveBtn,
new Separator(),
new Button("قص"),
new Button("نسخ"),
new Button("لصق"));
أضف تلميحات لكل زر في شريط الأدوات. الأزرار التي تعتمد على أيقونات فقط مدمجة لكنها غامضة للمستخدمين الجدد. سطر واحد يضبط التلميح: saveBtn.setTooltip(new Tooltip("حفظ (Ctrl+S)"));. تعرضه المنصة تلقائيًا عند تحريك المؤشر فوقه.
ادمج شريط القوائم وشريط الأدوات في VBox لتكديسهما بشكل أنيق في الأعلى:
VBox topBox = new VBox(menuBar, toolBar);
root.setTop(topBox);
مربعات الحوار التنبيهية
تُغلّف فئة Alert في JavaFX أنماط مربعات الحوار القياسية التي يتعرف عليها كل مستخدم سطح مكتب. تختار AlertType، وتضبط رسالة، وتستدعي showAndWait(). تحجب الاستدعاء على خيط تطبيق JavaFX حتى يُغلق المستخدم مربع الحوار، ثم تُعيد Optional<ButtonType> يصف أي زر ضغط.
أنواع التنبيه الخمسة المدمجة هي:
AlertType.INFORMATION — رسالة معلوماتية، زر موافقة واحد.
AlertType.WARNING — تحذير، زر موافقة واحد.
AlertType.ERROR — تقرير خطأ، زر موافقة واحد.
AlertType.CONFIRMATION — أزرار موافقة/إلغاء، يُعيد الزر المُختار.
AlertType.NONE — لا أزرار افتراضية؛ تُضيف ButtonType خاصتك.
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import java.util.Optional;
// --- معلوماتي ---
Alert info = new Alert(AlertType.INFORMATION);
info.setTitle("تم الحفظ");
info.setHeaderText(null); // لا سطر رأس عريض
info.setContentText("تم حفظ ملفك بنجاح.");
info.showAndWait();
// --- تأكيد (مع فحص النتيجة) ---
Alert confirm = new Alert(AlertType.CONFIRMATION);
confirm.setTitle("حذف الملف");
confirm.setHeaderText("لا يمكن التراجع عن هذا الإجراء.");
confirm.setContentText("هل أنت متأكد أنك تريد حذف الملف المحدد؟");
Optional<ButtonType> result = confirm.showAndWait();
if (result.isPresent() && result.get() == ButtonType.OK) {
deleteSelectedFile();
}
تسميات مخصصة للأزرار
عندما لا تتوافق التسميات الافتراضية "موافق" و"إلغاء" مع نص واجهة المستخدم، استبدلها بمثيلات ButtonType مسمّاة:
ButtonType saveBtn = new ButtonType("حفظ");
ButtonType discardBtn = new ButtonType("تجاهل");
ButtonType cancelBtn = new ButtonType("إلغاء", ButtonBar.ButtonData.CANCEL_CLOSE);
Alert unsaved = new Alert(AlertType.NONE);
unsaved.setTitle("تغييرات غير محفوظة");
unsaved.setContentText("هل تريد حفظ التغييرات قبل الإغلاق؟");
unsaved.getButtonTypes().setAll(saveBtn, discardBtn, cancelBtn);
Optional<ButtonType> choice = unsaved.showAndWait();
if (choice.isPresent()) {
if (choice.get() == saveBtn) { saveAndClose(); }
else if (choice.get() == discardBtn) { closeWithoutSave(); }
// وإلا: إلغاء — لا تفعل شيئًا
}
استدع دائمًا showAndWait() لا show() لمربعات الحوار التأكيدية. show() غير حاجبة — يستمر كودك فورًا بعد عرض مربع الحوار، لذا تكون Optional فارغة ولا يمكنك معرفة ما اختاره المستخدم. احجز show() فقط للنوافذ المنبثقة المعلوماتية غير الحاجبة التي لا تحتاج إلى نتيجة.
TextInputDialog و ChoiceDialog
مربّعا حوار مدمجان إضافيان يتعاملان مع أنماط تفاعل شائعة دون الحاجة إلى بناء تخطيط مخصص:
TextInputDialog — يعرض حقل نص ليُدخل المستخدم سلسلة نصية. يُعيد Optional<String>.
ChoiceDialog<T> — يعرض ComboBox مملوء مسبقًا بخيارات تُزوّدها. يُعيد Optional<T>.
// طلب اسم ملف
TextInputDialog nameDialog = new TextInputDialog("report.pdf");
nameDialog.setTitle("حفظ باسم");
nameDialog.setHeaderText("أدخل اسم الملف:");
nameDialog.setContentText("الاسم:");
Optional<String> name = nameDialog.showAndWait();
name.ifPresent(n -> saveAs(n));
// السماح للمستخدم باختيار سمة
ChoiceDialog<String> themeDialog =
new ChoiceDialog<>("فاتح", "فاتح", "داكن", "تباين عالٍ");
themeDialog.setTitle("اختر السمة");
themeDialog.setHeaderText(null);
themeDialog.setContentText("السمة:");
themeDialog.showAndWait().ifPresent(t -> applyTheme(t));
تجميع كل شيء معًا
في تطبيق حقيقي، ينتمي كود شريط القوائم وشريط الأدوات ومربعات الحوار إلى فئة المتحكم (التي تعلمتها في الدرس السابع). يُعلن ملف FXML عقدتي MenuBar وToolBar؛ يحمل المتحكم المراجع المحقونة بـ @FXML ويربط معالجات الإجراءات. تُنشأ مربعات الحوار دائمًا تقريبًا بشكل ضمني في أساليب المعالجة — فهي كائنات عابرة لا تستحق التصريح بها في FXML.
الخلاصة
يعرض تطبيق JavaFX الاحترافي الأوامر من خلال MenuBar (المجموعة الكاملة من الميزات)، وToolBar (الوصول السريع)، ومربعات الحوار Alert (مطالبات المستخدم). استخدم KeyCodeCombination مع SHORTCUT_DOWN للاختصارات متعددة الأنظمة. فضّل showAndWait() وافحص قيمة الإعادة Optional<ButtonType> لأي مربع حوار يتطلب قرارًا. في الدرس القادم ستبني تطبيقًا متكاملًا قائمًا على نموذج يستخدم جميع عناصر التحكم والأنماط من هذا البرنامج التعليمي.