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

فئة Application ودورة حياة التطبيق

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

فئة Application ودورة حياة التطبيق

يدور كل برنامج JavaFX حول فئة مجردة واحدة هي javafx.application.Application. إن الوراثة منها وتنفيذ أسلوبها المجرد start() هو الحدّ الأدنى المطلوب لعرض نافذة على الشاشة. لكن فئة Application تفعل أكثر بكثير من مجرد استدعاء start() — فهي تُدير دورة حياة متسلسلة بعناية تُهيّئ أدوات الواجهة الرسومية، وتُسلّم التحكم لكودك على الخيط الصحيح، وتُنهي كل شيء بشكل نظيف عند إغلاق المستخدم آخر نافذة. إن فهم دورة الحياة هذه هو ما يُميّز مطوّر JavaFX عن شخص نسخ مثال "Hello World" ببساطة.

أساليب دورة الحياة الثلاثة

تُعرّف فئة Application ثلاثة أساليب قابلة للتجاوز تُستدعى بترتيب مضمون:

  1. init() — يُستدعى مرةً واحدة على خيط المشغّل (Launcher Thread) قبل إنشاء الواجهة الرسومية. تجاوَزه لتحميل الإعدادات أو فتح اتصالات قاعدة البيانات أو أي عمل مكثّف لا يلمس عُقد الواجهة. التنفيذ الافتراضي لا يفعل شيئًا.
  2. start(Stage primaryStage) — يُستدعى على خيط تطبيق JavaFX (خيط الواجهة). هذا هو الأسلوب المجرد الوحيد؛ ويجب عليك تنفيذه. بحلول وقت استدعائه تكون أدوات الواجهة قد هُيِّئت بالكامل ويكون آمنًا إنشاء كائنات Stage وScene وعرضها. المعامل primaryStage هو النافذة الرئيسية التي يوفّرها النظام.
  3. stop() — يُستدعى مرةً واحدة على خيط تطبيق JavaFX بعد إغلاق آخر نافذة (أو بعد استدعاء Platform.exit()). تجاوَزه لتحرير الموارد — إغلاق مؤشرات الملفات، وإيقاف الخيوط الخلفية، وتفريغ ذاكرة التخزين المؤقت. التنفيذ الافتراضي لا يفعل شيئًا.
ملكية الخيط مهمة. يعمل init() على خيط المشغّل — لا يمكنك إنشاء Stage أو أي عقدة واجهة هناك. يعمل start() وstop() على خيط تطبيق JavaFX — جميع عمليات الواجهة تنتمي هنا. انتهاك هذه القواعد يتسبب في رمي IllegalStateException أثناء التشغيل.

أسلوب launch()

المهمة الحقيقية الوحيدة لأسلوب main() هي استدعاء Application.launch(). هذا الأسلوب الساكن:

  1. يُشغّل أدوات JavaFX وخيط معالجة الأحداث الخاص بها.
  2. يُنشئ نسخة من الفئة الفرعية Application الخاصة بك (باستخدام مُنشئها الذي لا يقبل معاملات).
  3. يستدعي init() على خيط المشغّل.
  4. يستدعي start(primaryStage) على خيط تطبيق JavaFX.
  5. يُحجب حتى يخرج التطبيق.
  6. يستدعي stop() على خيط تطبيق JavaFX.

الشكل الأكثر شيوعًا يمرّر حرفية الفئة وأي وسيطات سطر أوامر:

public static void main(String[] args) { Application.launch(MyApp.class, args); }

عند الاستدعاء من داخل الفئة الفرعية Application نفسها، التحميل الزائد الذي يُحذف فيه اسم الفئة يُعادل ذلك:

public static void main(String[] args) { launch(args); // يستنتج الفئة المُستدعِية أثناء التشغيل }
بيئات JDK الحديثة مع JavaFX المُعياري (Java 11+): إذا كنت تعمل مع JavaFX SDK كوحدات خارجية (شائع مع OpenJFX)، مرّر مسار الوحدة و--add-modules javafx.controls,javafx.fxml إلى JVM. استدعاء Application.launch() نفسه لم يتغيّر — التغيير في طريقة تشغيل JVM لا في الكود.

تطبيق بسيط — بشرح مفصّل

القائمة أدناه هي أصغر برنامج JavaFX كامل. اقرأ كل تعليق بعناية؛ لكل سطر سبب وجيه للوجود.

import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.stage.Stage; public class HelloApp extends Application { // يُستدعى على خيط المشغّل — آمن لأعمال التهيئة غير الرسومية. // مُحذف هنا لأنه لا شيء نُهيّئه مسبقًا. @Override public void start(Stage primaryStage) { // primaryStage يُوفّره النظام — لا تُنشئ Stage هنا أبدًا. Label label = new Label("مرحبًا، JavaFX!"); Scene scene = new Scene(label, 400, 200); // عقدة الجذر، العرض، الارتفاع primaryStage.setTitle("أول تطبيق لي"); primaryStage.setScene(scene); primaryStage.show(); // يجعل النافذة مرئية } // stop() غير مُتجاوَز — لا شيء نُنظّفه public static void main(String[] args) { launch(args); // يُسلّم التحكم لبيئة تشغيل JavaFX } }

تمرير معاملات إلى تطبيقك

أي سلاسل نصية تُمرَّر بعد اسم الفئة في سطر الأوامر (أو عبر launch(args)) تكون متاحة داخل init() وstart() عبر getParameters(). هذه هي الآلية المدمجة في JavaFX لقراءة إعدادات وقت التشغيل دون اللجوء إلى System.getenv() أو حقل ساكن.

@Override public void start(Stage stage) { Parameters params = getParameters(); // المعاملات المسمّاة: --mode=dark --user=Alice String mode = params.getNamed().getOrDefault("mode", "light"); String user = params.getNamed().getOrDefault("user", "Guest"); // المعاملات غير المسمّاة (الموضعية) List<String> raw = params.getRaw(); stage.setTitle("مرحبًا " + user + " [وضع " + mode + "]"); // ... بقية start() }

الإغلاق النظيف

يخرج JavaFX تلقائيًا عند إغلاق آخر نافذة، لكن لديك تحكم دقيق:

  • Platform.setImplicitExit(false) — يمنع الخروج التلقائي عند إغلاق آخر نافذة. مفيد لتطبيقات شريط النظام التي تُخفي نافذتها دون الإنهاء.
  • Platform.exit() — يطلب إيقاف تشغيل منظّم. يُشغّل stop() ثم يُنهي خيط تطبيق JavaFX. استدعه من أي خيط.
  • استدعاء System.exit() يتجاوز stop() كليًا — تجنّبه إلا في حالات خطأ لا يمكن التعافي منها.
لا تستدعِ System.exit() أبدًا كمسار إنهاء عادي. فهو يتجاوز stop()، لذا سيُتخطّى بصمت أي كود تنظيف وضعته هناك — تفريغ الكتابات، وتحرير الأقفال، وحفظ الحالة. استخدم دائمًا Platform.exit() لإغلاق تطبيق JavaFX بشكل مناسب.

دورة الحياة عمليًا — قالب واقعي

التطبيقات الحقيقية تتجاوز الخطافات الثلاثة لدورة الحياة. إليك قالبًا يمكنك نسخه وتكييفه:

import javafx.application.Application; import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class RealApp extends Application { private ExecutorService workerPool; @Override public void init() { // يعمل على خيط المشغّل — آمن للإدخال/الإخراج الحاجب واتصالات قاعدة البيانات وغيرها. workerPool = Executors.newFixedThreadPool(4); System.out.println("init() — الخيط: " + Thread.currentThread().getName()); } @Override public void start(Stage primaryStage) { System.out.println("start() — الخيط: " + Thread.currentThread().getName()); StackPane root = new StackPane(); Scene scene = new Scene(root, 800, 600); primaryStage.setTitle("تطبيق حقيقي"); primaryStage.setScene(scene); primaryStage.setOnCloseRequest(e -> Platform.exit()); primaryStage.show(); } @Override public void stop() { // يعمل على خيط تطبيق JavaFX — نظّف موارد الخلفية. System.out.println("stop() — الخيط: " + Thread.currentThread().getName()); workerPool.shutdownNow(); } public static void main(String[] args) { launch(args); } }

شغّل هذا وراقب وحدة التحكم — ستشاهد بالضبط أي خيط يُنفّذ كل أسلوب، مما يؤكد تسلسل دورة الحياة الذي تعلّمته أعلاه.

الخلاصة

فئة Application هي نقطة دخول ومالكة عمر كل برنامج JavaFX. يتولّى init() الإعداد السابق للواجهة على خيط المشغّل؛ ويبني start() الواجهة ويعرضها على خيط التطبيق؛ ويُحرّر stop() الموارد عند إغلاق التطبيق. يُنسّق Application.launch() كل هذا من أسلوب main(). إن وضع العمل المكثّف في init()، وعمل الواجهة في start()، والتنظيف في stop() يمنحك بنية تطبيق نظيفة ومتوقّعة منذ اليوم الأول. في الدرس القادم ستستكشف كائني Stage وScene اللذين يتلقّاهما start() ويُنشئهما.