اذهب إلى المحتوى

لوحة المتصدرين

  1. Wael Aljamal

    Wael Aljamal

    الأعضاء


    • نقاط

      11

    • المساهمات

      6975


  2. khaled0mohammed

    khaled0mohammed

    الأعضاء


    • نقاط

      5

    • المساهمات

      17


  3. Adnane Kadri

    Adnane Kadri

    الأعضاء


    • نقاط

      3

    • المساهمات

      5196


  4. رضوى العربي

    رضوى العربي

    الأعضاء


    • نقاط

      3

    • المساهمات

      114


المحتوى الأكثر حصولًا على سمعة جيدة

المحتوى الأعلى تقييمًا في 01/11/22 في كل الموقع

  1. ما هو مفهوم Encapsulation وما الفائدة منه
    2 نقاط
  2. 2 نقاط
  3. لدي سوال في الجاف سكربت كيف أنشئ مكتبة مصغرة للبحث عن كتاب معين من برنامج تم انشائه عن طريق 2D Array من خلاص الصورة التالية اذا احد يقدر يساعدني في الكود لانه بيساعدني كثير في فهم ال Array وله جزيل الشكر وهذا هو الكود الخاص بي : let BookStore =[["ID","Book Title","Author","Price", "Quantity"], [1,"Start with why","Simon Sinek",80.0,13], [2,"But how do it know","J. Clark Scott",59.9,22], [3,"Clean Code","Robert Cecil Martin",50.0,5], [4,"Zero to One","Peter Thiel",47,12], [5,"You don't know JS","Kyle Simpson",39.9,9] ];
    2 نقاط
  4. المشكلة في تكون مفتاح التطبيق، يمكنك من Terminal تنفيذ الأمر التالي: keytool -genkey -v -keystore my-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
    2 نقاط
  5. عند الضغط على Bulid APK يظهر هذا الخطأ : FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':app:validateSigningRelease'. > java.util.concurrent.ExecutionException: com.android.builder.utils.SynchronizedFile$ActionExecutionException: java.io.IOException: com.android.ide.common.signing.KeytoolException: Failed to create keystore. * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 1m 4s Running Gradle task 'assembleRelease'... 67.9s Gradle task assembleRelease failed with exit code 1
    1 نقطة
  6. لقد ناقشنا حتى الآن الكثير من الجوانب الأساسية المُستخدَمة ببرمجة واجهات المُستخدِم الرسومية (GUI)، وتَعلَّمنا ما يَكفِي لكتابة بعض البرامج الشيقة، ولهذا سنَفْحَص مثالين كاملين يُطبقان ما درسناه حتى الآن عن برمجة الواجهات الرُسومية (GUI) إلى جانب ما درسناه عن البرمجة في العموم بالفصول السابقة. ما يزال هنالك الكثير من الأمور الآخر لتَعْلُّمها، ولهذا سنعود للحديث عن نفس الموضوع بالفصل ١٣. لعبة ورق بسيطة برنامجنا الأول عبارة عن نُسخة من برنامج سطر الأوامر HighLow.java من القسم الفرعي ٥.٤.٣، ولكنها ستَكُون مُدعَّمة بواجهة مُستخدِم رُسومية (GUI). بنُسخة البرنامج الجديدة HighLowGUI.java، ينبغي أن تَنظُر إلى ورقة لعب ثم تَتَوقَّع ما إذا كانت قيمة ورقة اللعب التالية ستَكُون أكبر أو أقل من قيمة ورقة اللعب الحالية. إذا كان تَوقُّعك خاطئًا، فستَخسَر المباراة أما إذا كان صحيحًا لثلاث مرات متتالية، فستَفوز بالمباراة. بَعْد انتهاء أية مباراة، يُمِكنك النَقْر على "New Game" لبدء مباراة جديدة. تُوضِح الصورة التالية ما سيَبدُو عليه البرنامج خلال المباراة: شيفرة البرنامج بالكامل مُتاحَة بالملف HighLowGUI.java. حاول تَصْرِيفها وتَشْغِيلها. ملحوظة: يَتَطلَّب البرنامج كُلًا من الملفات Card.java و Deck.java و Hand.java من القسم ٥.٤ حيث تُعرِّف أصنافًا (classes) مُستخدَمة ضِمْن البرنامج كما أنه يَتَطلَّب ملف صورة ورق اللعب cards.png الذي اِستخدَمناه بالبرنامج RandomCards.java بالقسم الفرعي ٦.٢.٤. ربما من السهل تَخْمِين التخطيط (layout) المُستخدَم بالبرنامج: يَستخدِم HighLowGUI حاوية من النوع BorderPane كمُكوِّن جذري (root) لمبيان المشهد (scene graph). تَتَضمَّن تلك الحاوية حاوية آخرى من النوع Canvas مرسُوم عليها ورق لعب ورسالة وتَقَع بمنتصف الحاوية الخارجية. يَتَضمَّن المَوْضِع السفلي من الحاوية الخارجية حاوية آخرى من النوع HBox تَحتوِي بدورها على ثلاثة أزرار ضُبِطَت بحيث يَكُون لها جميعًا نفس قيمة العَرْض بغَرْض مَلْئ الحاوية بالكامل كما أَوضَحنا بالقسم الفرعي ٦.٥.٣. يُنفِّذ التابع start()‎ التالي ما سبق: public void start(Stage stage) { cardImages = new Image("cards.png"); // حمل صورة ورق اللعب board = new Canvas(4*99 + 20, 123 + 80); // مسافة أربعة أوراق Button higher = new Button("Higher"); // أنشئ المفاتيح higher.setOnAction( e -> doHigher() ); // وجهز معالجات الأحداث Button lower = new Button("Lower"); lower.setOnAction( e -> doLower() ); Button newGame = new Button("New Game"); newGame.setOnAction( e -> doNewGame() ); HBox buttonBar = new HBox( higher, lower, newGame ); // اضبط كل زر لكي يحصل على ثلث العرض المتاح higher.setPrefWidth(board.getWidth()/3.0); lower.setPrefWidth(board.getWidth()/3.0); newGame.setPrefWidth(board.getWidth()/3.0); BorderPane root = new BorderPane(); // أنشئ المكون الجذري لمبيان المشهد root.setCenter(board); root.setBottom(buttonBar); doNewGame(); // جهز المباراة الأولى Scene scene = new Scene(root); // Finish setting up the scene and stage. stage.setScene(scene); stage.setTitle("High/Low Game"); stage.setResizable(false); stage.show(); } // end start() تَستدعِي مُعالِجات الأحداث (event handlers) بالأعلى توابعًا مُعرَّفة بمكان آخر من البرنامج مثل التابع doNewGame()‎. تُعدّ برمجة تلك التوابع (methods) تمرينًا جيدًا على التفكير وفقًا لما يُعرَف باسم "آلة الحالة (state machine)": ينبغي أن تُفكِر بالحالات (states) التي يُمكِن للمباراة أن تَقَع ضِمْنها، وبالكيفية التي قد تَتَغيَّر بها من حالة لآخرى، وكذلك بالكيفية التي ستَعتمِد بها مُعالجات الأحداث (events) على تلك الحالات. لاحِظ أن الأسلوب الذي اتِبعَناه بالنسخة الأصلية النصية من البرنامج بالقسم الفرعي ٥.٤.٣ غَيْر مُناسِب هنا حيث ستُربكَك محاولة التفكير بالمباراة تفصيليًا خطوة بخطوة من البداية وحتى النهاية أكثر مما ستُساعِدك. تَتَضمَّن حالة المباراة ورق لعب (cards) مُمثَل بكائن (object) من النوع Hand بالإضافة إلى رسالة مُمثَلة بكائن من النوع String. تُخزَّن تلك القيم ضِمْن مُتْغيِّرات نُسخ (instance variables). هنالك جانب آخر أقل وضوحًا سنُضْمّنه بحالة (state) المباراة: في بعض الأحيان، يَكُون هنالك مباراة قَيْد التّنْفِيذ ونَكُون بانتظار اختيار المُستخدِم لتَوقُّعه عن ورقة اللعب التالية. في بعض الأحيان الآخرى، نَكُون للتو قد انتهينا من مباراة وبانتظار نَقْر المُستخدِم على الزر "New Game". لذلك، ربما من الجيد أن نُضيِف عنصرًا جديدًا إلى حالة المباراة يُميِّز ذلك الاختلاف البسيط. يَستخدِم البرنامج مُتْغيِّر نُسخة (instance variable) من النوع boolean اسمه gameInProgress لذلك الغرض. عندما يَنقُر المُستخدِم على أحد الأزرار، فقد تَتَغيَّر حالة المباراة. يُعرِّف البرنامج ثلاثة توابع (methods) للاستجابة على أحداث الضَغْط على زر هي: doHigher()‎ و doLower()‎ و newGame()‎، والتي يُوكَل إليها مُهِمّة مُعالِجة الأحداث (event-handling). لأننا لا نُريد أن نَسمَح للمُستخدِم ببدء مباراة جديدة طالما كان هنالك مباراة آخرى قَيْد التّنْفِيذ بالفعل، فإن استجابة التابع newGame()‎ ستَختلِف بناءً على قيمة مُتْغيِّر الحالة gameInProgress. إذا كانت قيمته تُساوِي true أي أن هنالك مباراة قَيْد التّنْفِيذ، فينبغي أن يُضبَط مُتْغيِّر النُسخة message إلى رسالة خطأ معينة. في المقابل، إذا كانت قيمة المُتْغيِّر تُساوِي false أي ليس هنالك أي مباراة، فينبغي ضَبْط جميع مُْتْغيِّرات الحالة (state variables) إلى القيم المناسبة لبدء مباراة جديدة. بجميع الأحوال، لابُدّ من إعادة رَسْم الرقعة (board) حتى يَرَى المُستخدِم أن الحالة قد تَغيَّرت. يُمكِننا تَعرِيف التابع newGame()‎ على النحو التالي: private void doNewGame() { if (gameInProgress) { // إذا لم تنته المباراة الحالية بعد، اعرض رسالة خطأ message = "You still have to finish this game!"; drawBoard(); return; } deck = new Deck(); // أنشئ مجموعة ورق اللعب واليد hand = new Hand(); deck.shuffle(); hand.addCard( deck.dealCard() ); // اسحب ورقة اللعب الأولى message = "Is the next card higher or lower?"; gameInProgress = true; drawBoard(); } // end doNewGame() التابعان doHigher()‎ و doLower()‎ مُتطابقان تقريبًا، وربما كان بإِمكَاننا دَمْجهُما ضِمْن تابع واحد يَستقبِل مُعاملًا (parameter) واحدًا. عندما يَنقُر المُستخدِم على الزر "Higher"، يُستدعَى البرنامج doHigher()‎، ولأن ذلك الاستدعاء ليس له أي معنى إلا في حالة وجود مباراة قَيْد التّنْفِيذ، يَفْحَص التابع قيمة مُتْغيِّر الحالة gameInProgress أولًا. إذا كانت قيمته تُساوِي false، فإنه يَعرِض رسالة خطأ أما إذا كانت قيمته تساوي true أي توجد مباراة قَيْد التّنْفِيذ، فإنه يُضيِف ورقة لعب جديدة إلى اليد (hand) ثم يَفْحَص تَوقُّع المُستخدِم. قد تنتهي المباراة في تلك اللحظة بفوز المُستخدِم أو بخسارته، وعندها يََضبُط التابع قيمة مُتْغيِّر الحالة gameInProgress إلى القيمة false لأن المباراة قد انتهت ببساطة. بجميع الأحوال، يُعاد رَسْم الرُقعة (board) لضمان عَرْض الحالة (state) الجديدة. اُنظر التابع doHigher()‎: private void doHigher() { if (gameInProgress == false) { // إذا كانت المباراة قد انتهت، فمن الخطأ النقر // ‫على زر "Higher" لذلك اعرض رسالة خطأ وانهي المعالجة message = "Click \"New Game\" to start a new game!"; drawBoard(); return; } hand.addCard( deck.dealCard() ); // اسحب ورقة لعب إلى اليد int cardCt = hand.getCardCount(); Card thisCard = hand.getCard( cardCt - 1 ); // تم سحب ورقة لعب Card prevCard = hand.getCard( cardCt - 2 ); if ( thisCard.getValue() < prevCard.getValue() ) { gameInProgress = false; message = "Too bad! You lose."; } else if ( thisCard.getValue() == prevCard.getValue() ) { gameInProgress = false; message = "Too bad! You lose on ties."; } else if ( cardCt == 4) { gameInProgress = false; message = "You win! You made three correct guesses."; } else { message = "Got it right! Try for " + cardCt + "."; } drawBoard(); } // end doHigher() يَعتمِد التابع drawBoard()‎ على قيم مُتْغيِّرات الحالة (state variables) لتَقْرِير ما ينبغي له أن يَرسِمه ضِمْن الحاوية (canvas)، فيَعرِض السِلسِلة النصية المُخزَّنة بالمُتْغيِّر message ويَرسِم ورق اللعب ضِمْن اليد hand. وأخيرًا، إذا كانت المباراة قَيْد التّنْفِيذ، فإنه يَرسِم ورقة لعب إضافية مقلوبة تُمثِل ورقة اللعب التالية بمجموعة ورق اللعب (deck). ملحوظة: لقد ناقشنا التقنية المُستخدَمة لرَسْم ورقة لعب (card) مُفرّدة بالقسم ٦.٢. القوائم Menu وشريط القوائم MenuBar سنَعرِض الآن برنامج رَسْم اسمه "MosaicDraw". تَتَوفَّر شيفرة البرنامج بالملف MosaicDraw.java كما يَتَطلَّب الصَنْف MosaicCanvas.java. تُظهِر الصورة التالية رسمة مَصنُوعة باِستخدام البرنامج: عندما يَنقُر المُستخدِم على الفأرة ويَسحَبها داخل مساحة الرسم (drawing area)، فإن الفأرة تَترُك أثرًا مُكوَّنًا من مربعات صغيرة مُلوَّنة عشوائيًا. بالإضافة إلى ذلك، يُوفِّر البرنامج شريط قوائم (menu bar) أعلى مساحة الرسم. أولًا، تحتوي قائمة "Control" على أوامر لمَلْئ مساحة الرسم وتنظيفها مع خيارات آخرى تُؤثِر على مَظهَر الصورة. ثانيًا، تَسمَح قائمة "Color" بتَخْصِيص اللون المُستخدَم أثناء الرسم. ثالثًا، تُؤثِر قائمة "Tools" على سلوك الفأرة، فعندما نَستخدِم أداة "Draw" الافتراضية، تَترُك الفأرة أثرًا مُكوَّنًا من مُربعات مُفردّة، ولكن عندما نَستخدِم أداة "Draw 3x3"، فإنها تترُك أثرًا يَبلُغ عَرْضه ثلاثة مربعات. تَتَوفَّر أيضًا أداة "Erase" لإعادة ضَبْط المربعات إلى اللون الأسود الافتراضي. سنُمثِل مساحة الرسم (drawing area) داخل البرنامج باستخدام الصَنْف الفرعي MosaicCanvas المُشتَق من الصَنْف Canvas المُعرَّف بالملف MosaicCanvas.java. لا يُدعِّم ذلك الصَنْف "رسوم الفسيفساء" مباشرةً، ولكن بإمكاننا اِستخدَامه لمحاكاتها عن طريق ضَبْط لون كل مربع على حدى. أَعدّ البرنامج MosaicDraw معالجين لحدثيّ الفأرة MousePressed و MouseDragged الواقعين ضِمْن الحاوية (canvas) بحيث تَستجِيب تلك المُعالجات بتطبيق الأداة (tool) المُختارة حاليًا على مَوضِع مُؤشر الفأرة عند النَقْر. يُعدّ هذا البرنامج في العموم مثالًا جيدًا على تَطبيق مُستمع حَدَث (event listeners) على كائن (object) مما يَسمَح له بالقيام بأمور لم يَكُن هو ذاته مُبرمَجًا للقيام بها. ستَجِد شيفرة البرنامج بالكامل بالملف MosaicDraw.java. لن نُناقش جميع تفاصيلها هنا، ولكن يُفْترَض أن تَكُون قادرًا على فهمها بالكامل بَعْد قراءة هذا القسم. في المقابل، تَعتمِد شيفرة الملف MosaicCanvas.java على بعض التقنيات التي لن تَتَمكَّن من استيعابها في الوقت الحالي، ومع ذلك، يُمكِنك قراءة التعليقات الموجودة بالملف لكي تَتَعلَّم المزيد عن واجهة برمجة التطبيقات (API) الخاصة بالصَنْف MosaicCanvas. سنَستخدِم مُكوِّن شريط القوائم (menu bar) للمرة الأولى ضِمْن البرنامج MosaicDraw. تَسمَح لك مكتبة جافا إف إكس (JavaFX) بإضافة قائمة إلى البرنامج بسهولة عن طريق استخدام كائنات (objects) تَنتمِي للصَنْف MenuItem أو إلى أي من أصنافها الفرعية (subclasses) الُمعرَّفة بحزمة javafx.scene.control لتَمثيِل عناصر القائمة. تَعمَل عناصر القائمة تقريبًا بنفس الكيفية التي تعمل بها الأزرار (buttons). يُوفِّر الصَنْف MenuItem باني كائن (constructor) بمُعامل واحد عبارة عن نص عنصر القائمة. بإمكانك استخدامه لإنشاء كائن من ذلك الصَنْف كالتالي: MenuItem fillCommand = new MenuItem("Fill"); نظرًا لأن عناصر القائمة قد تَتَكوَّن من صورة ونص مثل الأزرار، يُوفِّر الصَنْف باني كائن (constructor) آخر بمُعاملين لتَخْصِيصهما. عندما يختار المُستخدِم عنصر قائمة من النوع MenuItem من قائمة، يَقَع حَدَث من النوع ActionEvent. يُمكِنك إضافة مُستمِع حَدَث (event listener) إليه باِستخدَام تابعه setOnAction(handler)‎. بالإضافة إلى ذلك، قد تَستخدِم التابع setDisable(disabled)‎ لتَفْعيل عنصر قائمة أو تَعْطِيله، وكذلك التابع setText()‎ لتَغْيِير النص المعروض. يختلف عنصر القائمة (menu item) عن الزر (button) بكَوْنه يَظهَر ضِمْن قائمة (menu). في الواقع، أي عنصر قائمة هو عبارة عن عقدة من النوع Node تَظهَر عادةً ضِمْن قائمة، ولكنها قد تَظهَر أيضًا بأي مكان آخر ضِمْن مبيان المشهد (scene graph). تُمثَل القوائم (menus) بمكتبة جافا إف إكس (JavaFX) باِستخدَام الصَنْف Menu. لمّا كان ذلك الصَنْف صنفًا فرعيًا مُشْتقًا من الصَنْف MenuItem، فإنه من المُمكن إضافة قائمة ضِمْن قائمة آخرى كعنصر، وتَكُون عندها قائمة فرعية (submenu) من القائمة الأصلية المُضافة إليها. أي قائمة من الصَنْف Menu لها اسم يُمكِنك تَخْصِيصه من خلال باني الكائن (constructor). يُعيد تابع النسخة getItems()‎ المُعرَّف بالصَنْف Menu قائمة (list) بعناصر القائمة (menu items)، ويُمكِنك أن تُضيِف عنصر قائمة جديد بإضافته إلى تلك القائمة. تُوضِح الشيفرة التالية طريقة إضافة عناصر (items) إلى قائمة: Menu sampleMenu = new Menu("Sample"); sampleMenu.getItems().add( menuItem ); // اضف عنصر قائمة إلى القائمة sampleMenu.getItems().addAll( item1, item2, item3 ); // أضف عدة عناصر ينبغي أن تضيف القائمة (menu) بَعْد إنشائها إلى شريط قوائم (menu bar) يُمثَل باِستخدَام الصنف MenuBar. لا يملك شريط القوائم اسمًا، فهو يعمل فقط بمثابة حاوي (container) لعدة قوائم. يمكنك استدعاء باني الكائن (constructor) الخاص به بدون أي معاملات أو بقائمة مُعاملات (parameter list) تحتوي على القوائم (menus) المطلوب إضافتها إليه. يعيد تابع النسخة getMenus()‎ قائمة من القوائم (menus). يُعيد تابع النسخة getMenus()‎ المُعرَّف بالصَنْف MenuBar قائمة (list) بالقوائم (menus) ضمن الشريط، ويستخدم كُلًا من التابعين add()‎ و addAll()‎ لإضافة قوائم (menus) جديدة إلى الشريط. يَستخدَم برنامج MosaicDraw ثلاثة قوائم هي controlMenu و colorMenu و toolMenu. تُنشِئ التَعْليمَات التالية شريط قوائم (menu bar) وتُضيِف إليه عدة قوائم (menus): MenuBar menuBar = new MenuBar(); menuBar.getMenus().addAll(controlMenu, colorMenu, toolMenu); أو قد نُمرِّر القوائم (menu) مباشرةً إلى الباني (constructor) كالتالي: MenuBar menuBar = new MenuBar(controlMenu, colorMenu, toolMenu); تَبقَّى لنا إضافة شريط القوائم (menu bar) إلى مبيان المشهد (scene) الخاص بالبرنامج. عادةً ما يَكُون شريط القوائم أعلى النافذة، ولكن من الممكن وَضْعُه بأي مكان آخر. عادةً ما تَستخدِم البرامج -المُحتويّة على شريط قوائم- كائن حاوية من النوع BorderPane يَعمَل كمُكوِّن جذري (root) لمبيان المشهد (scene graph). يَظهَر شريط القوائم (menu bar) بالمُكوِّن الموجود أعلى الحاوية أما المُكوِّنات الرسومية (GUI components) الآخرى فتَظهَر بالمواضع الأربعة الآخرى. يُمكِنك إذًا إنشاء أي قائمة على النحو التالي: أولًا، اِنشِئ شريط قوائم (menu bar). ثانيًا، اِنشِئ قوائم (menus) وأَضِفها إلى ذلك الشريط. ثالثًا، اِنشِئ عناصر قائمة (menu items) وأَضِفها إلى تلك القوائم. أَضِف مُستمعِي أحداث للأحداث (events) الصادرة عن تلك العناصر لمُعالجتها. أخيرًا، ضَعْ شريط القوائم (menu bar) بأعلى مُكوِّن الحاوية من النوع BorderPane. تُوفِّر مكتبة جافا إف إكس (JavaFX) أنواعًا آخرى من عناصر القوائم (menu items)، والتي يُمكِن بطبيعة الحال إضافتها إلى أي قائمة. تلك العناصر مُعرَّفة كأصناف فرعية (subclasses) من الصَنْف MenuItem. على سبيل المثال، يَستخدِم المثال التالي عنصر قائمة من النوع SeparatorMenuItem لإضافة خط بين عناصر القائمة الأخرى: menu.getItems().add( new SeparatorMenuItem() ); يتوفَّر أيضًا الصنفين الفرعيين CheckMenuItem و RadioMenuItem. يُمثِل الصَنْف CheckMenuItem عنصر قائمة يُمكِنه أن يَكُون بواحدة من حالتين: مُختار (selected) أو غير مُختار (not selected). تَتَبدَّل حالة العنصر عندما يختاره المُستخدِم من القائمة الحاوية له. يَعمَل ذلك العنصر بنفس الطريقة التي تَعمَل بها مُربعات الاختيار من الصَنْف CheckBox (اُنظر القسم الفرعي ٦.٤.٣). يَستخدِم البرنامج MosaicDraw ثلاثة عناصر من النوع CheckMenuItems بقائمة "Control": يُستخدَم إحداها لتَفْعِيل ميزة الألوان العشوائية للمربعات وإلغاؤها. يُستخدَم آخر لغَلْق خاصية التَماثُل (symmetry) وفَتْحها. عند تَفْعِيل تلك الخاصية، تَنعكِس الرسمة أفقيًا ورأسيًا لإنتاج نمط مُتماثِل. أخيرًا، يَعرِض عنصر القائمة الثالث ما يُعرَف بـ"الحشو (grouting)" ويُخفِيه. يَتَكوَّن ذلك الحشو من خطوط رمادية مرسومة حول كل مربع داخل الرسمة. اُنظر التَعْليمَات التالية المُستخدَمة لإضافة عنصر القائمة المُمثِل للخيار "Use Randomness" إلى قائمة "Control": useRandomness = new CheckMenuItem("Use Randomness"); useRandomness.setSelected(true); // شغل العشوائية افتراضيًا controlMenu.getMenus().add(useRandomness); // أضف عنصر قائمة إلى القائمة لم نُضِف مُعالجًا للحدث ActionEvent الصادر عن عنصر القائمة useRandomness حيث يَفْحَص البرنامج حالته باستدعاء useRandomness.isSelected()‎ عند حاجته إلى تلوين أي مربع لكي يُقرِّر ما إذا كان سيُضيف بعض العشوائية للون أم لا. في المقابل، عندما يختار المُستخدِم مربع الاختيار "Use Grouting"من القائمة، فلابُدّ أن يُعِيد البرنامج رَسْم الحاوية (canvas) مباشرةً حتى تَعكِس حالتها الجديدة. تُضيِف الشيفرة التالية معالجًا (handler) إلى عنصر القائمة useGrouting المُمثِل لذلك الخيار لكي يَستدعِي التابع المناسب: useGrouting.setOnAction( e -> doUseGrouting(useGrouting.isSelected()) ); تحتوي قائمة "Tools" وقائمة "Color" بالبرنامج على عناصر قائمة من النوع RadioMenuItem تَعمَل بنفس الطريقة التي تَعمَل بها أزرار الانتقاء من الصَنْف RadioButton (اُنظر القسم الفرعي ٦.٤.٣). يُمكِن لأي عنصر قائمة من ذلك النوع أن يَكُون مختارً (selected) أو غير مختار، ولكن إذا أضفت عدة عناصر منها إلى كائن من الصَْنْف ToggleGroup، فلن تَتَمكَّن من اختيار أكثر من عنصر واحد فقط ضِمْن تلك المجموعة. يستطيع المُستخدِم أن يختار الأداة التي يَرغَب باِستخدَامها من قائمة "Tools"، ولأنه سيختار بطبيعة الحال أداة واحدة بكل مرة، كان من البديهي تَمثيِل تلك الأدوات بهيئة عناصر قائمة من الصَنْف RadioMenuItem، ومن ثَمَّ إضافتها جميعًا إلى كائن من الصَنْف ToggleGroup. يُؤشَر بعلامة على عنصر القائمة المُمثِل للأداة المُختارة حاليًا بالقائمة، وعندما يختار المُستخدِم أداة جديدة، يَتبدَّل مكان ذلك التأشير كاِستجابة مرئية تُوضِح الأداة المُختارَة حاليًا. بالإضافة إلى ذلك، تَملُك الكائنات من الصَنْف ToggleGroup خاصية "قابلة للمراقبة (observable)" تُمثِل الخيار المُحدَّد حاليًا (اُنظر القسم ٦.٣.٧). يُضيِف البرنامج مُستمِعًا (listener) إلى تلك الخاصية ويُخصِّص لها مُعالِج حَدَث (event handler) يُستدعَى تلقائيًا بكل مرة يختار فيها المُستخدِم أداة جديدة. اُنظر الشيفرة المسئولة عن إنشاء قائمة "Tools": Menu toolMenu = new Menu("Tools"); ToggleGroup toolGroup = new ToggleGroup(); toolGroup.selectedToggleProperty().addListener( e -> doToolChoice(toolGroup.getSelectedToggle()) ); addRadioMenuItem(toolMenu,"Draw",toolGroup, true); addRadioMenuItem(toolMenu,"Erase",toolGroup, false); addRadioMenuItem(toolMenu,"Draw 3x3",toolGroup, false); addRadioMenuItem(toolMenu,"Erase 3x3",toolGroup, false); وبحيث يُعرَّف التابع addRadioMenuItem على النحو التالي: private void addRadioMenuItem(Menu menu, String command, ToggleGroup group, boolean selected) { RadioMenuItem menuItem = new RadioMenuItem(command); menuItem.setToggleGroup(group); menu.getItems().add(menuItem); if (selected) { menuItem.setSelected(true); } } الشيفرة المسئولة عن إنشاء شريط القوائم (menua bar) مُعرَّفة بالكامل ضِمْن تابع (method) اسمه هو createMenuBar()‎. Scene و Stage قبل أن ننتهي من هذه المقدمة المُختصَرة عن برمجة واجهات المُستخدِم الرسومية (GUI)، سنَفْحَص صَنْفين أساسيين على نحو أكثر تفصيلي: Scene من حزمة package javafx.scene و Stage من حزمة package javafx.stage. يُمثِل أي مشهد من الصَنْف Scene مساحة المحتويات ضِمْن نافذة، ويَحمِل المُكوِّن الجذري لمبيان المشهد (scene graph) الخاص بها. يُعرِّف الصَنْف Scene بواني كائن (constructors) كثيرة تَستقبِل جميعها المُكوِّن الجذري لمبيان المشهد كمُعامل (parameters) إلى جانب عدة معاملات آخرى. على سبيل المثال، يَستقبِل الباني new Scene(root)‎ مُعاملًا واحدًا فقط يُمثِل المُكوِّن الجذري، ويُعدّ أكثر بواني الصنف شيوعًا. يُمكِنك أيضًا أن تُمرِّر عَرْض المشهد وارتفاعه كمُعاملات للباني باِستخدَام Scene(root,width,height)‎. إذا كان المُكوِّن الجذري عبارة عن حاوية من النوع Pane، يُضبَط حجم الحاوية بحيث يُساوِي حجم المشهد وتُعرَض محتوياتها بناءً على ذلك الحجم أما إذا لم يُخصَّص حجم المشهد، فإنه يُضْبَط إلى القيمة المفضلة لحجم المشهد. في المقابل، إذا كان المُكوِّن الجذري من الصَنْف Group المُعرَّف بحزمة package javafx.scene، فإنه لا يُضْبَط إلى نفس حجم المشهد وإنما يُقْصّ أي لا يَظهَر أي جزء يَقَع خارج المشهد (scene). لا تستطيع البرامج ضَبْط عَرْض أو ارتفاع مشهد من الصَنْف (Scene)، ولكن إذا تَغيَّر حجم المرحلة (stage) -من الصَنْف Stage- الحاوية لمشهد (scene)، فإن حجم المشهد يَتَغيَّر أتوماتيكيًا ليُطابِق الحجم الجديد لمساحة المُحتويات بالمرحلة (stage) كما يَتَغيَّر حجم المُكوِّن الجذري (root) للمشهد (scene) إذا كان من النوع Pane. يُمكِنك أن تُخصِّص لون مَلْئ (fill) -من النوع Paint- لخلفية مشهد (scene) عن طريق باني الكائن (constructor). مع ذلك، لا تَكُون خلفية المشهد مرئية في العموم لكَوْنها مغطاة بخلفية المُكوِّن الجذري (root). يُضْبَط لون خلفية المُكوِّن الجذري إلى الرمادي الفاتح افتراضيًا، ولكن قد تَضبُطه ليُصبِح شفافًا (transpatent) إذا أردت رؤية خلفية المشهد. تُمثِل أي مرحلة من الصَنْف Scene -المُعرَّف بحزمة javafx.stage- نافذة على شاشة الحاسوب. يَملُك أي تطبيق جافا إف إكس (JavaFX) مرحلة (stage) واحدة على الأقل يُطلَق عليها اسم "المرحلة الرئيسية (primary stage)". يُنشِئ النظام تلك المرحلة ويُمرِّرها كمُعامل (parameter) إلى التابع start()‎ الخاص بالتطبيق. تَستخدِم البرامج في العادة أكثر من مجرد نافذة واحدة كما يُمكِنها أن تُنشِئ كائنات مرحلة جديدة من النوع Stage. سنعُود للحديث عن ذلك بالفصل ١٣. تحتوي أي مرحلة (stage) على مشهد (scene) يَملَئ مساحة المحتويات (content area) الخاصة بها. يستخدم تابع النسخة stage.setScene(scene)‎ لضَبْط المشهد (scene) الخاص بمرحلة (stage). يُمكِنك عَرْض مرحلة لا تحتوي على أي مشهد، وستَظهَر عندها مساحة محتوياتها على هيئة مستطيل فارغ. علاوة على ذلك، تَحتوِي أي مرحلة (stage) على شريط عنوان (title bar) يَظهَر فوق مساحة محتوياتها (content area). يَتَكوَّن ذلك الشريط من عنوان نافذة إلى جانب مجموعة من أزرار التَحكُّم التي يستطيع المُستخدِم أن يَنقُر عليها للقيام بأشياء مثل غَلْق النافذة أو تكبيرها. يُضيِف نظام التشغيل (operating system) -لا الجافا- ذلك الشريط تلقائيًا، ويَتَحكَّم بتصميمه الخارجي. يُستخدَم تابع النسخة stage.setTitle(string)‎ لضَبْط النص المَعرُوض بشريط العنوان، وتستطيع استدعائه بأي وقت. افتراضيًا، يستطيع المُستخدِم أن يَضبُط حجم أي مرحلة (stage) بسَحْب حوافها أو أركانها. مع ذلك، يُمكِنك استدعاء stage.setResizable(false)‎ لكي تَمنَعه من ذلك. لاحِظ أنه حتى بَعْد استدعاء ذلك التابع على مرحلة، فما تزال شيفرة البرنامج ذاتها قادرة على تَغْيِير حجم تلك المرحلة باستدعاء توابع النسخة stage.setWidth(w)‎ و stage.setHeight(h)‎. عادةً ما يُحدَّد الحجم المبدئي لأي مرحلة بحجم المشهد (scene) الموجود داخلها، ولكن بإمكانك استدعاء setWidth()‎ و setHeight()‎ لضَبْط قيمة الحجم المبدئي قَبْل عَرْض النافذة. عندما يَكُون حجم مرحلة (stage) قابلًا للضَبْط من قِبَل المُستخدِم، فإن بإِمكانه تَصغِير النافذة أو تَكْبيرها إلى أي قيمة عشوائية افتراضيًا. يُمكِنك مع ذلك اِستخدَام توابع النسخ stage.setMinWidth(w)‎ و stage.setMaxWidth(w)‎ و stage.setMinHeight(h)‎ و stage.setMaxHeight(h)‎ لوَضْع بعض القيود على القيم المسموح بها. تُطبَق تلك القيود على ما يستطيع المُستخدِم القيام به أثناء سَحْبه لحواف النافذة أو أركانها. يُمكِنك تَغْيِير موضع مرحلة (stage) على الشاشة -عادةً قبل عَرْضها- باستدعاء توابع النُسخ stage.setX(x)‎ و stage.setY(y)‎. تُخصِّص الإحداثيات x و y مَوضِع الركن الأيسر العلوي من النافذة وفقًا لنظام إحداثيات الشاشة. أخيرًا، تَذكَّر أن أي مرحلة (stage) تَظلّ غَيْر مرئية إلى أن تَعرِضها على الشاشة صراحةً باستدعاء تابع النسخة stage.show()‎. عادةً ما يَكُون إظهار "المرحلة الرئيسية (primary stage)" هو آخر ما يُنفِّذه التابع start()‎ الخاص بالتطبيق. إنشاء ملفات Jar الملف المُنتهِي بالامتداد ‎.jar هو عبارة عن ملف جافا أرشيفي (java archive) يَتَضمَّن عددًا من ملفات الأصناف (class files) والموارد (resource files) المُستخدَمة ضِمْن البرنامج. عندما تُنشِئ برنامجًا يَعتمِد على أكثر من ملف واحد، يُنصَح عمومًا بوَضْع جميع الملفات التي يَتَطلَّبها ذلك البرنامج ضِمْن ملف جافا أرشيفي، وعندها لن يحتاج المُستخدِم لأكثر من ذلك الملف حتى يَتَمكَّن من تَشْغِيل البرنامج. يُمكِنك أيضًا أن تُنشِئ ما يُعرَف باسم "ملف جافا أرشيفي تّنْفِيذي (executable jar file)"، والذي يستطيع المُستخدِم أن يُشغِّله بنفس الكيفية التي يُشغِّل بها أي تطبيق آخر أي بالنَقْر المُزدوج على أيقونة الملف، ولكن لابُدّ من وجود نُسخة مُثبتَّة وصحيحة من الجافا على حاسوبه كما ينبغي أن تَكُون إعدادات حاسوبه مَضْبُوطَة بطريقة معينة. عادةً ما تُضبَط تلك الإعدادات تلقائيًا عند تثبيت الجافا بنظامي ويندوز وماك على الأقل. تَعتمِد طريقة إنشاء ملفات الجافا الأرشيفية (jar file) على بيئة البرمجة (programming environment) المُستخدَمة. لقد ناقشنا بالفعل نوعين أساسين منها هما: بيئة سطر الأوامر (command line) وبيئة التطوير المُتكاملة (IDE) بالقسم ٢.٦. تُوفِّر أي بيئة تطوير مُتكاملة للجافا (Java IDE) أمرًا لإنشاء ملفات الجافا الأرشيفية (jar files). اِتبِع مثلًا الخطوات التالية ببيئة تَطوِير إكلبس (Eclipse): اُنقُر بزر الفأرة الأيمن على المشروع داخل نافذة مُتصفِح الحزم (Package Explorer) ثُمَّ اِختَر "Export" من القائمة. ستَظهَر نافذة، اِختَر منها "JAR file" ثُمَّ اُنقُر على "Next". ستَظهَر نافذة أخرى، أَدْخِل اسمًا للملف الأرشيفي بصندوق الإدخال "JAR file" (يُمكنِك أيضًا النَقْر على زر "Browse" المجاور لذلك الصندوق لاختيار اسم الملف من خلال نافذة). لابُدّ أن يَنتهِي اسم الملف بالامتداد ‎.jar. إذا كنت تُنشِئ ملفًا أرشيفيًا (jar file) عاديًا لا ملفًا تنفيذيًا، تستطيع أن تَنقُر على "Finish" لإنشاء الملف. أما إذا كنت تريد أن تُنشِئ ملفًا أرشيفيًا تنفيذيًا (executable)، اُنقُر على زر "Next" مرتين إلى أن تَصِل إلى شاشة "Jar Manifest Specification". ستَجِد صندوق الإدخال "Main class" بنهاية الشاشة. ينبغي أن تُدْخِل به اسم الصَنْف المُتْضمِّن للبرنامج main()‎ المُراد تَشْغِيله عند تّنْفِيذ الملف الأرشيفي. إذا ضَغطَت على زر "Browse" المجاور للصندوق "Main class"، ستَظهَر قائمة بالأصناف المُحتويّة على برنامج main()‎ والتي يُمكِنك الاختيار من بينها. بَعدّ اختيار الصَنْف، اُنقُر على زر "Finish" لإنشاء الملف التّنْفِيذي. بالنسبة لبيئة سطر الأوامر، تَتَضمَّن عُدّة تطوير جافا (Java Development Kit - JDK) برنامج سطر الأوامر jar المُستخدَم لإنشاء ملفات جافا أرشيفية (jar files). إذا كانت جميع الأصناف (classes) واقعة ضِمْن الحزمة الافتراضية (default package) كغالبية الأمثلة التي تَعرَّضنا لها، يَكُون اِستخدَام الأمر jar سهلًا نوعًا ما. فمثلًا، لكي تُنشِئ ملفًا أرشيفيًا غَيْر تّنفِيذي بسطر الأوامر، اِنتقِل إلى المجلد الذي يَحتوِي على ملفات الأصناف المطلوب إضافتها إلى الملف الأرشيفي، ثُمَّ اِستخدِم الأمر التالي: jar cf JarFileName.jar *.class حيث JarFileName هو اسم ملف الجافا الأرشيفي الذي تَرغَب بإنشائه. لاحِظ أن المحرف * بـ ‎*.class هو عبارة عن محرف بدل (wildcard) يَجعَل ‎*.class تُطابِق جميع أسماء ملفات الأصناف (class files) الموجودة بالمجلد الحالي أي سيَحتوِي الملف الأرشيفي (jar file) على كل ملفات الأصناف ضِمْن المجلد. إذا كان البرنامج يَستخدِم ملفات موارد (resource files) مثل الصور، فلابُد من أن تُضيفها إلى الأمر jar أيضًا. أما إذا كنت تريد أن تُضيِف ملفات أصناف مُحدَّدة فقط، فينبغي أن تَذكُر أسمائها صراحةً بحيث تَفصِل مسافة فارغة بين كل اسم والذي يليه. إذا كانت ملفات الأصناف والموارد مُعرَّفة بحزم غَيْر الحزمة الافتراضية (default package)، تُصبِح الأمور أكثر تعقيدًا، ففي تلك الحالة، لابُدّ أن تَقَع ملفات الأصناف بمجلدات فرعية داخل المجلد الذي تُنفِّذ به الأمر jar. اُنظُر القسم الفرعي ٢.٦.٧. في المقابل، إذا كنت تريد أن تُنشِئ ملفًا أرشيفيًا تنفيذيًا من خلال سطر الأوامر، فالأمر ربما أعقد قليلًا. فلابُدّ من وجود طريقة لتَخْصِيص الصَنْف المُتضمِّن للبرنامج main()‎ مثلًا بإنشاء ملف نصي يحتوي على سطر وحيد كالتالي: Main-Class: ClassName يُشير ClassName -بالأعلى- إلى اسم الملف المُحتوِي على البرنامج main()‎. على سبيل المثال، إذا كان البرنامج main()‎ مُعرَّف ضِمْن الصنف MosaicDraw، فينبغي أن يَحتوِي الملف على العبارة "Main-Class: MosaicDraw". يُمكِنك أن تُسمِي الملف بأي اسم تريده، ولكن لابُدّ من أن تَضعُه داخل نفس المجلد الذي تُنفِّذ به الأمر jar كما ينبغي أن تَكتُب الأمر jar بالصياغة التالية: jar cmf ManifestFileName JarFileName.jar *.class يستطيع الأمر jar تّنْفِيذ الكثير من العمليات المختلفة. يخصص مُعامله (parameter) الأول cf أو cmf العملية المطلوب تّنْفِيذها. بالمناسبة، إذا استطعت إنشاء ملف أرشيفي تّنْفِيذي (executable jar file)، يُمكِنك اِستخدَام الأمر java -jar لتَشْغِيله عبر سطر الأوامر كالتالي: java -jar JarFileName.jar ترجمة -بتصرّف- للقسم Section 6: Complete Programs من فصل Chapter 6: Introduction to GUI Programming من كتاب Introduction to Programming Using Java.
    1 نقطة
  7. الإصدار 1.0.0

    14773 تنزيل

    أصبحت الترجمة عملًا رائجًا جدًا في هذه الأيام، فأي شاب أو فتاة في مرحلة الدراسة ربَّما يبحث أو تبحث عن وظائف حُرّة في الترجمة ظنًا منهم بأنها وظيفة سائغة لكلّ من أتقنَ لغة أجنبية (أو أكثر)، وربّما تكون الترجمة سهلة إذا كان كلّ ما يلزمها هو إتقان لغة أجنبية، لكن هذا الإتقان لا قيمة له إن لم يجتمع مع إتقان اللغة الأم (وهي العربيَّة هنا) ومع اطلاع متعمِّق على نظرية الترجمة وما اتصل بها من علوم اللغة والأدب، وبدونهما تأتي الترجمة ضعيفةً مثل بناءٍ متهالكٍ بناهُ مهندسٌ مبتدئٌ أو لوحة رسمتها يد تنقصها الخبرة. يُقدِّم هذا الكتاب دليلًا للمترجمين الجدد وأصحاب الخبرة المتوسطة ويساعدهم في استيعاب واكتساب بعض من مهارات الترجمة التي ترتقي بعملهم من "ترجمة المعنى" إلى "تعريبه" وتكييفه مع سياقه الثقافي واللغوي الصحيح، وهو موضوعٌ لم تُكرَّس له كتبٌ كثيرة في المكتبة العربية، فالكتاب مُوجَّه إلى المترجمين المبتدئين الراغبين بتعلّم الترجمة الكتابية من اللغة الإنكليزية إلى العربية، وهو لا يُعنى بالترجمة الشفهية ولا يتطرَّقُ إلى لغاتٍ غيرِ هاتين إلا في أمثلة عارضة، ولا يتطرق إلى الترجمة بالاتجاه العكسي (من العربية إلى الإنكليزية) لأن لها شروطًا وضرورات أخرى تحتاج شرحًا منفصلًا. يساعدُ الكتاب غير المختصّين على خوض غمار الترجمة نظريًا وأكاديميًا والتعرف إلى أهم المدارس الفكرية فيها، وإلى خصوصيات تطبيق هذه المناهج في سياق الترجمة من اللغة الإنكليزية إلى العربية (وهي خصوصيات تختلفُ بين كُلِّ لغتين من اللغات). وتبدأُ هذه الرحلة من سؤالٍ بسيطٍ وشيّق: فهل نقل أو "ترجمة" المعنى بين لغات الإنسان ممكنٌ أصلًا؟ وبذلك يبدأُ الكتاب بمدخلٍ إلى ظاهرة الترجمة وأساسياتها ثُمَّ يتناول مدارسها ومناهجها الكثيرة، وينتقلُ بعد ذلك إلى طُرُق التعريب وتعقيداته الشائكة في ترجمة المفردات والتراكيب والجملة وفي النهج والأسلوب، فيتطرَّقُ لأساسيات الكتابة العربية السليمة وضرورتها بالنسبة للمترجم. يُنصح بهذا الكتاب للمترجمين أصحاب الخبرة القليلة الراغبين بتطوير مهاراتهم وبناء خلفية منهجيَّة وأكاديميَّة في مجال الترجمة، على أنه لا يخلو من صعوبة للمستجدِّين في الترجمة بسببِ طرحهِ للكثير من المفاهيم والمشكلات اللغوية التي تحتاجُ إلى خبرة وتجربة تطبيقية لاستيعابها على أنه يمكن للمترجم المبتدئ قراءة الكتاب ثم الرجوع إليه كلما ازدادت خبرته إذ يمكن أن تعد هذا الكتاب مرجعًا تضعه بين متناول يديك، الهدف من الكتاب هو تأهيل المترجم الشغوف ليصبحَ خبيرًا مقتدرًا على أداء عمله وعلى خدمة لغته وثقافته، فالترجمة ليست منجم ذهبٍ وليست دائمًا مهنة مال، على أنها قوة جوهرية في التغيير والتقدُّم والنهضة، لذا أتمنَّى أن يضع بين يدَيْ المترجم العربي وسيلةً يهتدي بها إلى أصول الترجمة السليمة، وأن يساهم -ولو قليلًا- في نهوض الترجمة العربية لغةً ونوعًا لا كمًا وعددًا. وما يجعل هذا الكتاب جديدًا في المكتبة العربية أمران: الأول أنه كتاب أكاديميّ يُقدّم للقارئ منهجًا وأصولًا علميَّة متّبعة عالميًا، والثاني أنه يلاقحُ ما بينَ هذا المنهج العالمي وبينَ خصوصيَّات وسياق الترجمة العربية في زمننا وما يواجه المترجم فيها من عقبات وتحديات حاضرة. هذا الكتاب مرخص بموجب رخصة المشاع الإبداعي Creative Commons «نسب المُصنَّف - غير تجاري - الترخيص بالمثل 4.0». يمكنك قراءة فصول الكتاب على شكل مقالات مجموعة ضمن وسم «الترجم والتعريب» وتجد روابطها تاليًا: الفصل الأول: اللغة ومفهوم الترجمة بين اللغات النسبية اللغوية والترجمة: هل تؤثر اللغة على أفكارنا؟ فن الترجمة وأنواعها وأساليب الترجمة الحديثة كتب تزن ذهبا: تراث العرب والترجمة بين الحرفية والتصرف: تاريخ موجز لنظرية الترجمة آفات الترجمة على اللغة من لسان العرب إلى أكسفورد: المعاجم والقواميس وأهميتها في الترجمة تعريب المفردة والمصطلح تعريب تراكيب اللغة تعريب الجملة وأقلمتها تعريب الثقافة: مسائل لغوية في الترجمة العمل في الترجمة
    1 نقطة
  8. فيه بوتات في الفيس والتويتر تنشر في صور مثلا لكل فريم من أنمي أو فلم وما إلى ذلك، هل فيه حد يعرف مرجع نقدر نتعلم منه كيفية القيام بذلك أمثلة لتلك الصفحات https://www.facebook.com/EveryPMMadokaMagicaFrames/ https://www.facebook.com/HibikeEuphoniumFrames/
    1 نقطة
  9. لقد قمت بتثبيت ويب باك و خادم ويب باك لأتمكن من تحديث الصفحة بشكل تلقائي عند العمل على تطوير الموقع ولكن المشكلة أنه يعمل تحزيم وكله تمام ولكن لا يحدث الصفحة بشكل تلقائي هذا الكود في ملف webpack.config.js devServer: { static: './dist', }, وفي ملف package.json : "scripts": { "watch": "webpack --watch", "start": "webpack serve --open", "build": "webpack" }, أعطيه الأمر npm start فايبدأ بالتحزيم ويفتح لي صفحة على بورت معين ولكن عند العمل على المشروع لا يحدث الصفحة بشكل تلقائي ، ماهي المشكلة ؟
    1 نقطة
  10. كيف استطيع تثبيت تبويب التصميم والتبويبات الاخرى يختفي التبويب
    1 نقطة
  11. ماهي ال elbow method ؟ وماهي wcss ؟ وماهي محاسن ال elbow method؟ وما هي مساوئها؟
    1 نقطة
  12. غالباً ما تكون البيانات التي ستعمل عليها أبعاد متعددة مما يجعل من الصعب عرضها بيانياً أو تخيلها. وبالتالي لايكون العدد الأمثل للمجموعات واضحاً. لكن لحسن الحظ هناك طريقة لتحديد ذلك رياضياً وهي elbow method: elbow method هي طريقة لإيجاد عدد العناقيد (ال ـClusters) في خوارزمية K-Means، أو بمعنى آخر هي طريقة لإيجاد قيمة K في خوارزمية K-Means. نقوم برسم بياني يعبر عن العلاقة بين WCSS (Within Cluster Sum of Squares) و عدد العناقيد (ـClusters)، ثم نختار عدد العناقيد number of clusters عندما يبدأ التغيير في WCSS في الاستقرار. بالنسبة ل WCSS فهي مجموع مربعات الفرق بين كل عضو في ال Cluster ومركزها. الرسم البياني يعبر عن هذا الكلام حيث أن قيم K من 1 إلى 10 ويتم حساب قيمة WCSS المقابل لكل منها، كلما زاد عدد المجموعات ، ستبدأ قيمة WCSS في الانخفاض. تكون قيمة WCSS أكبر عندما تكون K = 1. عندما نحلل الرسم البياني ، يمكننا أن نرى أن الرسم البياني سيتغير بسرعة عند نقطة ما ، وبالتالي إنشاء شكل مرفق أو كوع (Elbow). من هذه النقطة ، يبدأ الرسم البياني في التحرك بشكل موازٍ للمحور السيني تقريباً (استقرار). قيمة K المقابلة لهذه النقطة هي قيمة K المثلى أو العدد الأمثل من المجموعات: elbow method هي أفضل طريقة موجودة لإيجاد قيمة K. لاتوجد مساوئ تذكر..
    1 نقطة
  13. الغرض من التغليف: يحمي الكود من الآخرين. قابلية صيانة الكود. مثال: public class Addition(){ int age=5; // هذا المتغير لا يمكن ان يكون سالب، لانه لا يوجد عمر بالسالب } إذا قام شخص ما بتغيير المتغير إلى 5- فهو غير واقعي. للتغلب على المشكلة نحتاج إلى اتباع الخطوات التالية: يمكننا جعل المتغير خاصًا أو محميًا. بحيث يمكن تعديل الكود أعلاه على النحو التالي: public class Addition(){ private int a = 5; // هنا يتم تمييز المتغير على أنه خاص // Setter public integer setAge(int a){ if(a > 0){// هنا يتم تطبيق الشرط ونضمن ان لا تكون قيمة المتغير سالبة $this.a = a; } } // Getter public integer getAge() { // هذه الدالة للوصول الى المتغير لانك لا تستطيع الوصول له مباشرة لانه محمي return a; } } للتغليف ، نحتاج إلى جعل جميع متغيرات الصنف خاصة وإنشاء دالة ادخال القيمة للمتغير ودالة للوصول الى المتغير. وهذا بدوره سيجبر الآخرين على الاتصال بالدوال بدلاً من الوصول إلى البيانات مباشرة.
    1 نقطة
  14. في React يمكنك عمل مكونات Components وهي عبارة عن دوال أو أصناف جافاسكربت و يمكن أن تحوي عناصر HTML (يصبح مكون React عنصر HTML جديد يمكنك استخدامه)، إن شيفرات JS يتم إرسالها للمتصفح ليعمل لها render ويتم منها تشكيل مكونات HTML الموافقة لها، أي خليط من جافاسكربت و HTML. CSS ضرورية في كل الحالات.
    1 نقطة
  15. فهذه اللغة تستطيع العمل في كل من Front End أي واجهات المستخدم، مثلا باستخدام مكتبة React js هل تقصد بهذه العبارة أنني يمكنني إنشاء واجهات المستخدم فقط باستخدام React js من غير الحاجة إلى استخدام html و css
    1 نقطة
  16. السلام عليكم ورحمة الله وبركاته أي الدورتين تناسبني أكثر لأتعلم كيفية إنشاء تطبيقات الجوال تطوير التطبيقات باستخدام javascript أم تطوير تطبيقات الجوال باستخدام تقنيات الويب مع العلم أني ملم بأسيات HTML و CSS
    1 نقطة
  17. لا علاقة للغة المستخدمة لتطوير تطبيقات الجوال بالاتصال بالانترنت ، الأمر يعتمد على ما تبنيه ضمن التطبيق، فمثلا عند بنائك لتطبيق بسيط لا يحتاج لجلب أي بيانات عبر الانترنت سيعمل هذا التطبيق دون الحاجة للاتصال بالانترنت، مثال على ذلك تطبيق آلة حاسبة. أما عند بنائك تطبيق من ضرورة عمله أن يجلب معلومات عبر الانترنت فهذا يضطر مستخدم التطبيق لفتح الاتصال بالانترنت للسماح لالتطبيق بأداء عمله، مثال على ذلك تطبيق مربوط مع متجر الكتروني.
    1 نقطة
  18. دورة تطوير التطبيقات باستخدام تقنيات الويب تركز على تطوير تطبيقات الجوال فقط باستخدام Cordova، تعلمك أولًا أساسيات تلك المنصة ثم تتدرب على بناء خمس تطبيقات عملية بواسطتها تطوير التطبيقات باستخدام لغة JavaScript تدربك على بناء التطبيقات بشكل عام، كلمة تطبيق هنا تعني تطبيقات الويب وتطبيقات الجوال معًا، ستبدأ الدورة معك من الصفر في تعلم أساسيات جافاسكريبت ثم منصات مثل NodeJS ومكتبات مثل React لتطوير واجهات تطبيقات الويب، و React Native لتطوير تطبيقات الجوال في حال كنت مهتمًا بتطوير تطبيقات الجوال فقط الدورة الأولى تركز على ذلك فقط دون الدخول في مجالات أخرى، أما الدورة الثانية ستدربك على بناء تطبيقات الجوال والويب معًا
    1 نقطة
  19. يمكنك أن تستخدم التابع filter والذي يعيد مصفوفة جديدة تحتوي على بعض العناصر المطابقة للشروط، وهنا مثال بسيط: let books = [ [1,"Start with why","Simon Sinek",80.0,13], [2,"But how do it know","J. Clark Scott",59.9,22], [3,"Clean Code","Robert Cecil Martin",50.0,5], [4,"Zero to One","Peter Thiel",47,12], [5,"You don't know JS","Kyle Simpson",39.9,9] ]; // البحث عن الكتاب الذي يحمل الاسم Clean Code books.filter(book => book[1] == "Clean Code"); // [[3, 'Clean Code', 'Robert Cecil Martin', 50, 5]] // البحث عن الكتب التي لديها سعر أقل من 50 books.filter(book => book[3] < 50); // [[4, 'Zero to One', 'Peter Thiel', 47, 12], [5, "You don't know JS", 'Kyle Simpson', 39.9, 9]] // البحث عن كتب المؤلف Kyle Simpson books.filter(book => book[2] == "Kyle Simpson"); // [[5, "You don't know JS", 'Kyle Simpson', 39.9, 9]] يمكنك القراءة أكثر حول التابع filter وكيفية إستخدامه من خلال موسوعة حسوب من هنا.
    1 نقطة
  20. ما هي الأدوات التي احتاج أتعلمها لكي أعمل boot ؟ مثلا اعمل رد الي على إنستقرام او بوت يعمل لي بحث إلي عن الوظائف الشاغرة المناسبة لي على لينكد إن وحفظها في قائمة وهل هناك دورة في اكاديمية حاسوب ممكن تساعد في هذا؟ ملاحظة: حاليا انا ادرس دورة علوم الحاسب وشارفت على إنهائها
    1 نقطة
  21. تحتاج إلى تعلم بعض الأدوات مثل لغة برمجة تستمح لك بالقيام بهذه الأمور، وفي الغالب يتم إستخدام لغة بايثون Python نظرًا لسهولتها وسرعة تعلمها، أيضًا تحتاج أن تتعلم بعض الأساسيات في التعامل مع الشبكة وكيفية إرسال طلبات requests من خلال لغة البرمجة، كيفية تحميل المواقع بإستخدام بايثون أو كيفية تحميل صورة معينة إلى الجهاز على سبيل المثال، وفي الغالب يتم إستعمال مكتبة requests للقيام بهذا الأمر حيث توفر سهولة كبيرة في التعامل مع هذه الطلبات. أيضًا يمكنك تعلم كيفية التحكم بالمتصفح من خلال لغة البرمجة، على سبيل المثال تجعل المتصفح يذهي إلى موقع جوجل ويكتب جملة معينة في مربع البحث ثم يضغظ على زر البحث ويطبع في سطر الأوامر النتائج التي حصل عليها من جوجل، ويمكن القيام بهذا الأمر بإستخدام مكتبة selenium وهي مشهورة للغاية في هذا المجال. ملاحظة: يسمى هذا المجال بمجال سحب البيانات من الإنترنت Web Scraping أيضًا في كثير من الأحيان ستضطر إلى البحث عن واجهة برمجية API خاصة بالتطبيق أو الموقع الذي تريد القيام فيه ببعض الأمور، فعلى سبيل المثال إن أردت أن تقوم ببرمجة بووت للنشر على أحد المجموعات في تليجرام Telegram فعليك أن تقرأ توثيق Telegram APIs الذي يشرح لك كيفية عمل برامج تستخدم التطبيق وتقوم بالنشر فيه أو القيام بأمور أخرى مختلفة. في دورة علوم الحاسوب يتم شرح كل الأساسيات التي تحتاجها للبدء في تعلم هذه الأمور، حيث يتم شرح لغة بايثون من الصفر وكذلك البرمجة الكائنية والخوارزميات وهياكل البيانات وغيرها من الأمور وكذلك يتم شرح ما هي الطلبات requests وكيف يقوم المتصفح بإرسال الطلبات إلى الخوادم servers ليحصل على رد response (في الغالب يكون كود HTML)، لذلك ستكون هذه الدورة مفيدة لك للغاية قبل تعلم الأمور المذكورة أعلاة.
    1 نقطة
  22. يجب عليك حذف منصات SDK المثبتة مسبقاً والتي يمكنك إيجادها ضمن المسار التالي: ~\Android\Sdk\platforms وإعادة تحميلها من جديد من خلال SDK Manager. في حال لم تتم حل المشكلة، يمكنك تحديث Gradle ضمن مشروعك: في الملف android/build.gradle: buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.2' // قم بتحديث النسخة لأحدث نسخة موجودة ... } } ضمن الملف android/gradle/wrapper/gradle-wrapper.properties: distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip # تحديث رقم النسخة هنا أيضاً
    1 نقطة
  23. في الغالب، تكون هاته المشكلة بسبب اتجاه النصوص والمحاذاة داخل عناصر span التي تكون مضبوطة تلقائيا، يمكنك ضبط تموضع النص بداخلها عن طريق وضعها داخل عنصر div أولا ثم إعطاء هذا العنصر محاذاة واتجاه من اليمين لليسار ليوافق الترميز العربي. يكون ذلك كالتالي: <div> <span> 10 جانفي 2022 </span> </div> الافتراضي: بعد تطبيق تنسيقات المحاذاة والاتجاه عليه: div { text-align:right; direction:rtl; }
    1 نقطة
  24. خلال دراستي للواجهات تداخلت علي الامور فما الفرق بينها وبين الكلاس المجرد.
    1 نقطة
  25. - وحدات التخزين الضوئية – ذواكر – CD: بالعودة تاريخياً فقد طورت هذه التقنية شركة فيليبس وسوني عام 1981 خلال سعيهما لإيجاد وسيلة تخزين موسيقية ذات سعة كبيرة ولها عدة أنواع : 1. CD - ROM: وهي أقراص تتألف من ثلاث طبقات : • الطبقة العليا للحماية. • طبقة وسطى تتألف من طبقة مذهبة عاكسة وأحياناً طبقة من الفضة ولكن بكفاءة أقل. • الطبقة الأساسية وتكون بقطر 120 mm وسعة تبلغ 680 MB . • وتستخدم مساراً حلزونياً واحداً ينطلق من المركز إلى المحيط و يقسم إلى قطاعات. وتتم القراءة من الداخل إلى الخارج، أي من مركز القرص إلى خارج القرص و يتم التخزين من خلال تسليط شعاع ليزري بطول موجة nm 780 ذو استطاعة عالية على طبقة من البلاستيك متعددة الكربونات فيترك أثراً عبارة عن تجويف بعرض 0.6μm . • أما القراءة فتتم من خلال تسليط شعاع ليزري ذو استطاعة منخفضة بحدود 0.5mv وعندما يصطدم بالتجويف يتبعثر الشعاع ولا ينعكس أما عند اصطدامه بالطبقة المسطحة فينعكس الشعاع من الطبقة المطلية بالذهب أو الفضة، و يتم تحسس الشعاع المنعكس بواسطة ثنائي ضوئي و يحوّل هذا الضوء إلى نبضات كهربائية تحول إلى الحاسب لمعالجته. 2. أقراص إعادة الكتابة CD – RW - ROM: تتألف من نفس الأقراص السابقة ولكنها متعددة مرات الكتابة عليها وتتم الكتابة عليها كما يلي : • تستخدم طبقة حرارية تحت الطبقة البلاستيكية تتغير طبيعتها وتترك أثراً عند تسليط ليزر بدرجة حرارة من 900 – 1300 F وعند مسح الكتابة تتم عملية الالتحام للأثر الليزري بتسليط شعاع ليزري جديد بدرجة حرارة تبلغ F 400 وهكذا ... - DVD أو DVD-ROM: هو اختصار للقرص الرقمي متعدد الاستخدامات "digital versatile disc" أو قرص الفيديو الرقمي "digital video disc"، قرصًا قادرًا على تخزين كمية كبيرة من البيانات أكثر من CDROMs. تستخدم أقراص DVD على نطاق واسع لتخزين وعرض الأفلام والبيانات الأخرى. * أحد أكثر أقراص DVD شيوعًا هو قرص أحادي الجانب أحادي الطبقة "single-sided, single-layer"، قادر على حمل 4.7 جيجا بايت. * القرص أحادي الجانب ذو الطبقة المزدوجة "single-sided, double-layer" قادر على حمل ما بين 8.5-8.7 جيجا بايت. * القرص مزدوج الوجه أحادي الطبقة "double-sided, single-layer" قادر على استيعاب 9.4 جيجا بايت. * على الرغم من ندرته ، إلا أن القرص مزدوج الطبقة ذو الوجهين "double-sided, double-layer" قادر على استيعاب ما يصل إلى 17.08 جيجابايت. بالنسبة للفرق بينهما: كلاهما أجهزة تخزين ضوئية ومن الناحية الفيزيائية متشابهين فكلاهما لهما وجه مُسمى وأخر غير مُسمى ويستخدمان الليزر، لكن أكبر فرق بين CDROMs وأقراص DVD هو سعتها. يحتوي قرص CDROM عادةً على 700 ميجابايت من البيانات لكل قرص بينما يمكن أن يحتوي قرص DVD على 4.7 جيجابايت على طبقة واحدة. إذاً يمكن أن تستوعب أقراص DVDs فيلم كامل في قرص واحد بينما لا تستطيع CDROMs ذلك. هناك ميزة أخرى لأقراص DVD وهي سرعات نقل البيانات (أسرع بكثير من CD)، حيث تُترجم السرعة الأساسية لـ CDROM التي تبلغ 1x إلى معدل نقل بيانات يبلغ 1.23 ميجابت / ثانية مع وصول محركات أقراص CDROM النموذجية إلى سرعات 56x وسرعات نقل تبلغ حوالي 68.8 ميجابت / ثانية. بينما سرعة 1x لقرص DVD لديها معدل نقل أعلى يبلغ 10.80 ميجابت / ثانية. على الرغم من أن الحد الأقصى الحالي لمحركات أقراص DVD المتاحة بشكل شائع لا يزال حوالي 20x ، إلا أنه لا يزال يترجم إلى 216 ميجابت / ثانية.
    1 نقطة
  26. استخدم lambda x: x.first_name ك key داخل دالة sorted , sorted(authors, key=lambda x: x.first_name) ويمكنك عكس الترتيب بإضافة reverse=True وهناك حل اخر ولكنه تعديل بسيط علي الاستعلام الخاص بك هذا authors = Author.objects.order_by('-score')[:30] يمكنك اضافة جملة اخري للاستعلام ولكن انتبه ان اردت استخدام - لانها تعيد الترتيب تنازليا ,فقط اضف التالي للبحث الخاص بك,. ('-score', 'first_name')[:30]
    1 نقطة
  27. أرجو كتابة الاستعلام بالصيغة التي أرفقتها لك في أول تعليق
    1 نقطة
  28. اجمع الحقلين بطريقة عادية sum + sum اي أقصد إشارة جمع بين ناتج تطبيق دالة sum
    1 نقطة
  29. تمام، مفهوم join يعمل في هذع الحالة، يمكنك التمرن عليه من خلال موسوعة حسوب sql join
    1 نقطة
  30. نعم تحديث الملف في السيرفؤ هو الحل و ليس تحديث البرنامج كل يوم. خدمات open VPN متغيرة و ليست ثابتة، فوجود حل على مستوى التكبيق غير موجود على الأغلب. تابع البحث لربما تجد خدمة توفر مصارد online لجلب مبف التهيئة يتم ربطها بحساب شخصي و محدثة من قبل المزود بشكل دائم
    1 نقطة
  31. يبدو أن firebase قامت بتحديث التعامل مع PhoneAuthProvider | verifyPhoneNumber يمكنك زيارة التوثيق الرسمي من هنا ، لذلك حاول تجربة إزالة الكود التالي PhoneAuthProvider.getInstance().verifyPhoneNumber( number, 10, TimeUnit.SECONDS, TaskExecutors.MAIN_THREAD, mCallBack ); إلى PhoneAuthOptions options = PhoneAuthOptions.newBuilder(mAuth) .setPhoneNumber(phoneNumber) // Phone number to verify .setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit .setActivity(this) // Activity (for callback binding) .setCallbacks(mCallBack) // OnVerificationStateChangedCallbacks .build(); PhoneAuthProvider.verifyPhoneNumber(options); ثم في ملف app\gradle يمكنك استدعاء السطر التالي في dependencies implementation 'androidx.browser:browser:1.2.0'
    1 نقطة
  32. أعتقد أن الامر اختلط عليك، Flask ليس خادم ويب ، إنه إطار عمل ويحتاج إلى خادم ويب مثل Gunicorn أو Nginx أو Apache ، لقبول طلبات HTTP التي سيعمل عليها بعد ذلك. السبب الذي يجعلنا نفكر في استخدام Nginx و Gunicorn معًا هو أنه بالإضافة إلى كونه خادم ويب ، يمكن لـ Nginx التعامل مع طلبات Gunicorn بشكل افضل والتي تجعل الاداء افضل بكثير. يقوم Gunicorn بمهمة تشغيل تطبيقك وأحد مزاياه هي جعل مشروعك يعمل علي هيئة خيوط معالجة متوازية مما يسمح بإجراء الكثير من العمليات في نفس اللحظة. يستمع Nginx للطلبات الواردة عبر http او https ويعيد توجيهها إلي Gunicorn والذي يدير Flask, اما عن متي يمكنك استخدام Nginx و Gunicorn هو في بيئة الانتاج ان اردت ان يحصل زوار موقعك علي وصول سريع في نفس الوقت وتقسيم للعمليات بشكل سلس , حينها يمكنك استخدام Nginx و Gunicorn, اما ان كان مشروعك بسيط لا يستقبل طلبات كثيفة أو مستخدمين كثر , فلا بأس استمر مع flask built-in server.
    1 نقطة
  33. إذاً الأفضل هو تحميل الملف إلى التطبيق بشكل ديناميكي، يكون لديك استضافة مرفوع عليها الملف، يقوم التطبيق عند التشغيل بالاتصال و تحميل ملف الإعداد ثم استخدامه لضبط الاتصال. بهذه الحالة لا تحتاج لتحديث التطبيق نفسه، بل عليك تحديث ملف الإعداد من الاستضافة وهذا أمر سهل يمكنك القيام به بشكل يومي لبضع دقائق مثلاً، وبهذه الطريقة تتخلص من تحديد مدة صلاحية الملف نفسه. يمكنك في موضع متقدم أن تقوم بكتابة سكربت يعمل على تحديث الملف في الاستضافة بشكل آلي وهذا يسهل عليك الأمور. بتطبيق الخطوة السابقة ستتمكن من استخدام خدمة vpngate
    1 نقطة
  34. هل يمكنك مشاركة بنية قاعدة البيانات و الجداول لديك؟ أعتقد أنك تستخدم لارافل إليكوينت Laravel Elequent عليك دمج الجدولين في استعلام join لجلب البيانات .. لاحظ المثال التالي: نحدد جدول الاستعلام الرئيسي (مستخدمين) ندمجه من ال contacts أي جهات الاتصال حسب رقم المستخدم ندمج ما سبق مع الطبيات orders حسب رقم المستخدم أيضاً ثم نطبق الاستعلام select // تضمين الصنف use Illuminate\Support\Facades\DB; // مثال من التوثيق $users = DB::table('users') ->join('contacts', 'users.id', '=', 'contacts.user_id') ->join('orders', 'users.id', '=', 'orders.user_id') ->select('users.*', 'contacts.phone', 'orders.price') ->get();
    1 نقطة
  35. إن كان لديك مشكلة مادية في شراء دورتين، يمكنك الاعتماد على دورة جافاسكربت، فهذه اللغة تستطيع العمل في كل من Front End أي واجهات المستخدم، مثلا باستخدام مكتبة React js وأيضا العمل كمخدم للموقع Back End أيضا من خلال بيئة العمل node وأيضا إطار العمل Express وأيضا قواعد البيانات.. وهذا كله يتم تعليمه في دورة واحدة. مع العلم بعد شراء أي دورة يتم فتح المسارات الأساسية من باقي الدورات ومنهم دورة تطوير واجهات المستخدم والتي تحوي في مسارها الأول على أساسيات كل من HTML - CSS - JS والتي تفيدك في دورة جافاسكربت عند تنسيق واجهات الموقع. تعتبرة دورة تطوير الواجهات ممتازة بالنسبة لعمل تفاصيل كثيرة في الموقع وإخراجه بأفضل شكل ممكن ولكنها تنحصر بالواجهات الأمامية، أي بدوت قواعد بيانات ومعالجة وتوثيق المستخدمين وإلا ماهنالك.. وبالاعتماد على دروس الأساسيات منها ودورة جافاسكربت سيكون لك قدرة على برمجة مواقع كاملة Full Stack.
    1 نقطة
  36. تُعدّ المُكوِّنات (components) اللبنة الأساسية بأي واجهة مُستخدِم رُسومية (graphical user interface). لا يَقْتصِر الأمر على إنشاء مُكوِّنات الواجهة بل تحتاج أيضًا إلى تَخْطِيط الكيفية التي ستَتَموْضَع بها تلك المُكوِّنات على الشاشة وكذلك تَخْصِيص أحجامها. في العموم، تُعدّ عملية حِسَاب إحداثيات المَواضِع معقدة نوعًا ما خاصةً إذا لم تَفْترِض ثبات حجم مساحة الرسم. تُوفِّر الجافا لحسن الحظ حلًا مُبسِّطًا لذلك. تُشير المُكوِّنات (components) إلى الأشياء المرئية التي تُشكِّل واجهة المُستخدِم الرُسومية (GUI). بعضًا منها عبارة عن مُكوِّنات حاوية (containers) يُمكِنها أن تَتَضمَّن مُكوِّنات آخرى. بمصطلحات جافا إف إكس (JavaFX)، المُكوِّنات الحاوية (container) هي عُقَد مبيان مشهد (scene graph node) يُمكِنها أن تَتَضمَّن عقد مبيان مشهد آخرى كأبناء (children). لابد من ضَبْط مواضع وأحجام أبناء أي مُكوِّن حاوي فيما يُعرَف باسم "التخطيط (layout)"، وهو ما قد تُبرمجه بنفسك، ولكن تُستخدَم عادةً حلول أتوماتيكية بواسطة المُكوِّن الحاوي (container) نفسه. تُنفِّذ أنواع مختلفة من المُكوِّنات الحاوية سياسات تخطيط (layout policies) مختلفة لتَحْديد مواضع العقد الأبناء (child nodes). سنُغطِي في هذا القسم بعض أنواع المُكوِّنات الحاوية (containers) بمنصة جافا إف إكس (JavaFX) مع توضيح سياسات التخطيط الخاصة بكُلًا منها كما سنَفْحَص أمثلة برمجية متعددة. لأن أي مُكوِّن حاوي (container) هو بالنهاية عُقدَة مبيان مشهد (scene graph node)، فيُمكِنك أن تُضيف مُكوِّن حاوي كابن لمُكوِّن حاوي آخر مما يَسمَح بتداخل مُعقد للمُكوِّنات كما هو مُبيَّن بالصورة التالية: تُوضِح الصورة السابقة مُكوِّن حاوي (container) كبير يَتَكوَّن من مُكوِّنين حاويين أصغر كلًا منهما يَحمِل بدوره ثلاثة مُكوِّنات آخرى. تُخصِّص كل عقدة مبيان مشهد (scene graph node) قيم صغرى وكبرى ومُفضّلة لكُلًا من عرضها وارتفاعها. يَستعِين أي مُكوِّن حاوي بتلك القيم عند تقريره لطريقة وَضْع أبنائه. من الجهة الآخرى، يُعدّ حجم بعض مُكوِّنات العُقَد ثابتًا -مثل الصنفين Canvas و ImageView-، وتَتَساوَى عندها كُلًا من القيم الصغرى والعظمى والمُفضّلة للعرض والارتفاع مع الحجم الفعليّ للمُكوِّن، ولا يُمكِن لأي مُكوِّن حاوي عندها تَغْيير ذلك الحجم أثناء التخطيط (layout). في العموم، سيَحسِب المُكوِّن الحاوي حجمه المُفضّل اعتمادًا على ما يَتَضمَّّنه من مُكوِّنات وبحيث يَسمَح ذلك الحجم بأن يَحصُل كل مُكوِّن داخله على حجمه المُفضّل على الأقل. سيَحسِب المُكوِّن الحاوي القيم الصغرى والعظمى لحجمه بنفس الطريقة أي وفقًا للأحجام الصغرى والعظمى لأبنائه. أثناء التخطيط (layout)، تلتزم أغلب المُكوِّنات الحاوية (container) بضَبْط قيمة عَرْض (width) أي مُكوِّن ابن بحيث تتراوح بين القيمة الصغرى والعظمى لعَرْض المُكوِّن الابن ذاته حتى لو أدى ذلك إلى تَداخُله مع مُكوِّنات آخرى أو إلى تَمدُّده إلى خارج المُكوِّن الحاوي (قد يُصبِح ذلك الجزء المُتمدِّد مرئيًا أو لا بالاعتماد على المُكوِّن الحاوي) أو حتى إلى ترك مساحات فارغة بالمُكوِّن الحاوي. يَنطبِق نفس الأمر على ارتفاع المُكوِّنات الأبناء. تُعرِّف العُقَد مُتغيِّرة الحجم (resizable nodes) -مثل مُكوِّنات التحكُّم (controls) وغالبية المُكوِّنات الحاوية- توابع نسخ (instance methods) لضَبْط قيم العَرْض الصغرى والعظمى والمُفضّلة، هي كالتالي: setMinWidth(w)‎ و setPrefWidth(w)‎ و setMaxWidth(w)‎ حيث المُعامِل w من النوع double بالإضافة إلى توابع نسخ مشابهة لضَبْط قيم الارتفاع. تَتَوفَّر أيضًا توابع مثل setMaxSize(w,h)‎ و setPrefSize(w,h)‎ لضَبْط قيمتي العرض والارتفاع بنفس الوقت. بالنسبة لمُكوِّن حاوي (container)، ستُبطِل القيم المضبوطة باِستخدَام هذه التوابع تلك القيم التي كانت ستُحسَب بالاعتماد على المُكوِّنات الأبناء. بمنصة جافا إف إكس (JavaFX)، تُعرَّف المُكوِّنات الحاوية المسئولة عن التخطيط بواسطة الصنف Pane وأصنافه الفرعية (subclasses) الواقعة بحزمة javafx.scene.layout. سنَفْحَص الآن عددًا قليلًا من أصناف التخطيط (layout classes) بدءًا بالصَنْف Pane. إعداد تخطيط مخصص أحيانًا قد ترغب بتصميم التخطيط (layout) الخاص بك بدلًا من اِستخدَام إحدى الأصناف الفرعية القياسية المُشتقَّة من الصَنْف Pane. سنَستخدِم الصَنْف الأعلى Pane ذاته بالمثال الأول، وهو ما يَعنِي أننا سنتتوَلّي مُهِمّة ضَبْط مواضع العُقد الأبناء (child nodes). بالنسبة لأحجام تلك العُقد، سيتولَّى الصَنْف Pane ضَبْطها إلى أحجامها المُفضّلة افتراضيًا، ومع ذلك يُمكِنك أن تُنجِز تلك المُهِمّة أيضًا إذا أردت. بفَرْض أن node عُقدة مبيان مشهد، تَضَع التَعْليمَة التالية الركن الأيسر العُلوي للعُقدة بإحداثيات النقطة (x,y) وفقًا لنظام إحداثيات المُكوِّن الحاوي (container) لتلك العُقدة: node.relocate( x, y ); كما تَضبُط التَعْليمَة التالية حجم العُقدة بشَّرْط أن تَكُون مُتْغيِّرة الحجم (resizable): node.resize( width, height ) لن يَكُون لتلك التَعْليمَة أي تأثير إذا كانت العُقدة ثابتة الحجم (non-resizable). ومع ذلك تَملُك بعض العُقد ثابتة الحجم -مثل Canvas- توابعًا مثل setWidth(w)‎ و setHeight(h)‎ لضَبْط أحجامها. إذا كانت العُقدة ضِمْن مُكوِّن حاوي (container) مسئول عن التخطيط (layout) كليًا، فلن يَكُون لأي من التابعين relocate()‎ و resize()‎ أي تأثير. أما إذا كانت ضِمْن مُكوِّن حاوي مثل Pane، فسيُؤثِر التابع resize()‎ بالعُقد مُتْغيِّرة الحجم أما التابع relocate()‎ فلن يَكُون له أي تأثير. يُعدّ ما سَبَق صحيحًا فقط إذا كانت العُقدة مُدارة (managed). يُمكِن ضَبْط عُقدة معينة لتُصبِح غَيْر مُدارة (unmanaged) باستدعاء التالي: node.setManaged(false); سنَفْحَص مثالًا يَحتوِي على ٤ مُكوِّنات: زرين (buttons) وعنوان (label) وحاوي (canvas) يَعرِض رقعة شطرنج: عندما يَنقُر المُستخدِم على الأزرار، سيَتبدَّل النص المعروض بمُكوِّن العنوان (label). إلى جانب ذلك، لن يُنفِّذ البرنامج أي شيء فعليّ آخر، فهو فقط مُجرّد مثال بسيط على التخطيط (layout). سنَستخدِم هذا المثال بالقسم ٧.٥ كنقطة انطلاق لبرنامج لعبة داما (checkers). لأن هذا المثال يَستخدِم كائنًا من الصَنْف Pane كعُقدَة جذرية (root node) للمشهد وكمُكوِّن حاوي (container) للمُكوِّنات الأربعة الآخرى، فإن البرنامج سيَكُون مسئولًا عن ضَبْط مَواضِع المُكوِّنات باستدعاء تابعها relocate()‎ وإلا سيَقَع الركن الأيسر العُلوي لجميع المُكوِّنات بالمَوضِع الافتراضي (٠،٠). سنَضبُط أيضًا حجم الزرين (buttons) لكي يُصبِحا بنفس الحجم الذي سيَكُون أكبر قليلًا من حجمهما المُفضّل، ولكن لمّا كان الزران مدارين (managed)، فإن استدعاء تابعهما resize()‎ لن يَكُون له أي تأثير حيث سيُعيد المُكوِّن الحاوي ضَبْطهما لحجمهما المُفضّل، لذا سنحتاج أولًا لجعلهما غَيْر مُدارين (unmanaged). اُنظر الشيفرة التالية من تابع التطبيق start()‎ الذي يُنشِئ المُكوِّنات الأربعة ويَضبُط مَواضِعها وأحجامها: /* أنشئ العقد الأبناء */ board = new Checkerboard(); // صنف فرعي من الصنف‫ Canvas board.draw(); // ارسم محتويات رقعة الشطرنج newGameButton = new Button("New Game"); newGameButton.setOnAction( e -> doNewGame() ); resignButton = new Button("Resign"); resignButton.setOnAction( e -> doResign() ); message = new Label("Click \"New Game\" to begin."); message.setTextFill( Color.rgb(100,255,100) ); // Light green. message.setFont( Font.font(null, FontWeight.BOLD, 18) ); // ‫اضبط موضع كل ابن باستدعاء تابعه relocate() board.relocate(20,20); newGameButton.relocate(370, 120); resignButton.relocate(370, 200); message.relocate(20, 370); /* اضبط حجم الأزرار. لابد أن تجعلها غير مدارة أولًا */ resignButton.setManaged(false); resignButton.resize(100,30); newGameButton.setManaged(false); newGameButton.resize(100,30); يُمثِل المُكوِّن الحاوي من الصَنْف Pane العُقدَة الجذرية (root node) للمشهد، لذا سيُضبَط حجم نافذة البرنامج بما يتناسب مع الحجم المُفضَّل لذلك المُكوِّن. افتراضيًا، يُحسَب الحجم المُفضَّل للمُكوِّن الحاوي بحيث يَكُون كبيرًا كفاية لعَرْض عُقده الأبناء المُدارة، ولأننا ضَبطَنا الأزرار لتُصبِح غَيْر مُدارة (unmanaged)، فإنها لن تُؤثِر على حجمه المُفضَّل. سنَضبُط عَرْض المُكوِّن الحاوي وارتفاعه بالقيم التالية: Pane root = new Pane(); root.setPrefWidth(500); root.setPrefHeight(420); سنضيف الآن كُلًا من الأزرار (buttons) والعنوان (label) والرقعة (board) كأبناء بالمكون الحاوي بخطوة واحدة كالتالي: root.getChildren().addAll(board, newGameButton, resignButton, message); أو على عدة خطوات بحيث يُضَاف كل مُكوِّن على حدى باِستخدَام التَعْليمَة التالية: root.getChildren().add(board); أو يُمكِنك أن تُمرِّر العُقد الأبناء (child nodes) كمُعامِلات (parameters) لبَانِي الكائن (constructor) كالتالي: Pane root = new Pane(board, newGameButton, resignButton, message); لابُدّ من ضَبْط المُكوِّن الحاوي من الصَنْف Pane ليَكُون المُكوِّن الجذري (root) للمشهد (scene). ينبغي أيضًا أن نَضَع ذلك المشهد بالمرحلة (stage) التي لابُدّ من ضَبْطها وعَرْضها على الشاشة. اُنظر كامل الشيفرة المصدرية بالملف OwnLayoutDemo.java. قد يَكُون تَخْصِيص التخطيط (layout) بالمثال السابق أمرًا سهلًا على نحوٍ معقول، ولكنه سيُصبِح أصعب كثيرًا إذا كان حجم النافذة عامًلا مُتْغيِّرًا. في تلك الحالة، يُفضَّل عمومًا الاعتماد على أحد المُكوِّنات الحاوية القياسية لتُنفِّذ التخطيط (layout) بدلًا منك، ولكن إذا أردت تّنْفيذه، فلابُدّ من أن تُعرِّف صنفًا فرعيًا من الصَنْف Pane أو من صَنْفها الأعلى Region ثم ينبغي أن تُعيد تَعرِيف (override) التابع layoutChildren()‎ الذي يَستدعِيه النظام لحَث المُكوِّن الحاوي (container) على تطبيق التخطيط (layout) بما في ذلك تَغْيِير حجم المُكوِّن الحاوي. BorderPane يُعدّ مُكوِّن الحاوية BorderPane صنفًا فرعيًا (subclass) من الصَنْف Pane. قد يَشتمِل ذلك المُكوِّن على ما يَصِل إلى ٥ مُكوِّنات (components) يَقَع أكبرها بالمنتصف بينما يُرتَّب البقية حوله بالأعلى وبالأسفل وعلى اليسار وعلى اليمين كما هو مُبيَّن بالصورة التالية: قد يَشتمِل مُكوِّن الحاوية BorderPane على عدد أقل من خمسة مُكوِّنات لذا أنت لست مُضطّرًا لأن تُخصِّص مُكوِّن بكل مَوْضِع ولكن عادةً ما يُخصَّص مُكوِّن بمنتصف الحاوية. يُوفِّر الصَنْف BorderPane بانيين (constructors): لا يَستقبِل أحدهما أية مُعامِلات (parameters) بينما يَستقبِل الآخر مُعامِلًا واحدًا يُخصِّص مُكوِّن ابن (child) يُفْترَض وَضعُه بمنتصف الحاوية. بالإضافة إلى ذلك، يُمكِنك أن تُخصِّص العُقد الأبناء (child nodes) الآخرى لحاوية pane من الصَنْف BorderPane باِستخدَام التوابع (methods) التالية: pane.setCenter(node); pane.setTop(node); pane.setRight(node); pane.setBottom(node); pane.setLeft(node); ملحوظة: إذا مَرَّرت مُعامِلًا (parameter) قيمته تُساوِي null لأي من تلك التوابع، فسيُحذَف المُكوِّن الموجود بذلك المَوْضِع ضِمْن الحاوية تَضبُط مُكوِّنات الحاوية من الصَنْف BorderPane حجم عُقدها الأبناء (child nodes) على النحو التالي (لابُدّ أن يتراوح كُلًا من عرض أي مُكوّن وارتفاعه بين قيمته العظمى والصغرى المُخصَّصة): تَحتَل المُكوِّنات بالمَوْضِع العلوي والسفلي -إن وجدت- مساحة ارتفاعها يُساوِي القيمة المُفضَّلة لارتفاع المُكوِّن أما عَرْضها فيُساوِي عَرْض الحاوية الكلي. في المقابل، تَحتَل المُكوِّنات على اليمين واليسار مساحة عَرْضها يُساوِي القيمة المُفضَّلة لعَرْض المُكوِّن أما ارتفاعها فيُساوِي قيمة ارتفاع الحاوية مطروحًا منها المسافة التي احتلتها المُكوِّنات بكُلًا من المَوْضِع العلوي والسفلي. أخيرًا، يَمتدّ المُكوِّن بمنتصف الحاوية عَبْر المساحة المُتْبقيّة. تُضبَط القيمة المُفضَّلة لحجم الحاوية على نحو يتناسب مع الحجم المُفضَّل لأبنائها من العُقد المُدارة (managed). تُحسَب القيمة الصغرى بنفس الطريقة أما القيمة العظمى الافتراضية فهي لا نهائية. تُتيِح بعض الأصناف الفرعية (subclasses) المُشْتقَّة من الصَنْف Pane تطبيق ما يُعرَف باسم "قيود التَخْطِيط (layout constraint)" لتَعْدِيل تَخْطِيط (layout) العُقد الأبناء. على سبيل المثال، ماذا سيَحدُث بحاوية من النوع BorderPane إذا لم يَكُن مَسمُوحًا بإعادة ضَبْط حجم مُكوِّن ابن لكي يتناسب تمامًا مع المساحة المُتوفرة؟ في تلك الحالة، يَحتَل ذلك المُكوِّن مَوْضعًا افتراضيًا ضِمْن تلك المساحة. يَقَع مكون بمنتصف مساحة الحاوية (pane) بينما يقع آخر بالركن الأيسر السفلي منها وهكذا. يُمكِنك أن تُعدِّل الطريقة الافتراضية باِستخدَام التابع الساكن (static method) التالي المُعرَّف بالصَنْف BorderPane: BorderPane.setAlignment( child, position ); يُشير child إلى العُقدة التي تَرغَب بتَعْدِيل مَوْضِعها أما position فيَحتوِي على إحدى ثوابت نوع التعداد Pos من حزمة package javafx.geometry مثل Pos.CENTER أو POS.TOP_LEFT أو Pos.BOTTOM_RIGHT أو غيرها. يَسمَح الصَنْف BorderPane بإضافة هامش (margin) لأي مُكوِّن ابن يَقَع ضِمْن الحاوية. الهامش عبارة عن مسافة فارغة تُحيِط بالمُكوِّن الابن وتُلوَّن بنفس لون خلفية الحاوية، ويُسنَد إليها قيمة من النوع Insets المُعرَّف بحزمة package javafx.geometry. يَملُك أي كائن من النوع Insets أربع خاصيات (properties) من النوع double هي top و right و bottom و left والتي يُمكِنك ضَبْطها باِستخدَام بَانِي الكائن (constructor): new Insets(top,right,bottom,left) يَتَوفَّر باني كائن (constructor) آخر يَستقبِل مُعاملًا (parameter) واحدًا يُمثِل قيمة الخاصيات (properties) الأربعة. عند اِستخدَامه لتَخْصِيص هامش (margin) حول مُكوِّن ابن معين، تُحدِّد تلك الخاصيات الأربعة عَرْض الهامش (margin) حول جوانبه الأربعة: أعلاه ويمينه وأسفله ويساره. تستطيع تَخْصِيص الهامش (margin) باِستخدَام التابع الساكن (static method) التالي: BorderPane.setMargin( child, insets ); على سبيل المثال: BorderPane.setMargin( topchild, new Insets(2,5,2,5) ); يُمكِنك أيضًا اِستخدَام لغة أوراق الأنماط المتعاقبة (CSS) لتَعْدِيل مَظهَر حاوية (container)، وهي في الواقع واحدة من أسهل الطرائق لضَبْط خاصيات مثل لون الخلفية. HBox و VBox إذا أردت ترتيب مجموعة من المُكوِّنات ضِمْن صف أفقي أو عمود رأسي، يُمكِنك اِستخدَام الصنفين HBox و VBox. يُرتِّب الصَنْف الفرعي HBox المُشْتق من الصَنْف Pane أبنائه إلى جوار بعضها البعض ضِمْن صف أفقي بينما يُرتِّب الصَنْف الفرعي VBox المُشتق من الصَنْف Pane أبنائه ضِمْن عمود أفقي. يُمكِنك مثلًا أن تَستخدِم حاوية من الصَنْف HBox بالمَوْضِع السفلي لحاوية من الصَنْف BorderPame مما سيَسمَح باصطفاف عدة مُكوِّنات على طول الحافة السفلية للحاوية. بالمثل، قد تَستخدِم حاوية من الصَنْف VBox بالمَوْضِع الأيسر أو الأيمن لحاوية من الصَنْف BorderPane. سنُناقش الصَنْف HBox فقط ولكن تستطيع اِستخدَام الصَنْف VBox بنفس الكيفية. بإِمكَانك ضَبْط الصَنْف HBox لكي يَترُك فراغًا بين كل ابن والابن الذي يليه. بفَرْض وجود حاوية hbox، يُمكِنك تَخْصِيص مقدار الفراغ بتمرير قيمة من النوع double للتابع التالي: hbox.setSpacing( gapAmount ); القيمة الافتراضية تُساوِي صفر. تُضَاف الأبناء إلى حاوية hbox من الصَنْف HBox بنفس الكيفية التي تُضَاف بها إلى حاوية من الصَنْف Pane أي باستدعاء hbox.getChildren().add(child)‎ أو hbox.getChildren().addAll(child1,child2,...)‎. يُوفِّر الصَنْف HBox بانيين (constructor). لا يَستقبِل الأول أية مُعاملات (parameters) بينما يَستقبِل الثاني مُعاملين هما حجم الفراغ المتروك وأي عُقد أبناء (child nodes) قد ترغب بإضافتها إلى تلك الحاوية. تَضْبُط أي حاوية من النوع HBox حجم كل ابن وفقًا لقيمة العَرْض المُفضَّلة لذلك الابن مع إِمكانية تَرك مسافة فارغة إضافية إلى اليمين. إِذا كان مجموع قيم العَرْض المُفضَّلة للأبناء ضِمْن حاوية معينة أكبر من القيمة الفعلية لعَرْض تلك الحاوية، يُقلَّص عَرْض كل ابن منها في حدود قيمة العَرْض الصغرى الخاصة به. في المقابل، يُضبَط ارتفاع الأبناء إلى الارتفاع المتاح بالحاوية بما يَتَوافَق مع كُلًا من القيم الصغرى والعظمى المُخصَّصة لارتفاع تلك الأبناء. إذا أردت تَوْسِيع عَرْض عدة أبناء ضِمْن حاوية من النوع HBox إلى ما هو أبعد من قيمها المُفضَّلة بغَرْض مَلْئ مساحة مُتاحَة ضِمْن تلك الحاوية، فينبغي أن تُطبِق "قيد تخطيطي (layout constraint)" على كل ابن تَرغَب بتوسيعه باستدعاء التابع الساكن (static method) التالي: HBox.setHgrow( child, priority ); يَستقبِل التابع بالأعلى مُعاملًا ثانيًا عبارة عن ثابت (constant) من نوع التعداد Priority المُعرَّف بحزمة package javafx.scene.layout. إذا اِستخدَمت القيمة Priority.ALWAYS، فدائمًا ما سيَتَقاسَم ذلك الابن أي مساحة إضافية مُتْبقيّة، ولكنه مع ذلك سيَظَلّ مقيدًا بقيمة العَرْض العظمى الخاصة به لذا قد تَضطّر إلى زيادتها لتَسمَح له بالتَوسُع كما ينبغي. لنَفْترِض مثلًا وجود حاوية من الصَنْف HBox تَحتوِي على ثلاثة أزرار هي but1 و but2 و but3 والتي تُريِدها أن تَتَوسَّع بما يَكفِي لمَلْئ الحاوية بالكامل. ستحتاج إذًا إلى ضَبْط القيد التخطيطي HGrow لكل زر منها. بالإضافة إلى ذلك، لمّا كانت كُلًا من القيمة العظمى والمُفضَّلة لعَرْض زر مُتساوية، كان من الضروري زيادة القيمة القصوى. بالمثال التالي، ضُبِطَت قيمة العَرْض العُظمى لكل زر إلى قيمة الثابت Double.POSITIVE_INFINITY مما سيُمكِّنها من التَوسُّع بدون أي قيود نهائيًا: HBox.setHgrow(but1, Priority.ALWAYS); HBox.setHgrow(but2, Priority.ALWAYS); HBox.setHgrow(but3, Priority.ALWAYS); but1.setMaxWidth(Double.POSITIVE_INFINITY); but2.setMaxWidth(Double.POSITIVE_INFINITY); but3.setMaxWidth(Double.POSITIVE_INFINITY); ستُوزَّع الآن أي مساحة إضافية بالتَساوِي على الأزرار الثلاثة وتُضاف إلى قيمة عَرْضها المُفضَّلة. لا يَعنِي ذلك أن عَرْضها سيُصبِح متساويًا بالضرورة؛ فقد لا تَكُون قيمها المُفضَّلة الأصلية مُتساوية أساسًا. إذا أردت ضَبْط الأزرار الثلاثة لتُصبِح بنفس الحجم، فلابُدّ من ضَبْط قيمة العَرْض المُفضَّلة لكُلًا منها لتَحمِل نفس القيمة كما يَلي: but1.setPrefWidth(1000); but2.setPrefWidth(1000); but3.setPrefWidth(1000); سيَحتَلّ الآن كل زر ضِمْن تلك الحاوية مساحة عَرْضها مُتساوي. تَتَوفَّر طرائق آخرى لتَعْدِيل التخطيط (layout) ضِمْن حاوية من النوع HBox. يُمكِنك مثلًا إضافة هامش (margin) حول أي مُكوِّن ابن باِستخدَام تابع ساكن (static method) مُشابه لذلك المُستخدَم لنفس الغرض بالحاويات من النوع BorderPane. تستطيع أيضًا استدعاء hbox.setFillHeight(false)‎ لضَبْط ارتفاع حاوية معينة إلى القيمة المُفضَّلة لارتفاع أبنائها بدلًا من توسيعهم ليتلائموا مع ارتفاع تلك الحاوية. قد تَستدعِي hbox.setAlignment(position);‎ لضَبْط مَوْضِع ابن ضِمْن حاوية عندما لا يَملَؤها بالكامل. لاحِظ أن مُعاملها (parameter) من النوع Pos بقيمة افتراضية تساوي Pos.TOP_LEFT. يُمكِنك أيضًا أن تُطبِّق عليها خاصيات لغة أوراق الأنماط المتعاقبة (CSS). سنَفْحَص الآن مثالًا لواجهة مُستخدِم رُسومية (GUI) مبنية بالكامل على الصَنْفين HBox و VBox. يُمكِنك الإطلاع على شيفرة البرنامج بالملف SimpleCalc.java. تَتَضمَّن نافذة البرنامج ما يلي: حَقْلين نصيين من الصَنْف TextField تَسمَح للمُستخدِم بكتابة عدد، بالإضافة إلى أربعة أزرار يُمكِن للمُستخدِم النَقْر عليها لإجراء عملية جمع أو طرح أو ضرب أو قسمة على العددين المُدْخَلين، وأخيرًا عنوان نصي من الصَنْف Label يَعرِض نتيجة العملية المُجراة. تُبيِّن الصورة التالية ما تبدو عليه نافذة البرنامج: تُستخدَم حاوية من الصَنْف VBox كعُقدة جذرية لنافذة البرنامج، وتَحتوِي على أربعة عناصر: الثلاثة الأولى منها عبارة عن حاويات من الصَنْف HBox. يَحتوِي أول حاوي منها على عنوان من النوع Label يَعرِض النص "‎x =‎" بالإضافة إلى حقل نصي من الصَنْف TextField. يُنشَئ ذلك باستخدام الشيفرة التالية: xInput = new TextField("0"); // Text input box initially containing "0" HBox xPane = new HBox( new Label(" x = "), xInput ); يُنشَئ العنوان (label) باستخدام باني (constructor) ثم يُضاف مباشرةً إلى الحاوية لعدم حاجتنا إلى مَرجِع (reference) يُشير إليه فيما بَعْد. ستُضاف هذه الحاوية لاحقًا كابن للحاوية الخارجية من الصنف VBox. سيَعرِض ثالث حاوي الأزرار الأربعة. ولأن القيمة الافتراضية لعَرْض الزر صغيرة نسبيًا، فسنُطبِّق القيد hgrow عليها كما سنُزيد قيمة العرض العظمى لكُلًا منها لكي نَتَأكَّد من كَوْنها تملئ عَرْض الحاوية بالكامل. يُمكِنك القيام بما سبق باِستخدَام الشيفرة التالية: // ‫انشئ الأزرار الأربعة وحاوية HBox لحملها Button plus = new Button("+"); plus.setOnAction( e -> doOperation('+') ); Button minus = new Button("-"); minus.setOnAction( e -> doOperation('-') ); Button times = new Button("*"); times.setOnAction( e -> doOperation('*') ); Button divide = new Button("/"); divide.setOnAction( e -> doOperation('/') ); HBox buttonPane = new HBox( plus, minus, times, divide ); /* ‫ينبغي أن تضبط الأزرار الأربعة قليلًا لكي تملئ الحاوية buttonPane * بإمكانك القيام بذلك بضبط القيمة العظمى لعرض كل زر إلى قيمة أكبر */ HBox.setHgrow(plus, Priority.ALWAYS); plus.setMaxWidth(Double.POSITIVE_INFINITY); HBox.setHgrow(minus, Priority.ALWAYS); minus.setMaxWidth(Double.POSITIVE_INFINITY); HBox.setHgrow(times, Priority.ALWAYS); times.setMaxWidth(Double.POSITIVE_INFINITY); HBox.setHgrow(divide, Priority.ALWAYS); divide.setMaxWidth(Double.POSITIVE_INFINITY); يَعرِض القسم الأخير بالحاوية الخارجية من النوع VBox مُكوِّنًا واحدًا عبارة عن عنوان من النوع Label. نظرًا لكَوْنه مُكوِّنًا وحيدًا، يُمكِننا إضافته إلى الحاوية الخارجية مباشرةً دون الحاجة لوَضْعه أولًا ضِمْن حاوية من النوع HBox. لكي نَضْمَن ظهور النص بمنتصف النافذة بدلًا من جانبها الأيسر، سنحتاج إلى زيادة قيمة العَرْض العظمى لمُكوِّن العنوان (label) وكذلك إلى ضَبْط خاصية محاذاة النص (alignment) بمُكوِّن العنوان (label) إلى المنتصف بدلًا من اليسار. اُنظر الشيفرة التالية: answer.setMaxWidth(Double.POSITIVE_INFINITY); answer.setAlignment(Pos.CENTER); عندما يَنقُر المُستخدِم على أي من الأزرار الأربعة، يُستدعَى التابع doOperation()‎ ليُنفِّذ ما يَلي: يقرأ العَدَدين الذين أَدْخَلهما المُستخدِم بالحقول النصية (text fields) ثم يُجرِي العملية الحسابية المطلوبة ويَعرِض الناتج بنص العنوان (label). نظرًا لأن محتويات الحقول النصية (text fields) تُعاد كسَلاسِل نصية من النوع String، لابُدّ من تَحْوِيلها إلى قيم عددية أولًا. إذا فشلت عملية التَحْوِيل، يَعرِض العنوان (label) رسالة خطأ: private void doOperation( char op ) { double x, y; // الأعداد المدخلة try { // اقرأ x من أول صندوق إدخال String xStr = xInput.getText(); x = Double.parseDouble(xStr); } catch (NumberFormatException e) { // ‫لم يكن xStr عدد صالح // ‫اعرض رسالة خطأ وانقل موضع التركيز إلى xInput // وحدد محتوياته بالكامل answer.setText("Illegal data for x."); xInput.requestFocus(); xInput.selectAll(); return; // توقف عن المعالجة عند حدوث خطأ } try { // ‫اقرأ y من ثاني صندوق إدخال String yStr = yInput.getText(); y = Double.parseDouble(yStr); } catch (NumberFormatException e) { answer.setText("Illegal data for y."); yInput.requestFocus(); yInput.selectAll(); return; } /* نفذ العملية الحسابية بالاعتماد على قيمة المعامل الممررة */ if (op == '+') answer.setText( "x + y = " + (x+y) ); else if (op == '-') answer.setText( "x - y = " + (x-y) ); else if (op == '*') answer.setText( "x * y = " + (x*y) ); else if (op == '/') { if (y == 0) { // Can't divide by zero! Show an error message. answer.setText("Can't divide by zero!"); yInput.requestFocus(); yInput.selectAll(); } else { answer.setText( "x / y = " + (x/y) ); } } } // end doOperation() يُمكِنك الإطلاع على شيفرة البرنامج بالكامل بالملف SimpleCalc.java. GridPane و TilePane سنَطلِّع الآن على الصَنْف الفرعي GridPane المُشتَّق من الصَنْف Pane. يُرتِّب ذلك الصَنْف أبنائه ضِمْن شبكة (grid) من الصفوف والأعمدة مُرقَّمة بدءًا من الصفر. تُبيِّن الصورة التالية شبكة مُكوَّنة من ٤ صفوف و ٥ أعمدة: قد لا تَكُون الصفوف بنفس الارتفاع كما قد لا تَكُون الأعمدة بنفس العرض. يُمكِنك أن تَتَرُك فراغات بين الصفوف أو بين الأعمدة وستَظهَر خلفية الحاوية بتلك الفراغات. إذا كان grid عبارة عن حاوية من النوع GridPane، يُمكِنك استدعاء التالي لضَبْط حجم تلك الفراغات: grid.setHGap( gapSize ); // مسافة بين الأعمدة gris.setVGap( gapSize ); // مسافة بين االصفوف إذا أردت أن تُضيِف ابنًا (child) إلى حاوية من النوع GridPane، يُمكِنك استدعاء التابع التالي، والذي تستطيع من خلاله تَخْصِيص كُلًا من رقمي الصف والعمود لمَوْضِع ذلك الابن ضِمْن الشبكة: grid.add( child, column, row ); ملحوظة: يُخصَّص رقم العمود أولًا. قد يَحتَل ابن (child) أكثر من مجرد صف أو عمود واحد ضِمْن الحاوية. يَستقبِل التابع add عدد الأعمدة والصفوف التي ينبغي لابن أن يَحتَلّها كما يلي: grid.add( child, column, row, columnCount, rowCount ); يُحدَّد عدد الصفوف والأعمدة ضِمْن شبكة معينة بناءً على قيم المواضع التي أُضيفت إليها الأبناء. تَضبُط أي حاوية من الصَنْف GridPane حجم كل ابن داخلها بحيث يَملئ المَوضِع أو مجموعة المَواضِع المُخصَّصة له ضِمْن شبكة الحاوية. تُضبَط القيمة المُفضَّلة لعَرْض عمود ضِمْن الشبكة لتَكُون كبيرة كفاية بما يَتَناسَب مع عَرْض جميع الأبناء ضِمْن ذلك العمود، ويَنطبِق الأمر نفسه على القيمة المُفضَّلة للارتفاع. يتَوفَّر أيضًا عدد من الطرائق لتَعْدِيل ذلك، ولكننا سنَكتفِي بذِكر إمكانية التَحكُّم بالقيمة الفعلية لكُلًا من عَرْض العمود وارتفاع الصف. يُمكِنك أن تُطبِّق بعض القيود (constraints) على أعمدة وصفوف حاوية من الصَنْف GridPane لكي تُخصِّص الكيفية التي يُحسَب على أساسها كُلًا من عَرْض العمود وارتفاع الصف. بصورة افتراضية، يُحسَب عَرْض أي عمود وفقًا لعَرْض الأبناء المُضمَّنة داخله، ولكن قد تَعيد ضَبْطُه إلى قيمة ثابتة (constant) أو قد تَحسِب قيمته كنسبة من المساحة المُتاحة. يَنطبِق الأمر نفسه على ارتفاع الصفوف. تُسنِد الشيفرة التالية قيم ثابتة (constant) لارتفاع أربعة صفوف ضِمْن حاوية من النوع GridPane: gridpane.getRowConstraints().addAll( new RowConstraints(100), // ارتفاع الصف رقم 0 يساوي 100 new RowConstraints(150), // ارتفاع الصف رقم 1 يساوي 150 new RowConstraints(100), // ارتفاع الصف رقم 2 يساوي 100 new RowConstraints(200), // ارتفاع الصف رقم 3 يساوي 200 ); بالمثال الأعلى، الارتفاع الكلي للحاوية عبارة عن قيمة ثابتة بغَضْ النظر عن مقدار المساحة المُتوفِّرة. أما إذا اِستخدَمنا النسب (percentages)، فستتوسَّع الحاوية لتَملئ أي مساحة إضافية مُتوفِّرة، وسيُحسَب ارتفاع الصف وعَرْض العمود وفقًا لقيمة تلك النسب. بفَرْض أن gridpane هي كائن حاوية من الصَنْف GridPane مُكوَّنة من خمسة أعمدة، يُمكِننا اِستخدَام الشيفرة التالية للتأكُّد من أن الحاوية تُغطِي العَرْض المُتاح بالكامل، وكذلك للتأكُّد من أن عَرْض جميع أعمدتها متساوي: for (int i = 0; i < 5; i++) { ColumnConstraints constraints = new ColumnConstraints(); constraints.setPercentWidth(20); // لا يوجد باني يقوم بذلكّ gridpane.getColumnConstraints().add(constraints); } إذا تجاوز مجموع النسب المُخصَّصة ١٠٠، فإنها تُقلَّص بشكل مُتناسب أي أن ضَبْط قيم النسب الخمسة إلى ٥٠ له نفس تأثير ضَبْطها جميعًا إلى ٢٠. قد تُصبِح الأمور أكثر تعقيدًا من ذلك ولكن هذه هي الحالات الأكثر شيوعًا في العموم. يَستخدِم البرنامج SimpleColorChooser.java من القسم الفرعي ٦.٢.١ حاوية من الصَنْف GridPane. ينبغي الآن أن تَكُون قادرًا على قرائته بالكامل وفهمه. إذا كنت تحتاج إلى شبكة (grid) مُكوَّنة من مستطيلات مُتساوية الحجم، قد تَستخدِم حاوية من الصَنْف TilePane حيث يُقسَّم سطحها إلى "بلاطات (tiles)" متساوية الحجم ضِمْن صفوف وأعمدة. تُناظِر كل بلاطة (tile) عُقدة واحدة والعكس أي لا يُمكِن تَوزِيع عُقدة على أكثر من بلاطة واحدة. تَملُك أي حاوية tpane من الصَنْف TilePane عددًا مُفضَّلًا من الصفوف والأعمدة يُمكِنك ضَبْطُه باستدعاء ما يلي: tpane.setPrefColumns(cols); عادةً ما تُعرَض الحاوية بحجمها المُفضَّل، ويعتمد عدد أعمدتها في تلك الحالة على عدد الأعمدة المفضَّل، ولكنها قد تُعرَض أحيانًا بحجم مختلف، ويَعتمِد عندها عدد الأعمدة على المساحة المتاحة. أما عدد الصفوف فيُحدَّد بناءً على عدد العقد الأبناء بالحاوية حيث تَملَئ تلك العُقد الحاوية بدايةً من صفها الأول من اليسار إلى اليمين ثُمَّ تَملَئ الصف الثاني وهكذا حتى تَنتهِي العقد. لاحظ أنه ليس من الضروري للصف الأخير أن يَكُون مملوءًا بالكامل. يُعدّ ما سبق صحيحًا إذا كان "اتجاه (orientation)" الحاوية أفقيًا أما إذا كان عموديًا، فيُفْترَض بك تَخْصِيص عدد الصفوف المُفضَّل وليس عدد الأعمدة. من الشائع جدًا اِستخدَام حاويات من الصَنْف TilePane عدد أعمدتها المُفضَّل يُساوِي واحد، وتُشبِه عندها الحاويات من الصَنْف VBox كما يَشيِع اِستخدَام حاويات عدد أعمدتها مُساوِي لعدد العُقد الأبناء (child nodes) داخلها، وتُشبِه في تلك الحالة الحاويات من الصَنْف HBox. عند عَرْض حاوية بحجمها المُفضَّل، يَكُون عَرْض جميع البلاطات (tiles) مُساوِيًا لأكبر عَرْض مُفضَّل لأي من أبنائها وبالمثل لارتفاع البلاطة. بالإضافة إلى ذلك، تُعيد الحاوية ضَبْط حجم كل ابن لكي يَملَئ البلاطة الخاصة به بالكامل بما هو مُتوافِق بالطبع مع القيمة العُظمى لعَرْض ذلك الابن وارتفاعه. يُوفِّر الصَنْف TilePane باني كائن (constructor) بدون أية مُعاملات (parameters) وباني آخر يَستقبِل قائمة مُكوَّنة من أي عدد من الأبناء المطلوب إضافتها للحاوية. تستطيع أيضًا إضافة أي أبناء آخرى لاحقًا باِستخدَام أي مما يلي: tpane.getChildren().add(child); tpane.getChildren().addAll(child1, child2, ...); قد تَستخدِم باني الكائن (constructor) التالي لتَخْصِيص حجم الفراغ الأفقي بين الأعمدة أو حجم الفراغ الرأسي بين الصفوف، وستَظهَر خلفية الحاوية بتلك المسافات الفارغة: TilePane tpane = new TilePane( hgapAmount, vgapAmount ); أو قد تُخصِّصها لاحقًا باستدعاء التوابع tpane.setHgap(h)‎ و tpane.setVgap(v)‎. ترجمة -بتصرّف- للقسم Section 5: Basic Layout من فصل Chapter 6: Introduction to GUI Programming من كتاب Introduction to Programming Using Java.
    1 نقطة
  37. تَعلَّمنا خلال الأقسام السابقة كيف نَستخدِم سياقًا رُسوميًا (graphics context) للرَسْم على الشاشة كما تَعلَّمنا كيف نُعالِج كُلًا من أحداث الفأرة (mouse events) وأحداث لوحة المفاتيح (keyboard events). من ناحية، يُمثِل ذلك كل ما هو مُتعلِّق ببرمجة واجهات المُستخدِم الرسومية (GUI) في العموم، فإذا كنت تريد الآن برمجة الرسوم ومعالجة أحداث لوحة المفاتيح والفأرة، فليس هناك أي شيء آخر تحتاج لتَعلُّمه. من ناحية آخرى، فإنك وبهذه المرحلة ستَضطرّ إما للقيام بعَمَل أكبر بكثير مما ينبغي أو ستضطرّ لبرمجة واجهات مُستخدِم (user interfaces) بسيطة. في الواقع، عادةً ما تَتَكوَّن واجهات المُستخدِم التقليدية من مُكوِّنات واجهة قياسية كالأزرار وشرائط التمرير (scroll bars) وصناديق الإِدْخَال النصية والقوائم، ولقد كُتبَت بالفعل تلك المُكوِّنات بطريقة تُمكِّنها من رَسْم نفسها ومُعالجة أحداث لوحة المفاتيح والفأرة الخاصة بها لكي لا تَضطرّ لتَكْرار ذلك. على سبيل المثال، أحد أبسط مُكوِّنات واجهة المُستخدِم هو "زر الضَغْط (push button)". الزر مُحاط بإطار (border) ويَعرِض نصًا معينًا يُمكِن تَغْييره. في بعض الأحيان، يَكُون الزر مُعطَّلًا (disabled) أي غَيْر مُفعَّل وعندها لا يُصبِح للنقر عليه أي تأثير كما يختلف مظهره نوعًا ما. عندما يَنقُر المُستخدِم على الزر، فإن مظهره يَتَغيَّر لحظة الضَغْط عليه ثم يَعود لمظهره العادي لحظة تَحْرِيره. لاحِظ أنه في حالة تحريك مُؤشِر الفأرة إلى خارج الزر قبل تَحْريره، سيَعُود عندها إلى مظهره العادي بذات اللحظة، كما أن تَحْريره لن يُمثِل نقرة على الزر. لتَتَمكَّن من تّنْفيذ ما سبَق، فلابُدّ أن تستجيب إما لحَدَث سَحْب الفأرة أو لحَدَث خُروج الفأرة. علاوة على ذلك، قد يُصبِح الزر مَوضِع التركيز (focus) ببعض المنصات، وعندها يَتَغيَّر مظهره، ولو ضَغَطَ المُستخدِم على مسطرة المسافات (space bar) في تلك اللحظة، فستُعدّ تلك الضغطة بمثابة نقرة على الزر. لذا لابُدّ أن يستجيب الزر لكُلًا من أحداث لوحة المفاتيح (keyboard events) والتركيز (focus events). في الواقع، أنت لست مُضطرًا لبرمجة أي من ذلك بِفَرْض اِستخدَامك لكائن ينتمي إلى الصَنْف القياسي javafx.scene.control.Button. يَتولَّى كائن الزر من الصَنْف Button مسئولية رَسْم نفسه وكذلك مُعالجة كُلًا من أحداث التركيز والفأرة ولوحة المفاتيح. ستحتاج للتَعامُل مع ذاك الزر فقط عندما يَنقُر المُستخدِم عليه أو عندما يَضغَط على مسطرة المسافات (space bar) بينما هو موضع التركيز (focus). عندما يَحدُث ذلك، سيُنشَئ كائن حَدَث (event object) ينتمي إلى الصنف javafx.event.ActionEvent ثم سيُرسَل إلى أي مُستمِع (listener) مُسجَّل لتبلّيغه بأن هنالك من ضَغَطَ على الزر، وعليه، سيَحصُل برنامجك فقط على المعلومة التي يحتاجها وهي أن الزر قد ضُغِطَ عليه. تُعرِّف واجهة برمجة تطبيقات منصة جافا إف إكس لواجهات المُستخدِم الرسومية (JavaFX GUI API) العديد من مُكوِّنات التَحكُّم (controls) القياسية باستخدام أصناف فرعية (subclasses) من الصنف Control المُعرَّف بحزمة package javafx.scene.control. يستطيع مُستخدِم البرنامج أن يَتعامَل مع تلك المُكوِّنات مما يؤدي إلى إنتاج مُدْخَلات أو وقوع أحداث (events). تُعرِّف مُكوِّنات التَحكُّم (controls) العديد من التوابع المفيدة سنَستعرِض منها ثلاثة يُمكِن اِستخدَامها مع أي مُكوِّن تَحكُّم control من الصنف Control: control.setDisable(true)‎: يُعطِّل (disable) مُكوِّن التحكُّم ويُمكِنك استدعاء control.setDisable(false)‎ لتَفْعيله مرة آخرى. عندما يَكُون مُكوِّن التَحكُّم مُعطَّلًا (disabled)، يَتَغيَّر مظهره ولا يُمكِن له عندها أن يَكُون مقصدًا (event target) لأحداث المفاتيح أو الفأرة. في الواقع، لا يَقْتصِر استدعاء تلك الدالة (function) على مُكوِّنات التَحكُّم (controls) وإنما يُمكِن استدعائها لأي عقدة (node) بمبيان المشهد (scene graph). لاحِظ أنه عند تعطيل عُقدة (node) معينة، فإن جميع العُقد (nodes) المُضمَّنة داخل تلك العُقدة تُصبِح مُعطَّلة أيضًا. تَتَوفَّر الدالة control.isDisabled()‎ والتي تُعيد قيمة منطقية تُحدِّد ما إذا كان مُكوِّن تَحكُّم (control) معين مُعطَّلًا سواء كان ذلك نتيجة لطلب تعطيله صراحةً أو لكَوْنه مُضمَّن بعُقدَة (مُكوِّن حاوي [container]) كانت قد عُطِّلَت. control.setToolTipText(string)‎: يَستقبِل سِلسِلة نصية ويَضبُطها لتُصبِح تلميحًا (tooltip) لمُكوِّن التَحكُّم. عادةً ما يُعطِي ذلك التلميح (tooltip) بعضًا من المعلومات عن مُكوِّن التَحكُّم وطريقة اِستخدَامه ويَظهَر عندما يَقَع مؤشر الفأرة بالمُكوِّن ولا يتحرك لعدة ثواني. control.setStyle(cssString)‎: يَضبُط قواعد الأنماط لمُكوِّن التَحكُّم. تُكْتَب تلك القواعد بلغة أوراق الأنماط المتعاقبة (CSS) التي ناقشناها بالقسم الفرعي ٦.٢.٥. لكي تَستخدِم مُكوِّن تحكُّم (control) أو أي عقدة آخرى بمبيان المشهد (graph node)، تحتاج عمومًا للقيام بعدة خطوات: لابُدّ أولًا أن تُنشِئ الكائن المُمثِل للمُكوِّن باستخدام باني الكائن (constructor) ثم تُضيفه إلى مُكوِّن حَاوِي (container). غالبًا ما ستحتاج إلى تسجيل مُستمِع (listener) يستجيب إلى الأحداث (events) الصادرة من ذلك المُكوِّن. قد تُخزِّن مَرجِعًا (reference) إلى المُكوِّن بمُتْغيِّر نُسخة (instance variable) في بعض الحالات لكي تَتَمكَّن من إعادة اِستخدَام المُكوِّن بَعْد إنشائه. سنَفْحَص خلال هذا القسم عددًا قليلًا من مُكوِّنات التَحكُّم (controls) القياسية البسيطة المُتاحة بمنصة جافا إف إكس (JavaFX)، والمُعرَّفة باستخدام أصناف بحزمة javafx.scene.control. سنَتَعلَّم بالقسم التالي كيفية وَضْع مُكوِّنات التحكُّم تلك بمُكوِّنات حاوية (containers). ImageView قبل مناقشة مُكوِّنات التحكُّم (controls)، سنتناول سريعًا نوع عُقدَة (node) آخر هو ImageView مُعرَّف بحزمة javafx.scene.image. كما ذَكَرَنا سابقًا بالقسم الفرعي ٦.٢.٣، تُمثِل الكائنات من الصنف Image صورًا يُمكِن تحميلها من ملفات موارد (resource files)، ولأن كائنات الصَنْف Image لا تُعدّ عُقدًا من النوع Node، لا يُمكِن لها إذًا أن تَكُون جزءًا من مبيان المشهد (scene graph)، ولهذا سنَضطرّ لرسَمها على كائن حاوية من النوع Canvas. في الواقع، يَسمَح الصنف ImageView بإضافة صورة إلى مبيان مشهد دون الحاجة إلى رَسْمها على حاوية (canvas)، فكائناتها عبارة عن عُقدَ مبيان مشهد (scene graph node) يَعمَل كُلًا منها كمُغلِّف (wrapper) للصورة تمهيدًا لعَرْضها. تُخصَّص الصورة كمُعامِل (parameter) إلى باني الصَنْف ImageView. لنَفْترِض أن مسار ملف صورة معينة هو "icons/tux.png"، يُمكِننا إذًا أن نُنشِئ كائنًا من النوع ImageView لعَرْض الصورة كالتالي: Image tux = new Image("icons/tux.png"); ImageView tuxIcon = new ImageView( tux ); نحن هنا نفكر بالصورة كما لو كانت "أيقونة" أي كما لو كانت صورة صغيرة معروضة فوق زر أو ضمن عنوان نصي (label) أو بعنصر قائمة لإضافة لمسة رسومية إلى جانب النص البسيط المعروض، وهو في الواقع ما سنرى كيفية القيام به بمنصة جافا إف إكس (JavaFX). العناوين Label والأزرار Button سنَفْحَص الآن أربعة من مُكوِّنات التَحكُّم (controls) تتشارك جميعها في أنها تَعرِض سِلسِلة نصية للمُستخدِم يُمكِنه أن يراها لكن دون أن يُجرِي عليها أي تَعْدِيل كما يُمكِنها أيضًا أن تَعرِض عنصرًا رسوميًا (graphical element) إلى جوار تلك السِلسِلة النصية أو كبديل عنها. يُمكِن لأي كائن من النوع Node أن يُمثِل ذلك العنصر الرسومي، ولكن عادةً ما تُعرَض أيقونة صغيرة مُنفَّذة (implement) باستخدام كائن من النوع ImageView. تَرِث مُكوِّنات التحكُّم (controls) الأربعة سُلوكها (behavior) من صَنْف أعلى (superclass) مشترك هو الصنف Labeled. بالقسم الفرعي ٦.٦.٢، سنَفْحَص عناصر القوائم والتي تَرِث سلوكها من نفس الصَنْف. يُعرِّف الصنف Labeled عددًا من توابع النُسخ (instance methods) التي يُمكِننا أن نَستخدِمها مع العناوين النصية (labels) والأزرار وغيرها من مُكوِّنات التحكُّم المُعنونة، نَستعرِض بعضا منها فيما يلي: setText(string)‎: يَضبُط السِلسِلة النصية المعروضة بمُكوِّن التحكُّم، والتي قد تَتكوَّن من عدة أسطر. يدل محرف سطر جديد ‎\n بسِلسِلة نصية على نهاية السطر. setGraphic(node)‎: يَضبُط العنصر الرسومي (graphical element) لمُكوِّن التحكُّم. setFont(font)‎: يَضبُط الخط المُستخدَم لرسم السِلسِلة النصية. setTextFill(color)‎: يَضبُط لون الملء المُستخدَم لرسم السِلسِلة النصية. setGraphicTextGap(size)‎: يَضبُط عَرْض المسافة الفارغة بين كُلًا من السِلسِلة النصية والعنصر الرسومي. لاحظ أن المُعامِل size من النوع double. setContentDisplay(displayCode)‎: يَضبُط موقع العنصر الرسومي بالنسبة للسِلسِلة النصية. لاحظ أن قيمة المُعامِل ستَكُون واحدة من ثوابت التعداد ContentDisplay أي ContentDisplay.LEFT أو ContentDisplay.RIGHT أو ContentDisplay.TOP أو ContentDisplay.BOTTOM. يَتَوفَّر تابع جَلْب (getter methods) لكل تابع من توابع الضَبْط (setter methods) السابقة مثل getText()‎ و getFont()‎. يَتَوفَّر أيضًا تابع آخر لضَبْط خاصية لون الخلفية. بفَرْض أن c هو مُكوِّن تحكُّم (control)، يُمكِننا اِستخدَام الشيفرة التالية لضَبْط لون خلفيته إلى الأبيض: c.setBackground(new Background(new BackgroundFill(Color.WHITE,null,null))); حيث الأصناف Background و BackgroundFill مُعرَّفة بحزمة javafx.scene.layout. في الواقع، يُمكِنك القيام بنفس الشيء بطريقة أسهل باِستخدَام التابع setStyle()‎ لضَبْط قواعد أنماط CSS الخاصة بمُكوِّن التحكُّم (control)، ويُمكِنك أيضًا اِستخدَامها لضَبْط كُلًا من الإطار (border) والحشوة (padding). تُمثِل العناوين من الصَنْف Label أحد أبسط أنواع مُكوِّنات التحكُّم، فهو لا يُضيف أي شيء تقريبًا إلى الصنف Labeled، ويُستخدَم لعَرْض نص غَيْر قابل للتَعْدِيل مع رسمة أو بدون. يُعرِّف الصنف Label بانيين (constructors)، يَستقبِل الأول مُعامِلًا من النوع String يُحدِّد نصًا للعنوان (label) أما الثاني فيَستقبِل مُعامِلًا إضافيًا من النوع Node يُحدِّد رسمة (graphic) للعنوان. بفَرْض أن tuxIcon هو كائن من النوع ImageView من القسم الفرعي السابق، اُنظر المثال التالي: Label message = new Label("Hello World"); Label linuxAd = new Label("Choose Linux First!", tuxIcon); لاحِظ أن خلفية العناوين (labels) من النوع Label تَكُون شفافة وبدون إطار (border) أو حشوة (padding) افتراضيًا. غالبًا يُفضَّل إضافة قليل من الحشوة على الأقل. اُنظر المثال التالي لضَبْط الخاصيات الثلاثة باِستخدَام لغة CSS: message.setStyle("-fx-border-color: blue; -fx-border-width: 2px; " + "-fx-background-color: white; -fx-padding: 6px"); لقد تَعرَّضنا للأزرار بالقسم ٦.١. تَعرِض الأزرار من الصنف Button نصًا مع رسمة أو بدون. يُعرِّف الصَنْف Button بانيين (constructors) تمامًا مثل الصَنْف Label: Button stopButton = new Button("Stop"); Button linuxButton = new Button("Get Linux", tuxIcon); عندما يَضغَط المُستخدِم على زر، يَقَع حَدَث (event) من النوع ActionEvent، ويُستخدَم التابع setOnAction لتسجيل مُعالِج حدث (event handler) كالتالي: stopButton.setOnAction( e -> animator.stop() ); بالإضافة إلى التوابع (methods) الموروثة من الصَنْف Labeled، يُعرِّف الصنف Button توابع نُسخ (instance methods) آخرى مثل setDisable(boolean)‎ و setToolTip(string)‎ والتي ذَكَرناها ببداية هذا القسم. يُمكِننا أيضًا اِستخدَام التابعين setDisable()‎ و setText()‎ لإعطاء المُستخدِم بعض المعلومات عما يَحدُث بالبرنامج. اِستخدَامك لزر مُعطَّل (disabled) هو أفضل عمومًا من اِستخدَامك لزر يُعطِي رسالة خطأ مثل "عذرًا، لا يُمكِنك الضَغْط على الزر الآن" بَعْد الضَغْط عليه. لنَفْترِض مثلًا أن لدينا زرين لتَشْغِيل تحريكة (animation) مُمثَلة بالكائن animator من الصَنْف AnimationTimer وإيقافها، فينبغي تَعْطيل زر بدء التَشْغِيل عندما تَكُون التحريكة مُشغَّلة أما زر الإيقاف فينبغي أن يُعطَّل عندما تَكُون التحريكة مُتوقِّفة مؤقتًا. اُنظر الشيفرة التالية: Button startButton = new Button("Run Animation"); Button stopButton = new Button("Stop Animation"); stopButton.setDisable(true); // Stop button is initially disabled. startButton.setOnAction( e -> { animator.start(); startButton.setDisable(true); stopButton.setDisable(false); } ); stopButton.setOnAction( e -> { animator.stop(); startButton.setDisable(false); stopButton.setDisable(true); } ); تتأكَّد الشيفرة بالأعلى من أن المُستخدِم لا يُمكِنه أن يحاول بدء تَشْغِيل تحريكة (animation) بينما هي مُشغَّلة بالفعل أو إيقافها عندما تَكُون مُتوقِّفة. غالبًا ما يُوفِّر البرنامج زرًا يؤدي لحُدوث فِعل (action) افتراضي معين. فمثلًا، قد يُدخِل المُستخدِم مجموعة بيانات بصناديق إِدْخَال نصية ثم يَنقُر على زر "Compute" لمعالجة تلك البيانات. سيَكُون من الرائع لو تَمكَّن المُستخدِم من مُجرّد الضغط على المفتاح "Return" عند انتهاءه من الكتابة بدلًا من النقر على الزر. في الحقيقة، يُمكِنك ضَبْط الكائن button من الصَنْف Button ليُصبِح زر النافذة الافتراضي (default button) باستدعاء التالي: button.setDefaultButton(true); في حالة وجود زر افتراضي (default button) بنافذة (window) معينة، فإن الضَغْط على المفتاح "Return" أو "Enter" بلوحة المفاتيح يَكُون مكافئًا للنقر على ذلك الزر الافتراضي إلا لو اُستُهلِك حَدَث (event) الضَغْط عليه من قِبَل مُكوِّن آخر. مربعات الاختيار CheckBox وأزرار الانتقاء RadioButton مربعات الاختيار (check box) من الصَنْف CheckBox هي نوع آخر من مُكوِّنات التَحكُّم. أي مربع اختيار له "حالة (state)"، فإما أن يَكُون مختارًا (selected) أو غَيْر مُختار (unselected)، ويستطيع المُستخدِم عمومًا أن يُغيِّر حالة مربع اختيار (check box) معين بالنقر عليه. تُستخدَم قيمة من النوع boolean لتمثيل حالة مربع الاختيار بحيث تَكُون مُساوية للقيمة المنطقية true إذا كان مختارًا (selected) وللقيمة المنطقية false إذا كان غَيْر مُختار. علاوة على ذلك، يَعرِض أي مربع اختيار (checkbox) عنوانًا (label) يُخصَّص عند إنشائه كالتالي: CheckBox showTime = new CheckBox("Show Current Time"); لأن الصَنْف CheckBox هو صَنْف فرعي (subclass) من الصَنْف Labeled، يُمكِنك بطبيعة الحال اِستخدَام جميع توابع النُسخ (instance methods) المُعرَّفة بالصَنْف Labeled مع كائنات مربعات الاختيار (checkboxes). بالمثل من مُكوِّنات التَحكُّم السابقة (controls)، يُمكِن لمربعات الاختيار أن تَعرِض رسمة (graphic)، ولكنها لا تُوفِّر باني (constructor) لضَبْط تلك الرسمة وإنما لابُدّ من استدعاء setGraphic(node)‎ لضَبْطها. عادةً ما يَكُون المُستخدِم هو المسئول عن ضَبْط حالة مربع اختيار من الصَنْف CheckBox بالنقر عليه، ولكن من الممكن أيضًا ضَبْط حالته (state) برمجيًا باستدعاء التابع setSelected(boolean)‎. على سبيل المثال، إذا كان لديك كائن مربع اختيار showTime من الصَنْف CheckBox، فبإمكانك اختياره (selected) باستدعاء showTime.setSelected(true)‎ أو إلغاء الاختيار باستدعاء showTime.setSelected(false)‎. يَتَوفَّر أيضًا التابع isSelected()‎ والذي يُعيد قيمة منطقية من النوع boolean تُحدِّد الحالة (state) الحالية لمربع الاختيار. عندما يُغيِّر مُستخدِم من حالة (state) مربع اختيار (checkbox) من الصَنْف CheckBox، يَقَع حَدَث (event) من النوع ActionEvent، لذا إذا أردت أن تُنفِّذ شيئًا أثناء تَغيُّر حالة مربع اختيار معين، فلابُدّ إذًا من أن تُسجِّل معالجًا (handler) بذلك المربع عن طريق استدعاء تابعه setOnAction()‎. ولكن في العادة، لا تحتاج البرامج لذلك وتَقْتصِر على فَحْص حالة مربعات الاختيار باستدعاء التابع isSelected()‎. لاحِظ أنه في حالة تَغيُّر الحالة برمجيًا أي باستدعاء التابع setSelected()‎، لا يَقَع حَدَث من النوع ActionEvent، ولكن هنالك تابع آخر مُعرَّف بالصنف CheckBox اسمه هو fire()‎، والذي يُحاكِي حُدوث نقرة على مربع الاختيار ويُولِّد حَدَثًا من النوع ActionEvent. في الواقع، هنالك حالة ثالثة لمربعات الاختيار (checkboxes): "غير مُحدَّد (indeterminate)" على الرغم من كَوْنها غَيْر مُفعَّلة افتراضيًا. اُنظر توثيق واجهة برمجة التطبيقات (API) لمزيد من التفاصيل. تتشابه أزرار الانتقاء (radio buttons) مع مربعات الاختيار (check boxes) نوعًا ما. كما هو الحال مع مربعات الاختيار، يُمكِن لأي زر انتقاء أن يَكُون مُختارًا (selected) أو غَيْر مُختار. يُفْترَض لأزرار الانتقاء (radio buttons) أن تَقَع ضِمْن مجموعات، ويُسمَح باختيار زر انتقاء وحيد على الأكثر ضِمْن المجموعة بأي لحظة، بتعبير آخر، تَسمَح مجموعات أزرار الانتقاء للمُستخدِم باختيار وحيد من عدة بدائل. تُستخدَم كائنات الصَنْف RadioButton بمنصة جافا إف إكس لتمثيل أزرار الانتقاء (radio button). عند اِستخدَامها بمفردها، فإنها تكون شبيهة تمامًا بمربع اختيار من الصَنْف CheckBox، فلديها نفس الباني (constructor)، والتوابع (methods)، والأحداث (events) بما في ذلك التوابع الموروثة من الصنف Labeled. لكنها غالبًا ما تُستخدَم ضِمْن مجموعة، ويُستخدَم عندها كائن من الصنف ToggleGroup لتمثيل المجموعة. لاحِظ أن الصنف ToggleGroup ليس بمُكوِّن ولا يظهر على الشاشة، فهو يعمل فقط خلف الكواليس كمُنظم لمجموعة من أزرار الانتقاء (radio buttons) ليتأكَّد من اختيار زر واحد فقط ضِمْن المجموعة بأي لحظة. لكي تَستخدِم مجموعة أزرار انتقاء (radio buttons)، لابُدّ أن تُنشِئ كائنًا من الصَنْف RadioButton لكل زر انتقاء ضِمْن المجموعة بالإضافة إلى كائن واحد من النوع ToggleGroup لتنظيمها. لا يلعب الصنف ToggleGroup أي دور فيما يتعلَّق برسم الأزرار على الشاشة، فلابُدّ أن تُضيف كُلًا منها على حدى إلى مبيان المشهد (scene graph) لكي يَظهَر على الشاشة. لابُدّ أيضًا أن تُضيف كُلًا منها إلى كائن الصنف ToggleGroup من خلال استدعاء تابع النُسخة setToggleGroup(group)‎ بكائن زر الانتقاء. إذا أردت اختيار أحد الأزرار بصورة افتراضية، يُمكِنك استدعاء التابع setSelected(true)‎ لذلك الزر، وإن لم تَفعَل، فلن يَكُون أيًا منها مُختارًا (selected) إلى أن يَنقُر المُستخدِم على إحداها. على سبيل المثال، تَستعرِض الشيفرة التالية طريقة تهيئة مجموعة من أزرار الانتقاء (radio buttons) التي تَسمَح للمُستخدِم باختيار لون: RadioButton redRadio, blueRadio, greenRadio, yellowRadio; // تمثل المتغيرات أزرار انتقاء // قد تكون تلك متغيرات نسخة لكي تستخدم عبر البرنامج ToggleGroup colorGroup = new ToggleGroup(); redRadio = new RadioButton("Red"); // اِنشئ زر redRadio.setToggleGroup(colorGroup); // أضفه إلى مجموعة الأزرار blueRadio = new RadioButton("Blue"); blueRadio.setToggleGroup(colorGroup); greenRadio = new RadioButton("Green"); greenRadio.setToggleGroup(colorGroup); yellowRadio = new RadioButton("Yellow"); yellowRadio.setToggleGroup(colorGroup); redRadio.setSelected(true); // Make an initial selection. بدلًا من استدعاء التابع redRadio.setSelected(true)‎، يُمكِنك أن تَستدعِي تابع النسخة selectToggle()‎ المُعرَّف بالصَنْف ToggleGroup لاختيار زر الانتقاء (radio button) كالتالي: colorGroup.selectToggle( redRadio ); تمامًا كمربعات الاختيار (checkboxes)، لست مضطرًّا إلى أن تُسجِّل مُستمِعًا (listener) لأحداث أزرار الانتقاء (radio buttons). يُمكِنك فَحْص حالة (state) زر انتقاء معين من الصَنْف RadioButton باستدعاء تابعه isSelected()‎ أو باستدعاء التابع getSelectedToggle()‎ بكائن المجموعة من الصَنْف ToggleGroup. يُعيد ذلك التابع قيمة نوعها Toggle عبارة عن واجهة (interface) يُنفِّذها (implement) الصَنْف RadioButton. اُنظر المثال التالي: Toggle selection = colorGroup.getSelectedToggle(); if (selection == redRadio) { color = Color.RED; } else if (selection == greenRadio){ . . . تُوضِح الصورة التالية كيف ستبدو أزرار الانتقاء (radio buttons) بالأعلى عند اصطفافها رأسيًا ضِمْن مُكوِّن حاوية (container): الحقول النصية TextField والمساحات النصية TextArea يُمثِل الصنفان TextField و TextArea مُكوِّنات إِدْخَال نصي (text input component) حيث تَعرِض نصًا يستطيع المُستخدِم أن يُعدّله. يُمكِن لأي حقل نصي من الصنف TextField أن يَحمِل سطرًا واحدًا فقط أما المساحة النصية من الصَنْف TextArea فيُمكِنها أن تَحمِل عدة أسطر. تستطيع أن تضبط حقلًا نصيًا TextField معينًا أو مساحة نصية TextArea معينة بحيث تَكُون للقراءة فقط (read-only) أي سيَتَمكَّن المُستخدِم عندها من قراءة النص الموجود لكنه لن يَتَمكَّن من تَعْديله. في الواقع، الأصناف TextField و TextArea هي أصناف فرعية (subclasses) من صَنْف مُجرّد (abstract class) اسمه TextInputControl يُعرِّف بعضًا من الخاصيات المشتركة بينها. يشترك الصَنْفان TextField و TextArea بكثير من التوابع (methods) سنَستعرِض بعضًا منها فيما يلي: يُعيد تابع النُسخة getText() قيمة من النوع String تُمثِل محتويات مُكوِّن إِدْخَال معين. يُستخدَم تابع النسخة setText(text) لتَغْيير النص المعروض بمُكوِّن إِدْخَال حيث يَستقبِل مُعامِلًا من النوع String ويستبدله بالنص الحالي لمُكوِّن الإدخال. يُستخدَم تابع النسخة appendText(text)‎ لإضافة سِلسِلة نصية من النوع String بنهاية النص الموجود بالفعل بمُكوِّن إِدْخَال. قد يَتَضمَّن النص المُمرَّر إلى التابعين setText()‎ و appendText()‎ محرف سطر جديد ‎\n لتمثيل نهاية السطر، ولكنه لن يُؤثِر بالحقول النصية من الصَنْف TextField. يُمكِنك أيضًا أن تَستخدِم تابع النسخة setFont(font)‎ لتَغْيير الخط المُستخدَم بمُكوِّن الإدخال النصي. يُمكِنك أيضًا استدعاء التابع setEditable(false)‎ لمَنْع المُستخدِم من تَعْدِيل نص مُكوِّن إِدْخَال معين. مَرِّر القيمة المنطقية true لنفس التابع لكي تجعله قابل للتَعْدِيل مرة آخرى. يستطيع المُستخدِم أن يَكْتُب بمُكوِّن إِدْخَال نصي (input component) معين فقط بَعْد جَعَله موضع التركيز (focus) عن طريق النقر عليه بالفأرة. يُمكِنك أيضًا استدعاء التابع requestFocus()‎ لمُكوِّن إِدْخَال -مثل الحقول النصية (text fields)- لضبطه برمجيًا ليُصبِح موضع التركيز (focus)، وهو ما قد يَكُون مفيدًا في بعض الأحيان. يستطيع المُستخدِم أيضًا أن يُحدِّد جزءًا من نص مُكوِّن إدخال ويَظهَر عندها مُظلَّلًا ويُمكِن قَصُه (cut) أو نَسخُه (copy) كما يَتَضمَّن الصَنْف TextInputComponent مجموعة من توابع النسخ (instance methods) لأغراض تَحْدِيد النصوص (text selection) منها التابع selectAll()‎ والذي يُحدِّد نص مُكوِّن إِدْخَال بأكمله. على سبيل المثال، عندما تَكْتشِف وجود خطأ بالقيمة التي أَدْخَلها المُستخدِم بحَقْل نصي input من الصَنْف TextField، يُمكِنك عندها أن تَستدعِي كُلًا من التابعين input.requestFocus()‎ و input.selectAll()‎، وهو ما يُساعِد المُستخدِم على اكتشاف مكان حُدوث الخطأ كما يَسمَح له ببدء تصحيحه مباشرة؛ فبمُجرّد أن يبدأ بالكتابة، سيُحذَف النص المُظلَّل. يُعرِّف الصَنْفين TextField و TextArea بانيين (constructors). لا يَستقبِل الباني الأول أي مُعامِلات (parameter) ويُستخدَم لإنشاء مُكوِّن إِدْخَال نصي فارغ أما الثاني فيَستقبِل مُعامِلًا من النوع String يُخصِّص نصًا افتراضيًا لمُكوِّن الإِدْخَال. تملك الحقول النصية من الصَنْف TextField خاصية عدد الأعمدة (columns)، والتي تُخصِّص العرض (width) المُفضّل للحقل، وتُساوِي ١٢ بشكل افتراضي. تُستخدَم تلك القيمة المُفضّلة لضَبْط حجم الحقل إذا لم يُعاد ضَبْطه بواسطة البرنامج على سبيل المثال. بفَرْض وجود كائن حقل نصي input من الصَنْف TextField، يُمكِنك استدعاء التابع input.setPrefColumnCount(n)‎ لضَبْط عدد الأعمدة حيث n هي عدد صحيح موجب. على نحو مُشابه، تَملُك المساحات النصية من الصَنْف TextArea عدد أعمدة يُساوِي ٤٠ افتراضيًا وكذلك عدد صفوف يُساوِي ١٠ افتراضيًا. يُستخدَم تابعي النسخة setPrefColumnCount(n)‎ و setPrefRowCount(n)‎ بالصَنْف TextArea لتَعْدِيل قيمتهما على الترتيب. إلى جانب التوابع (methods) الموروثة (inherit) من الصَنْف TextInputControl، يُعرِّف الصَنْف TextArea عدة توابع آخرى بما في ذلك تابع لضَبْط مقدار المسافة التي نزلها النص، وآخر لجَلْب ذلك المقدار. مثال آخر هو التابع setWrapText(wrap)‎ حيث wrap هو مُعامِل من النوع boolean. يُخصِّص ذلك التابع طريقة عَرْض الأسطر النصية الطويلة التي لا تَتَناسَب مع حجم المساحة النصية (text area). إذا مرَّرنا true كقيمة للمُعامِل wrap، فإنها ستُقسَّم إلى عدة أسطر مع إضافة محرف سطر جديد بين الكلمات إن أمكن. أما إذا مرَّرنا القيمة false، فإنها ستمتد إلى خارج المساحة النصية وسيضطرّ المُستخدِم إلى تمرير المساحة النصية أفقيًا (scroll) ليرى محتوى السطر بالكامل. لاحِظ أن قيمة wrap الافتراضية هي false. لمّا كانت الحاجة لتَحرِيك مساحة نصية (text area) من الصَنْف TextArea أفقيًا (scroll) ضرورية في أحيان كثيرة لكي نَتَمكَّن من رؤية مُحتواها النصي بالكامل، فإن ذلك الصَنْف يُوفِّر شرائط تمرير (scroll bars) تُصبِح مرئية عند الضرورة فقط أي عندما لا يَتَناسَب النص مع المساحة المتاحة. يُمكِنك الإطلاع على ملف البرنامج TextInputDemo.java والذي يُوظِّف حقلًا نصيًا (text field) ومساحة نصية (text area) ضِمْن مثال بسيط. تُبيِّن الصورة التالية نافذة البرنامج بَعْد تَعْدِيل النص وتمريره للأسفل قليلًا: المنزلق Slider يَسمَح مُنزلِق (slider) من الصَنْف Slider للمُستخدِم باختيار قيمة عددية صحيحة ضِمْن نطاق من القيم المُحتملة عن طريق سَحْب عُقدة على طول شريط. يُمكِنك تَزْيِين المُنزلِق بعلامات تجزئة (tick marks) أو بعناوين (labels). تَعرِض نافذة البرنامج SliderDemo.java بالأسفل ثلاثة مُنزلِقات يَسمَح كُلًا منها بنطاق مُختلف من القيم كما أنها مُزيَّنة بشكل مختلف: اُستخدِمت علامات تجزئة لتَزْيِين المُنزلِق الثاني بينما اُستخدِمت عناوين لتَزْيِين المُنزلِق الثالث. يُمكِنك أيضًا تَزْيِين مُنزلِق واحد بكلتا الطريقتين معًا. يُخصِّص الباني (constructor) الأكثر شيوعًا بالصَنْف Slider بداية ونهاية نطاق القيم المُحتملة للمُنزلِق (slider) وكذلك قيمته الافتراضية عند عَرْضه على الشاشة لأول مرة. يُكْتَب ذلك الباني كالتالي: public Slider(double minimum, double maximum, double value) إذا لم نُمرِّر أي مُعامِل (parameter) للباني، ستُستخدَم القيم ٠ و ١٠٠ و ٠ افتراضيًا وعلى الترتيب. يَظهَر المُنزلِق أفقيًا بصورة افتراضية، ولكن يُمكِنك استدعاء setOrientation(Orientation.VERTICAL)‎ لضَبْطه بحيث يَظهَر رأسيًا. لاحِظ أن Orientation عبارة عن تعداد مُعرَّف بحزمة package javafx.geometry. يُمكِنك قراءة القيمة الحالية لمُنزلِق (slider) من الصَنْف Slider بأي لحظة باستدعاء تابعه getValue()‎ والذي يُعيد قيمة من النوع double. أما إذا أردت ضَبْط قيمته، فيُمكِنك استدعاء التابع setValue(val)‎ والذي يَستقبِل مُعاملًا (parameter) من النوع double. إذا كانت القيمة المُمرَّرة لتابع الضَبْط واقعة خارج نطاق القيم المسموح بها، فإنها ستُعدَّل لكي تُصبِح ضِمْن ذلك النطاق. إذا أردت أن تُنفِّذ شيئًا عندما يُغيِّر المُستخدِم قيمة مُنزلِق (slider) معين، فينبغي أن تُسجِّل مُستمِعًا (listener) بذلك المُنزِلق. بخلاف مُكوِّنات التَحكُّم الآخرى، لا تُولِّد المنزلِقات من الصَنْف Slider أحداثًا من النوع ActionEvent، وإنما تَملُك خاصية قابلة للمُراقبة (observable) من النوع Double تُمثِل قيمته (انظر القيم الفرعي ٦.٣.٧). بفَرْض وجود كائن مُنزلِق slider من الصَنْف Slider، يُمكِنك استدعاء التابع slider.valueProperty()‎ لمَعرِفة قيمته. يُمكِنك أيضًا أن تُسجِّل مُستمِعًا (listener) لتلك الخاصية سيُستدعَى بكل مرة تَتَغيَّر فيها قيمة المُنزلِق. تُبيِّن الشيفرة التالية كيفية إضافة مُستمِع (listener) لكائن مُنزلِق: slider1.valueProperty().addListener( e -> sliderValueChanged(slider1) ); سيُستدعَى ذلك المُستمِع (listener) كلما تَغيَّرت قيمة المُنزلِق سواء كان ذلك نتيجة لسَحْب العقدة على الشريط أو لاستدعاء البرنامج للتابع setValue()‎. يُمكِنك استدعاء التابع isValueChanging()‎ المُعرَّف بكائن المُنزلِق لمَعرِفة ما إذا كان المُستخدِم هو المسئول عن وقوع الحَدَث (event) حيث يُعيد ذلك التابع القيمة المنطقية true إذا كان المُستخدِم يَسحَب العقدة على الشريط. لعرض علامات التجزئة (tick marks) على مُنزلِق (slider)، ستحتاج للقيام بالخطوتين التاليتين: اُضبط أولًا الفاصل بين علامات التجزئة (tick marks) ثم بلِّغ المُنزلِق برغبتك بعَرْض علامات التجزئة. في الحقيقة، هنالك نوعين من علامات التجزئة: علامات تجزئة رئيسية (major) وأخرى ثانوية (minor)، ويُمكِنك عَرْْض واحدة منهما فقط أو كلتاهما. علامات التجزئة الرئيسية تَكُون أطول قليلًا بالمُوازنة مع علامات التجزئة الثانوية. يَستقبِل التابع setMajorTickSpacing(x)‎ مُعامِلًا من النوع double ويُشير إلى أن علامات التجزئة الرئيسية ينبغي أن تُعرَض بفارق x من الوحدات على طول المُنزلِق. لاحِظ أن الفاصل بين تلك العلامات يَكُون على أساس قيم المُنزلِق وليس البكسل. في المقابل، يَستقبِل التابع setMinorTickCount(n)‎ مُعامِلًا من النوع int، ويُخصِّص عدد علامات التجزئة الثانوية المُفْترَض عَرْضها بين كل علامتين رئيسيتين متتاليتين وتُساوِي ٤ افتراضيًا. يُمكِنك ضَبْطها إلى صفر إذا لم ترغب بعَرْض أي علامات تجزئة ثانوية. لاحِظ أن استدعاء تلك التوابع (methods) ليس كافيًا لعَرْض علامات التجزئة (tick marks)، بل ستحتاج أيضًا إلى استدعاء setShowTickMarks(true)‎. على سبيل المثال، تُنشِئ التَعْليمَات التالية المُنزلِق (slider) الثاني من البرنامج السابق وتَضبُطه: slider2 = new Slider(); // استخدم القيم الافتراضية ‫(0,100,0) slider2.setMajorTickUnit(25); // المسافة بين علامات التجزئة الرئيسية slider2.setMinorTickCount(5); // ‫5 علامات تجزئة بين كل علامتي تجزئة slider2.setShowTickMarks(true); تُعالَج العناوين (labels) على المنزلق (slider) بنفس الطريقة، فسيُعرَض العنوان بكل علامة تجزئة رئيسية، ولكن قد تُحذَف بعض العناوين إذا تداخلت مع بعضها البعض. ستحتاج إلى استدعاء setShowTickLabels(true)‎ لعَرْض العناوين. على سبيل المثال، تُنشِئ التَعْليمَات التالية المُنزلِق (slider) الثالث من البرنامج السابق وتَضبُطه: slider3 = new Slider(2000,2100,2018); slider3.setMajorTickUnit(50); // لا تعرض علامات التجزئة slider3.setShowTickLabels(true) لاحِظ أن قيمة أي مُنزلِق (slider) تَكُون من النوع double. أحيانا، قد تَرغَب بقَصرِها على الأعداد الصحيحة فقط أو على مضاعفات قيمة معينة. في مثل هذه الحالات، يُمكِنك استدعاء التابع slider.setSnapToTicks(true)‎، وعندها ستُضبَط قيمة المُنزلِق تلقائيًا إلى أقرب علامة تجزئة رئيسية أو ثانوية -حتى لو لم تَكُن مرئية- بعدما ينتهي المُستخدِم من سَحْب العُقدَة. لا يُطبَق ذلك التقييد بينما يسَحَب المُستخدِم العُقدَة وإنما تُضبَط قيمته فقط بَعْد انتهائه. لا يَلتزِم أيضًا التابع setValue(x)‎ بتلك القيود، لذا قد تَستخدِم التابع adjustValue(x)‎ بدلًا عنه والذي يَضبُط قيمة المُنزِلق لأقرب علامة تجزئة. على سبيل المثال، إذا أردت لمُنزلِق أن يَقْتصِر على الأعداد الصحيحة بنطاق يتراوح من ٠ إلى ١٠، يُمكِنك اِستخدَام التالي: Slider sldr = new Slider(0,10,0); sldr.setMajorTickUnit(1); // major ticks 1 unit apart sldr.setMinorTickCount(0); // لا توجد علامة تجزئة ثانوية sldr.setSnapToTicks(true); ضُبِطَ المُنزلِق الثالث بالمثال التوضيحي بحيث يَقْتصِر على قيمة صحيحة بنهاية أي عملية سَحْب. ترجمة -بتصرّف- للقسم Section 4: Basic Controls من فصل Chapter 6: Introduction to GUI Programming من كتاب Introduction to Programming Using Java.
    1 نقطة
  38. مرحبا عندي مشروع تخرج بدي انقل بيانات من قاعدة البيانات ع صفحة ويبhtml
    1 نقطة
  39. 1 - عليك تجهيز قاعده البيانات بالجداول المطلوبه في المشروع .. الخ 2 - في ملف ال php تقوم بربط الملف بقاعده البيانات " لتتمكن من ارسال و استقبال البيانات من قاعده البيانات للصفحه و العكس ، كما هو موضح $host = "localhost"; $name = "root"; $pass = ""; $dbname = ""; // هنا اكتب اسم قاعده البيانات بين العلامتين $connection = mysqli_connect($host,$name,$pass,$dbname); mysqli_query($connection,"set character_set_server='utf8'"); mysqli_query($connection,"set names 'utf8'"); if (!$connection) { die(); } 3 - و الآن لاسترجاع البيانات من قاعده البيانات و لنفترض من جدول يسمى members ، انظر المثال <?php $get_news_sql = "SELECT * FROM `members`"; $get_news_sql = mysqli_query($connection, $get_news_sql); while ($row = mysqli_fetch_array($get_news_sql)) { echo $row['id']; // نبدأ باسترجاع بيانات كل عضو مكتوب في جدول الاعضاء عن طريق كتابه اسم العمود بين القوسين echo $row['name']; // اسم العضو .. وهكذا } ?>
    1 نقطة
×
×
  • أضف...