الربط والأحداث والتنسيق في JavaFX

التنسيق باستخدام CSS

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

التنسيق باستخدام CSS

يمتلك JavaFX محرك CSS متكاملاً مبنياً مباشرةً داخل رسم المشهد (scene graph). كل عقدة في المشهد تحمل فئة تنسيق (style class)، ويمكنها احتواء خصائص -fx- مباشرة، وتشارك في آلية الترتيب الوراثي (cascade) المشابهة تقريباً لـ CSS في المتصفح. والنتيجة فصل واضح بين منطق التطبيق وعرضه البصري — نفس المبدأ الذي يطبّقه مطورو الويب يومياً، لكن مستهدفاً رسم مشهد سطح المكتب بدلاً من DOM المتصفح.

كيف يعمل CSS في JavaFX

عندما يرتّب وقت تشغيل JavaFX المشهد فإنه يُنفّذ أيضاً تمريرة CSS. يحمّل أوراق الأنماط بالترتيب — ثيم Modena الافتراضي أولاً، ثم أوراق أنماط التطبيق، ثم سمات style المباشرة (inline) — ويحدّد مجموعة الخصائص البصرية النهائية لكل عقدة. جميع خصائص CSS في JavaFX مسبوقة بـ -fx- لتجنّب التعارض مع خصائص CSS القياسية.

Modena هو الثيم الافتراضي. وهو ملف CSS مضمّن داخل JAR وقت تشغيل JavaFX. لست بحاجة لتحميله بنفسك أبداً؛ فهو دائماً ورقة الأنماط ذات الأولوية الأدنى. وتتجاوزه أوراق أنماط تطبيقك بشكل انتقائي.

إضافة ورقة أنماط إلى المشهد

استدع getStylesheets().add() على أي Scene أو Parent. استخدم getClass().getResource() لتحديد المسار نسبةً إلى فئتك — هذا يجعل التطبيق يعمل سواء شغّلته من IDE أو JAR أو مسار الوحدات.

// داخل الفئة الفرعية Application أو المتحكم Scene scene = new Scene(root, 900, 600); scene.getStylesheets().add( getClass().getResource("/css/app.css").toExternalForm() );

يمكنك أيضاً تحديد نطاق ورقة الأنماط لشجرة فرعية بإضافتها إلى عقدة Parent (مثل VBox أو AnchorPane). تنطبق الأنماط المضافة هناك فقط على تلك العقدة وعناصرها الفرعية.

المحدّدات: الفئة والمعرّف والنوع

يدعم CSS في JavaFX ثلاثة أنواع من المحدّدات مرتبطة بمفاهيم JavaFX:

  • محدّد النوع — يطابق فئة تحكم JavaFX. يستهدف .button { } جميع عقد Button. يتبع اسم المحدّد اصطلاح CSS في JavaFX (أحرف صغيرة مع واصلات).
  • محدّد فئة الأنماط (.name) — يطابق العقد التي تمتلك فئة الأنماط هذه في قائمة getStyleClass(). وهو الأداة الرئيسية للتجميع الدلالي.
  • محدّد المعرّف (#name) — يطابق العقدة الوحيدة التي تساوي خاصية id فيها هذه السلسلة. استخدمه باعتدال؛ يجب أن تكون المعرّفات فريدة داخل المشهد.
/* جميع عقد Button */ .button { -fx-background-color: #3498db; -fx-text-fill: white; -fx-font-size: 14px; -fx-padding: 8 20 8 20; -fx-cursor: hand; -fx-background-radius: 6; } /* فئة أنماط دلالية */ .danger-button { -fx-background-color: #e74c3c; } /* معرّف فريد */ #submit-btn { -fx-font-weight: bold; }

في كود Java تضيف فئات الأنماط وتزيلها في وقت التشغيل، تماماً كما تبدّل فئات CSS في JavaScript:

Button btn = new Button("حذف"); btn.getStyleClass().add("danger-button"); // إضافة btn.getStyleClass().remove("danger-button"); // إزالة btn.setId("submit-btn"); // تعيين المعرّف

الفئات الشبه (pseudo-classes): hover وfocused وdisabled

يطبّق JavaFX فئات شبه تلقائياً على العقد استناداً إلى حالتها. تنسّقها بنفس الطريقة كما في CSS للويب باستخدام صيغة النقطتين ::

.button:hover { -fx-background-color: #2980b9; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.25), 6, 0, 0, 2); } .button:focused { -fx-border-color: #1abc9c; -fx-border-width: 2; -fx-border-radius: 6; } .button:disabled { -fx-opacity: 0.5; -fx-cursor: default; } /* حالة الضغط */ .button:pressed { -fx-background-color: #1a6ea8; -fx-translate-y: 1; }
عرّف فئات شبه مخصصة لعناصر التحكم الخاصة بك. استخدم PseudoClass.getPseudoClass("error") وnode.pseudoClassStateChanged(errorClass, true) لتطبيق CSS الخاص بـ :error { } من منطق التحقق — دون الحاجة إلى تبديل فئات الأنماط يدوياً.

خصائص -fx- الشائعة

أكثر خصائص CSS استخداماً في تطبيقات JavaFX:

  • -fx-background-color — لون صلب أو تدرّج أو قائمة طلاء متعددة الطبقات.
  • -fx-text-fill — لون النص للتسميات والأزرار.
  • -fx-font-family و-fx-font-size و-fx-font-weight — التنسيق الطباعي.
  • -fx-padding — الحشو الداخلي (أعلى يمين أسفل يسار، نفس اختصار CSS).
  • -fx-background-radius / -fx-border-radius — زوايا مدوّرة.
  • -fx-border-color و-fx-border-width — تزيين الحدود.
  • -fx-effect — ظلال وتمويه وتوهّج داخلي.
  • -fx-cursor — شكل المؤشر (hand، crosshair، wait وغيرها).

الثيمات: الوضعان الفاتح والداكن

تعتمد استراتيجية الثيمات النظيفة على متغيّرات CSS (تُعرف في JavaFX بالألوان المُبحث عنها). عرّف ألواناً مسمّاة على المستوى الجذري وأشر إليها في كل مكان. تبديل الجذر يغيّر الثيم كله فوراً.

/* في light-theme.css */ .root { -app-bg: #ffffff; -app-surface: #f4f6f8; -app-primary: #3498db; -app-on-primary: #ffffff; -app-text: #2c3e50; -app-border: #dce1e7; } /* في dark-theme.css */ .root { -app-bg: #1e1e2e; -app-surface: #2a2a3e; -app-primary: #7c9ef8; -app-on-primary: #1e1e2e; -app-text: #cdd6f4; -app-border: #45475a; } /* ملف المكوّنات المشترك — يشير إلى المتغيّرات */ .card { -fx-background-color: -app-surface; -fx-border-color: -app-border; -fx-border-radius: 8; -fx-background-radius: 8; -fx-padding: 16; } .label { -fx-text-fill: -app-text; }

لتبديل الثيم في وقت التشغيل، استبدل ورقة الأنماط:

private boolean darkMode = false; public void toggleTheme(Scene scene) { ObservableList<String> sheets = scene.getStylesheets(); String light = getClass().getResource("/css/light-theme.css").toExternalForm(); String dark = getClass().getResource("/css/dark-theme.css").toExternalForm(); if (darkMode) { sheets.remove(dark); sheets.add(light); } else { sheets.remove(light); sheets.add(dark); } darkMode = !darkMode; }
الترتيب مهم في قائمة أوراق الأنماط. ورقة الأنماط الأخيرة المضافة تفوز في حالة التعارض. أضف ورقة أنماط الثيم أولاً وورقة أنماط المكوّنات ثانياً حتى تتمكّن قواعد المكوّنات من تجاوز إعدادات الثيم الافتراضية عند الحاجة.

الأنماط المباشرة (Inline) والتنسيق البرمجي

للتجاوزات الفردية يمكنك تعيين style مباشرةً على عقدة. تتفوّق الأنماط المباشرة دائماً على أوراق الأنماط الخارجية في آلية الترتيب الوراثي — استخدمها فقط للقيم الديناميكية حقاً (ألوان محسوبة في وقت التشغيل، أحجام مستمدة من البيانات).

Label badge = new Label(status.toUpperCase()); String colour = status.equals("ACTIVE") ? "#27ae60" : "#e74c3c"; badge.setStyle( "-fx-background-color: " + colour + ";" + "-fx-text-fill: white;" + "-fx-padding: 3 8 3 8;" + "-fx-background-radius: 4;" );
تجنّب التنسيق المباشر المكثّف في كود الإنتاج. فهو يبعثر المنطق البصري في متحكماتك ويجعل تطبيق الثيمات شبه مستحيل. احتفظ بـ setStyle() للقيم الديناميكية حقاً فقط. لكل شيء آخر استخدم فئات الأنماط وبدّلها برمجياً.

الفئات الشبه المخصصة: مثال عملي

لنفترض أن لديك TextField يجب أن يظهر بحدود حمراء عند فشل التحقق. عرّف فئة شبه مخصصة واربطها بمنطق التحقق الخاص بك:

import javafx.css.PseudoClass; import javafx.scene.control.TextField; public class ValidatedField extends TextField { private static final PseudoClass ERROR = PseudoClass.getPseudoClass("error"); public void setError(boolean hasError) { pseudoClassStateChanged(ERROR, hasError); } }
/* في ملف CSS الخاص بك */ .text-field:error { -fx-border-color: #e74c3c; -fx-border-width: 2; -fx-border-radius: 4; -fx-background-color: #fff5f5; }

الآن يستدعي متحكمك emailField.setError(true) ويتولّى محرك CSS الباقي — دون تلاعب بالأنماط المباشرة ولا سباق إزالة فئات الأنماط.

الخلاصة

يمنحك CSS في JavaFX تحكماً كاملاً في الطبقة البصرية دون لمس كود Java. استخدم أوراق الأنماط الخارجية المحمّلة عبر getStylesheets()، واستهدف العقد بمحدّدات النوع وفئات الأنماط والمعرّفات، واستفد من الفئات الشبه للحالات التفاعلية، وابنِ الثيمات حول متغيّرات الألوان المُبحث عنها. أبقِ الأنماط المباشرة استثنائية. هذا الفصل النظيف يجعل واجهتك قابلة للصيانة والاختبار وسهلة التسليم إلى مصمّم.