عناصر التحكم المخصصة وإعادة الاستخدام
عناصر التحكم المخصصة وإعادة الاستخدام
تأتي JavaFX مزوّدةً بمكتبة غنية من عناصر التحكم المدمجة، غير أن التطبيقات الحقيقية تحتاج دائمًا تقريبًا إلى شيء لا توفره المكتبة جاهزًا: حقل إدخال بعنوان، أو بطاقة عنصر واجهة تضم رأسًا وجسمًا، أو صف نموذج قابل للإعادة يربط تسمية بحقلها برسالة التحقق منها. يعلّمك هذا الدرس الآليات والقرارات التصميمية المتعلقة ببناء مثل هذه المكونات حتى يمكن إسقاطها في أي مشهد تمامًا مثل Button أو TextField.
نهجان: التركيب مقابل الوراثة الفرعية
قبل كتابة أي كود تحتاج إلى اتخاذ قرار بشأن استراتيجية التنفيذ. تمنحك JavaFX خيارين نظيفين.
- التركيب (مُفضَّل): امتدّ من لوحة تخطيط موجودة (
HBoxأوVBoxأوStackPaneوغيرها) وأضف عُقدًا فرعية داخل منشئها. والنتيجة هي لوحة يمكن وضعها في أي مكان يُقبل فيهNode. - الوراثة الفرعية من Control: امتدّ من
Controlوأرفقه بتنفيذSkin. هذا هو المسار الصحيح للعناصر المعقدة القابلة للتخصيص بالسمات (skinnable)، لكنه يتطلب مزيدًا من الكود المعياري.
بالنسبة لغالبية عناصر التحكم المخصصة اليومية، يُعدّ التركيبُ الإجابةَ الصحيحة. تحصل على سلوك التخطيط مجانًا وتركّز كليًا على واجهة برمجة المكون ومنطقه.
بناء مكون LabeledField قابل للإعادة
يُعدّ LabeledField متطلبًا شائعًا جدًا: تسمية فوق حقل نص (أو بجانبه)، بالإضافة إلى رسالة خطأ اختيارية أسفله — تتحرك العُقد الثلاث معًا كوحدة واحدة.
labelText وerrorText كحقول StringProperty وإعادتها من labelTextProperty()، يستطيع المستدعون استخدام واجهة الربط الكاملة — بما فيها تعبيرات Bindings.when(…) والمستمعين — تمامًا كما يفعلون مع أي عنصر تحكم مدمج.
استخدام المكون في المشهد
بمجرد وجود الفئة تستخدمها كأي عُقدة أخرى:
تحميل FXML داخل عنصر تحكم مخصص
للعناصر ذات التخطيطات المعقدة، يكون الاحتفاظ بالبنية في ملف FXML أكثر نظافة بكثير من إنشاء شجرة المشهد بالكامل في Java خالص. النمط هو تحميل FXML داخل منشئ المكون ذاته، مع قيام المكون بدور الجذر والمتحكم معًا.
<fx:root> — لا <VBox> — كعنصر جذر في ملف FXML. هذه هي الإشارة إلى FXMLLoader بأن كائن الجذر مُقدَّم خارجيًا (عبر setRoot(this)). بدونه يُنشئ المحمّل نسخةً ثانية من VBox ولا تُحقن حقول @FXML أبدًا في المكون.
إتاحة واجهة CSS
يكون عنصر التحكم المُصمَّم جيدًا قابلًا للتخصيص بالـ CSS دون الحاجة إلى تغيير المصدر. هناك مستويان من الدعم يجب أن توفرهما.
- فئات الأنماط: خصّص سلاسل فئات نمط ذات معنى حتى يتمكن المستخدمون من استهداف مكونك في ورقة الأنماط.
getStyleClass().add("labeled-field"); label.getStyleClass().add("labeled-field-label"); field.getStyleClass().add("labeled-field-input"); errorLabel.getStyleClass().add("labeled-field-error");
- الفئات الزائفة في CSS: للحالات مثل غير صالح، استخدم
PseudoClassحتى يستجيب عنصر التحكم لـ:invalidفي CSS.import javafx.css.PseudoClass; private static final PseudoClass INVALID = PseudoClass.getPseudoClass("invalid"); // قم بتبديله عند تغيير الخطأ errorText.addListener((obs, old, val) -> { boolean hasError = val != null && !val.isEmpty(); errorLabel.setVisible(hasError); errorLabel.setManaged(hasError); pseudoClassStateChanged(INVALID, hasError); });في ورقة أنماط التطبيق:.labeled-field:invalid .labeled-field-input { -fx-border-color: #e53935; }
إبقاء عناصر التحكم قابلة للإعادة: إرشادات التصميم
- غلّف التخطيط، وأفصح عن البيانات. قرارات التخطيط الداخلية (المسافات والحشوات وبنية العُقد) يجب ألا تتسرب. ينبغي أن يرى المستدعون فقط واجهة برمجية نظيفة قائمة على الخصائص.
- لا تُضمّن الأبعاد بشكل ثابت داخل المكون. اترك لتخطيط الأصل أو CSS التحكم في الحجم. استخدم
setMaxWidth(Double.MAX_VALUE)على العُقد الفرعية التي تريد أن تنمو مع الحاوية. - فضّل
ObjectPropertyوStringPropertyوBooleanPropertyعلى الحقول البسيطة. هذا يتيح الربط ويجعل المكون يعمل بشكل طبيعي مع باقي JavaFX. - وفّر منشئًا بدون وسيطات بالإضافة إلى المنشئات الملائمة. لا يستطيع FXML استدعاء المنشئات التي تأخذ معاملات إلا إذا استخدمت
@NamedArg، لذا يبقى المنشئ الافتراضي المكوّن قابلًا للاستخدام من FXML.
@FXML في جسم المنشئ — استخدم @FXML initialize() بدلًا من ذلك. عندما يستدعي محمّل FXML منشئك تكون الحقول المحقونة قيمتها null؛ فهي لا تُملأ إلا بعد تحليل XML. أعدّ الروابط والمستمعين التي تشير إلى تلك الحقول داخل التابع initialize()، الذي يستدعيه المحمّل بمجرد اكتمال الحقن.
تعبئة عناصر التحكم لإعادة الاستخدام عبر المشاريع
بمجرد امتلاكك عدة عناصر تحكم مخصصة يستحق تعبئتها كـ JAR مستقلة (أو وحدة Java). ضع كل فئة تحكم وموردها FXML في نفس الحزمة. في المشروع المعياري، أفصح عن الحزمة في module-info.java:
توجيه opens … to javafx.fxml إلزامي لأن FXMLLoader يستخدم الانعكاس (reflection) لحقن الحقول المُعلَّمة بـ @FXML، ونظام الوحدات يحجب الوصول الانعكاسي افتراضيًا.
الخلاصة
تُبنى عناصر التحكم المخصصة بامتداد لوحة تخطيط (التركيب) أو Control (عند الحاجة إلى تخصيص كامل بالسمات)، مع الإفصاح عن واجهة برمجية قائمة على الخصائص، وتحميل FXML بنمط <fx:root> عندما يكون التخطيط معقدًا، وتوفير خطاطيف CSS عبر فئات الأنماط والفئات الزائفة. والنتيجة عُقدة لا تختلف — من منظور شجرة المشهد — عن أي عنصر تحكم مدمج في JavaFX، ويمكن لأي شاشة في التطبيق إعادة استخدامها دون تكرار.