الوحدة 5: التفاعل مع المستخدم: الطباعة والإدخال عبر Console (Scanner) و GUI (JOptionPane)

تعلم كيفية التفاعل مع المستخدمين باستخدام الكونسول والنوافذ الرسومية

المدة: أسبوعان 6 ساعات نظرية 6 ساعات عملية

المقدمة والأهداف التعليمية

في الوحدات السابقة، تعلمنا أساسيات المتغيرات، أنواع البيانات، وكيفية إجراء العمليات الحسابية والمنطقية. كما تعرفنا على مفهوم الدوال كوسيلة لتنظيم الكود. في هذه الوحدة، سنخطو خطوة إضافية نحو بناء برامج أكثر تفاعلية من خلال تعلم كيفية **الطباعة والإدخال عبر الكونسول** باستخدام فئة `Scanner`، وكيفية استخدام **النوافذ الرسومية البسيطة** (`JOptionPane`) للتفاعل مع المستخدم.

سواء كنت تبني تطبيقات نصية بسيطة أو تتجه نحو الواجهات الرسومية، فإن فهم آليات الإدخال والإخراج أمر حيوي. ستوفر لك هذه الوحدة الأدوات اللازمة لجعل برامجك تتلقى البيانات من المستخدم وتستجيب له بطرق مختلفة.

الأهداف:

بنهاية هذه الوحدة، ستكون قادرًا على:

  • فهم الغرض من فئتي `Scanner` و `JOptionPane` في جافا.
  • استخدام الكلاس `Scanner` لاستقبال أنواع مختلفة من المدخلات من الكونسول (نصوص، أعداد صحيحة، أعداد عشرية).
  • استخدام دالة `showMessageDialog()` لعرض أنواع مختلفة من الرسائل للمستخدم في نافذة رسومية.
  • استخدام دالة `showInputDialog()` لأخذ المدخلات النصية من المستخدم في نافذة رسومية.
  • تحويل المدخلات النصية التي تم الحصول عليها من `JOptionPane` إلى أنواع بيانات رقمية (مثل `int` و `double`).
  • تطبيق كلتا الطريقتين (`Scanner` و `JOptionPane`) لإنشاء برامج بسيطة تتفاعل مع المستخدم.
  • التعامل مع الأخطاء الشائعة المرتبطة بإدخال البيانات (مثل `NullPointerException`, `NumberFormatException`).
---

مدخل إلى إدخال البيانات في جافا

في الوحدات السابقة، كنا نكتب الكود ثم ننفذه مباشرة، بحيث تكون القيم التي نعتمد عليها في البرنامج محددة مسبقًا داخل الكود نفسه. أي أننا كنا نعرف النتيجة قبل تشغيل البرنامج لأننا قمنا بتخزين القيم في المتغيرات أثناء كتابة الكود.

لكن البرمجة التفاعلية تتطلب شيئًا آخر: نريد أن ينفذ البرنامج أوامر معينة بناءً على مدخلات المستخدم، أي أن المستخدم هو من يحدد القيم أثناء تشغيل البرنامج.

في هذا الجزء من الوحدة، سنتعلم كيفية جعل برنامج جافا يتفاعل مع المستخدم عبر **الكونسول (Console)**، بحيث يطلب منه إدخال بيانات من لوحة المفاتيح، ثم يقوم البرنامج بمعالجة هذه البيانات أو استخدامها في تنفيذ عمليات معينة.

---

الكلاس Scanner

الكلاس Scanner هو أحد الكلاسات الجاهزة في مكتبة Java ضمن الحزمة java.util. وظيفته الأساسية هي استقبال البيانات من المستخدم (Input) وتحويلها إلى النوع المناسب من البيانات (int, double, String… إلخ) لمعالجتها.

يُستخدم هذا الكلاس بشكل أساسي لقراءة المدخلات من مصادر نصية مثل الكونسول (لوحة المفاتيح)، الملفات، أو حتى سلاسل نصية (Strings).

أمثلة على مصادر الإدخال التي يمكن أن يتعامل معها `Scanner`:
  • `System.in` (الكونسول): الأكثر شيوعًا، لقراءة مدخلات المستخدم عبر لوحة المفاتيح.
  • الملفات (`File`): لقراءة البيانات المخزنة في ملف نصي.
  • السلاسل النصية (`String`): لقراءة البيانات من نص موجود مسبقًا في البرنامج.
---

أنواع البيانات التي يمكن استقبالها باستخدام Scanner

يمكن للـ Scanner استقبال معظم أنواع البيانات الأساسية في Java، ومنها:

  • الأعداد الصحيحة (byte, short, int, long): مثل 123.
  • الأعداد العشرية (float, double): مثل 10.55.
  • الحروف (char): على الرغم من أن `Scanner` لا يمتلك دالة `nextChar()` مباشرة، يمكن قراءة حرف واحد باستخدام `next().charAt(0)`.
  • الكلمات والسلاسل النصية (String): سواء كانت كلمة واحدة أو جملة كاملة.
  • القيم المنطقية (boolean): مثل true أو false.

لكل نوع بيانات تريد استقباله في البرنامج، توجد دالة خاصة به في `Scanner`، وسنتعرف عليها بالتفصيل لاحقًا.

---

مصطلحات تقنية مهمة

  • Delimiter (فاصل): هو النمط (Pattern) المستخدم للفصل بين البيانات أو الكلمات. بشكل افتراضي، `Scanner` يستخدم المسافات البيضاء (White spaces) كفواصل. يمكنك برمجيًا تحديد رموز أو أحرف معينة لتُعامل كفواصل (مثل الفاصلة `,` أو النقطة `;`).
  • White Space (مسافة بيضاء): تشير إلى أي فراغات غير مرئية تفصل بين العناصر في النص، مثل المسافة العادية (` `)، التاب (`\t`)، أو السطر الجديد (`\n`).
  • Console Application (تطبيق الكونسول): هو البرنامج الذي يعمل في واجهة نصية فقط (مثل سطر الأوامر)، دون واجهة رسومية (GUI). جميع البرامج التي تتفاعل مع المستخدم باستخدام `System.out.print` و `Scanner` هي تطبيقات كونسول.
---

خطوات جعل البرنامج يستقبل بيانات من المستخدم (عبر الكونسول)

لكي نكتب برنامجًا في Java يستقبل بيانات من المستخدم عبر الكونسول، علينا اتباع ثلاث خطوات رئيسية:

1. استيراد الكلاس `Scanner`

هذه الخطوة تتيح لنا استخدام الكلاس Scanner في البرنامج. يجب وضع هذا السطر في بداية ملف الجافا الخاص بك، عادةً بعد تعريف الحزمة (`package`) الخاصة بالبرنامج (إن وجدت) وقبل تعريف الكلاس الرئيسي (`public class`).

import java.util.Scanner;

2. إنشاء كائن من الكلاس `Scanner`

نحن بحاجة لإنشاء كائن (Object) من الكلاس Scanner ليكون مسؤولاً عن استقبال المدخلات. نمرر System.in كمعامل لكونستركتور Scanner، والذي يعني أن مصدر الإدخال سيكون لوحة المفاتيح (الكونسول).

Scanner input = new Scanner(System.in);
// هنا أنشأنا كائن باسم 'input' (يمكنك اختيار أي اسم) من الكلاس Scanner،
// ليكون مسؤولاً عن استقبال المدخلات من لوحة المفاتيح عبر مجرى الإدخال القياسي (System.in).

3. استخدام إحدى دوال `Scanner` لإدخال البيانات

بعد إنشاء الكائن، يمكننا استدعاء إحدى دوال الإدخال المتاحة في الكلاس Scanner لاستقبال النوع المحدد من البيانات من المستخدم. على سبيل المثال:

System.out.print("من فضلك أدخل عمرك: "); // نطبع رسالة إرشادية للمستخدم
int age = input.nextInt(); // هذا الكود يجعل البرنامج ينتظر من المستخدم إدخال عدد صحيح، ثم يخزن هذه القيمة في المتغير age.
ملاحظات مهمة عند استخدام `Scanner`:
  • **إغلاق `Scanner`:** من الضروري جدًا إغلاق كائن Scanner بعد الانتهاء من استخدامه لتحرير الموارد التي تم حجزها. يتم ذلك باستخدام الدالة input.close();.
  • **مشكلة `nextInt()` و `nextLine()`:** عند استخدام `nextInt()` (أو `nextDouble()`, `nextBoolean()`، إلخ) متبوعة مباشرة بـ `nextLine()`، قد تواجه مشكلة. السبب هو أن `nextInt()` تقرأ العدد ولكنها لا تستهلك محرف السطر الجديد (`\n`) الذي يضغط عليه المستخدم. عندما تستدعي `nextLine()` بعد ذلك، فإنها تقرأ هذا السطر الجديد المتبقي وتعتبره مدخلًا فارغًا.
    الحل: أضف input.nextLine(); فارغة بعد كل قراءة لعدد وقبل قراءة سطر كامل لتستهلك المحرف العالق.
  • **معالجة الأخطاء:** من الأفضل دائمًا وضع دوال الإدخال داخل كتلة `try-catch` لمعالجة أي أخطاء محتملة قد تحدث إذا أدخل المستخدم بيانات من نوع غير متوقع (مثل إدخال نص بدلاً من رقم)، مما يمنع البرنامج من الانهيار. سنتعمق في معالجة الاستثناءات في وحدات لاحقة.
---

أمثلة شاملة حول إدخال البيانات (Console)

المثال الأول: برنامج يطلب من المستخدم إدخال اسمه، عمره، ومهنته، ثم يعرض هذه البيانات على الشاشة.

هنا سنستخدم دوال مثل nextLine() و nextInt() من الكلاس Scanner.

import java.util.Scanner; // استيراد الكلاس Scanner

public class UserDataCollector {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in); // إنشاء كائن Scanner

        System.out.print("أدخل اسمك: "); // طلب الاسم
        String name = input.nextLine(); // استقبال سطر كامل (الاسم)

        System.out.print("أدخل عمرك: "); // طلب العمر
        int age = input.nextInt(); // استقبال عدد صحيح (العمر)

        // تنبيه: nextInt() لا تستهلك السطر الجديد، مما قد يؤثر على nextLine() اللاحقة
        // لذا، نضيف input.nextLine() إضافية لامتصاص السطر الجديد العالق
        input.nextLine(); 

        System.out.print("أدخل مهنتك: "); // طلب المهنة
        String profession = input.nextLine(); // استقبال سطر كامل (المهنة)

        System.out.println("\n--- بيانات المستخدم ---");
        System.out.println("الاسم: " + name);
        System.out.println("العمر: " + age + " سنة");
        System.out.println("المهنة: " + profession);

        input.close(); // إغلاق كائن Scanner
    }
}

المثال الثاني: برنامج يطلب من المستخدم إدخال علاماته المدرسية، ثم يخزنها داخل مصفوفة (Array).

هذا يتطلب معرفة بالمصفوفات (سيتم تغطيتها بتفصيل في وحدات لاحقة)، ولكن هذا مثال يوضح إمكانية إدخال عدة قيم.

import java.util.Scanner; // استيراد الكلاس Scanner

public class StudentGrades {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in); // إنشاء كائن Scanner

        System.out.print("كم عدد المواد الدراسية؟ ");
        int numSubjects = input.nextInt();
        input.nextLine(); // امتصاص السطر الجديد

        // تعريف مصفوفة لتخزين الدرجات
        double[] grades = new double[numSubjects];

        System.out.println("الرجاء إدخال علامات المواد:");
        for (int i = 0; i < numSubjects; i++) {
            System.out.print("علامة المادة " + (i+1) + ": ");
            grades[i] = input.nextDouble();
            input.nextLine(); // امتصاص السطر الجديد
        }

        // حساب المعدل
        double sum = 0;
        for (double grade : grades) {
            sum += grade;
        }
        double average = sum / numSubjects;

        System.out.println("\n--- النتائج ---");
        System.out.println("عدد المواد: " + numSubjects);
        System.out.println("المعدل: " + average);

        input.close(); // إغلاق كائن Scanner
    }
}

المثال الثالث: برنامج لتحويل درجة الحرارة من فهرنهايت إلى مئوية

هذا المثال يوضح كيفية أخذ مدخلات عددية وإجراء عمليات حسابية عليها:

import java.util.Scanner;

public class TemperatureConverter {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        
        System.out.print("أدخل درجة الحرارة بالفهرنهايت: ");
        double fahrenheit = input.nextDouble();
        
        double celsius = (fahrenheit - 32) * 5.0/9.0; // استخدم 5.0/9.0 لضمان القسمة العشرية
        
        System.out.println(fahrenheit + " فهرنهايت = " + celsius + " مئوية");
        
        input.close();
    }
}
---

كونستركتورات الكلاس Scanner

يحتوي الكلاس Scanner على عدة كونستركتورات (Constructors) تسمح بإنشاء كائنات `Scanner` لقراءة المدخلات من مصادر مختلفة. الكونستركتور هو دالة خاصة تُستخدم لإنشاء كائن جديد من الكلاس.

الكونستركتور الوصف
Scanner(File source) يقرأ البيانات من ملف. يتطلب استيراد java.io.File وقد يرمي استثناء FileNotFoundException.
Scanner(InputStream source) يقرأ البيانات من مصدر إدخال مباشر (مثل System.in، وهو الأكثر شيوعاً لقراءة مدخلات الكونسول).
Scanner(Readable source) يقرأ البيانات من مصدر قابل للقراءة (مثل BufferedReader).
Scanner(ReadableByteChannel source) يقرأ البيانات من قناة بايت قابلة للقراءة، مفيد لعمليات الإدخال/الإخراج المعقدة.
Scanner(String source) يقرأ البيانات من سلسلة نصية محددة. مفيد لتحليل النصوص الثابتة.
Scanner(Path source) يقرأ البيانات من ملف محدد بواسطة مسار (`Path`). يتطلب استيراد java.nio.file.Path.
Scanner(File source, String charsetName) يقرأ البيانات من ملف مع تحديد ترميز الأحرف.
مثال على استخدام كونستركتور الملف (`Scanner(File source)`):
import java.io.File;
import java.io.FileNotFoundException; // يجب استيراد هذا الاستثناء
import java.util.Scanner;

public class FileReaderExample {
    public static void main(String[] args) {
        try {
            // قم بإنشاء ملف باسم data.txt في نفس مجلد المشروع وضع فيه بعض النصوص.
            // مثال:
            // السطر الأول من البيانات
            // السطر الثاني من البيانات
            Scanner fileScanner = new Scanner(new File("data.txt"));
            
            System.out.println("قراءة محتويات الملف:");
            while (fileScanner.hasNextLine()) { // التحقق مما إذا كان هناك سطر آخر للقراءة
                System.out.println(fileScanner.nextLine()); // قراءة وطباعة السطر
            }
            
            fileScanner.close(); // إغلاق كائن Scanner
        } catch (FileNotFoundException e) {
            System.err.println("خطأ: الملف data.txt غير موجود."); // طباعة رسالة خطأ
        }
    }
}
---

أهم دوال الإدخال في الكلاس Scanner

بعد إنشاء كائن `Scanner`، يمكنك استخدام هذه الدوال لقراءة أنواع مختلفة من البيانات:

دوال قراءة البيانات الأساسية (`nextType()`):

الدالة الوصف مثال
nextInt() تقرأ العدد الصحيح التالي (بنوع `int`). int age = input.nextInt();
nextDouble() تقرأ العدد العشري التالي (بنوع `double`). double price = input.nextDouble();
nextFloat() تقرأ العدد العشري التالي (بنوع `float`). float temp = input.nextFloat();
nextLong() تقرأ العدد الصحيح الطويل التالي (بنوع `long`). long population = input.nextLong();
nextShort() تقرأ العدد الصحيح القصير التالي (بنوع `short`). short code = input.nextShort();
nextByte() تقرأ البايت التالي (بنوع `byte`). byte data = input.nextByte();
nextBoolean() تقرأ القيمة المنطقية التالية (`true` أو `false`). boolean flag = input.nextBoolean();
next() تقرأ الكلمة التالية (سلسلة نصية `String`) حتى أول مسافة بيضاء أو فاصل. String word = input.next();
nextLine() تقرأ السطر بأكمله (سلسلة نصية `String`) حتى محرف السطر الجديد (`\n`). String line = input.nextLine();
الفرق بين `next()` و `nextLine()`:

هذا فرق جوهري ويسبب التباسًا للكثيرين:

  • next(): تقرأ **كلمة واحدة** فقط. تتوقف عن القراءة عند أول مسافة بيضاء (` `، `\t`، `\n`).
  • nextLine(): تقرأ **السطر بأكمله**. تستمر في القراءة حتى تجد محرف السطر الجديد (`\n`).

تذكر دائماً استخدام input.nextLine(); فارغة بعد قراءة الأرقام أو الكلمات الفردية إذا كنت تخطط لقراءة سطر كامل بعدها، لتجنب قراءة المحرف العالق.

---

دوال البحث (Searching Methods - `hasNextType()`)

هذه الدوال تسمح لك بالتحقق مما إذا كان هناك بيانات من نوع معين متاحة للقراءة في مجرى الإدخال *قبل* محاولة قراءتها. تُرجع هذه الدوال `true` إذا كان النوع المتوقع متاحًا، و `false` بخلاف ذلك. هذا يساعد على منع أخطاء إدخال النوع غير المتطابق (`InputMismatchException`).

الدالة الوصف مثال
hasNext() تتحقق مما إذا كان هناك المزيد من الـ "رموز" (tokens) المتاحة للقراءة (بناءً على الفاصل الحالي). if (input.hasNext()) { // ... }
hasNextInt() تتحقق مما إذا كان الرمز التالي يمكن تفسيره كعدد صحيح (`int`). if (input.hasNextInt()) { // ... }
hasNextDouble() تتحقق مما إذا كان الرمز التالي يمكن تفسيره كعدد عشري (`double`). if (input.hasNextDouble()) { // ... }
hasNextLine() تتحقق مما إذا كان هناك سطر آخر متاح للقراءة. if (input.hasNextLine()) { // ... }
hasNextBoolean() تتحقق مما إذا كان الرمز التالي يمكن تفسيره كقيمة منطقية (`boolean`). if (input.hasNextBoolean()) { // ... }
مثال على استخدام دوال البحث للتحقق من النوع:
import java.util.Scanner;

public class InputValidationExample {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("أدخل عددًا صحيحًا: ");

        if (input.hasNextInt()) { // التحقق قبل القراءة
            int number = input.nextInt();
            System.out.println("لقد أدخلت عددًا صحيحًا: " + number);
        } else {
            System.out.println("خطأ: لم تدخل عددًا صحيحًا!");
        }

        input.close();
    }
}
---

دوال معالجة وسلوك `Scanner`

هذه الدوال تسمح لك بتعديل سلوك الـ `Scanner` أو الحصول على معلومات حول حالته:

الدالة الوصف مثال
ioException() تُرجع آخر استثناء `IOException` حدث أثناء عمليات الإدخال. IOException e = input.ioException();
radix() تُرجع أساس نظام الأعداد (Base) الذي يستخدمه `Scanner` لتفسير الأرقام (الافتراضي هو 10 للنظام العشري). int currentRadix = input.radix(); // غالباً سيكون 10
useRadix(int radix) تحدد أساس نظام الأعداد الذي سيستخدمه `Scanner` لتفسير الأرقام. (مثال: 16 للنظام الست عشري). input.useRadix(16); // لتمكين قراءة الأرقام الست عشرية
delimiter() تُرجع نمط الفاصل (`Pattern`) الذي يستخدمه `Scanner` حاليًا للفصل بين الرموز. Pattern p = input.delimiter();
useDelimiter(String pattern) تحدد نمط فاصل جديد سيستخدمه `Scanner` للفصل بين الرموز بدلاً من المسافات البيضاء الافتراضية. input.useDelimiter(","); // استخدام الفاصلة كفاصل
locale() تُرجع الإعدادات المحلية (`Locale`) التي يستخدمها `Scanner` (تؤثر على كيفية قراءة الأرقام العشرية والتاريخ والوقت). Locale currentLocale = input.locale();
useLocale(Locale locale) تحدد إعدادات محلية جديدة لـ `Scanner`. input.useLocale(Locale.US); // استخدام الإعدادات المحلية الأمريكية
skip(String pattern) تتخطى أي مدخلات مطابقة للنمط المحدد. input.skip("\\s+"); // تخطي المسافات البيضاء المتتالية
close() يغلق كائن `Scanner` ويحرر أي موارد نظام مرتبطة به. **مهم جدًا استدعائها عند الانتهاء.** input.close();
مثال على استخدام الفواصل المخصصة (`useDelimiter()`):
import java.util.Scanner;

public class CustomDelimiterExample {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        input.useDelimiter(","); // تعيين الفاصلة كفاصل بين المدخلات بدلاً من المسافة
        
        System.out.println("أدخل 3 أرقام صحيحة مفصولة بفواصل (مثال: 10,20,30): ");
        try {
            int a = input.nextInt();
            int b = input.nextInt();
            int c = input.nextInt();
            
            System.out.println("الأرقام المدخلة: " + a + ", " + b + ", " + c);
            System.out.println("المجموع: " + (a + b + c));
        } catch (java.util.InputMismatchException e) {
            System.err.println("خطأ في الإدخال: تأكد من إدخال أرقام صحيحة مفصولة بفواصل.");
        } finally {
            input.close(); // يجب إغلاق الـ Scanner دائماً
        }
    }
}
---

مقدمة إلى JOptionPane

بعد أن تعلمنا كيفية التفاعل مع المستخدم عبر الكونسول باستخدام `Scanner`، لننتقل الآن إلى طريقة أخرى أكثر جاذبية وتفاعلية: **النوافذ الرسومية المنبثقة (Pop-up windows)** باستخدام فئة `JOptionPane`. هذه الطريقة تمنح برامجك واجهة مستخدم رسومية بسيطة (GUI) بدلاً من الشاشة النصية السوداء.

`JOptionPane` (Java Option Pane) هي جزء من مكتبة Swing في جافا، وتوفر طرقًا جاهزة وسهلة الاستخدام لإنشاء نوافذ حوار قياسية (مثل نوافذ الرسائل، نوافذ الإدخال، نوافذ التأكيد) التي تظهر للمستخدم.

  • **عرض الرسائل:** إعلام المستخدم بشيء ما (مثل خطأ، نجاح عملية، معلومات).
  • **أخذ المدخلات:** مطالبة المستخدم بإدخال نص أو رقم.
  • **الحصول على تأكيد:** سؤال المستخدم عن قراره (نعم/لا/إلغاء).
ملاحظة هامة:

لكي تتمكن من استخدام `JOptionPane` في برنامجك، يجب عليك استيرادها في بداية الملف. يتم ذلك بإضافة السطر التالي:

import javax.swing.JOptionPane;

هذا السطر يخبر مترجم الجافا بأنك ستستخدم الفئات الموجودة داخل حزمة `javax.swing.JOptionPane`.

---

عرض الرسائل: `showMessageDialog()`

تُستخدم دالة `showMessageDialog()` لعرض رسالة بسيطة للمستخدم في نافذة منبثقة. يمكنك التحكم في محتوى الرسالة، عنوان النافذة، ونوع الأيقونة التي تظهر.

الصيغة العامة:

JOptionPane.showMessageDialog(Component parentComponent, Object message, String title, int messageType);
  • `parentComponent` (اختياري): يحدد المكون الأب الذي ستظهر فوقه النافذة. غالبًا ما نستخدم null لكي تظهر النافذة في منتصف الشاشة.
  • `message` (إلزامي): الرسالة التي تريد عرضها. يمكن أن تكون نصًا (String) أو أي كائن آخر.
  • `title` (إلزامي): النص الذي سيظهر كعنوان للنافذة المنبثقة.
  • `messageType` (اختياري): يحدد نوع الأيقونة التي ستظهر بجانب الرسالة ومستوى الرسالة. القيم الشائعة هي:
    • JOptionPane.INFORMATION_MESSAGE: أيقونة معلومات (دائرة زرقاء مع حرف i).
    • JOptionPane.WARNING_MESSAGE: أيقونة تحذير (مثلث أصفر مع علامة تعجب).
    • JOptionPane.ERROR_MESSAGE: أيقونة خطأ (دائرة حمراء مع X).
    • JOptionPane.QUESTION_MESSAGE: أيقونة سؤال (دائرة زرقاء مع علامة استفهام).
    • JOptionPane.PLAIN_MESSAGE: لا توجد أيقونة.

أمثلة على `showMessageDialog()`:

import javax.swing.JOptionPane;

public class ShowMessageExamples {
    public static void main(String[] args) {
        // 1. رسالة معلومات بسيطة
        JOptionPane.showMessageDialog(null, "تمت العملية بنجاح!", "إشعار", JOptionPane.INFORMATION_MESSAGE);

        // 2. رسالة تحذير
        JOptionPane.showMessageDialog(null, "تحذير: لا تتوفر مساحة كافية على القرص.", "تحذير النظام", JOptionPane.WARNING_MESSAGE);

        // 3. رسالة خطأ
        JOptionPane.showMessageDialog(null, "خطأ: اسم المستخدم أو كلمة المرور غير صحيحة.", "خطأ في تسجيل الدخول", JOptionPane.ERROR_MESSAGE);

        // 4. رسالة بدون أيقونة (Plain message)
        JOptionPane.showMessageDialog(null, "هذه رسالة نصية عادية بدون أيقونة.", "رسالة عادية", JOptionPane.PLAIN_MESSAGE);
    }
}
---

أخذ المدخلات: `showInputDialog()`

تُستخدم دالة `showInputDialog()` لطلب نص أو قيمة من المستخدم عبر نافذة حوار. يتم عرض مربع نص يمكن للمستخدم الكتابة فيه، وعند الضغط على "OK"، تُرجع الدالة النص الذي أدخله المستخدم كقيمة من نوع String.

الصيغة العامة:

String userInput = JOptionPane.showInputDialog(Object message);
// أو
String userInput = JOptionPane.showInputDialog(Component parentComponent, Object message, String title, int messageType);
  • `message` (إلزامي): الرسالة التي تظهر للمستخدم لإرشاده حول ما يجب إدخاله.
  • **القيمة المرجعة:** تُرجع هذه الدالة دائمًا String يحتوي على النص الذي أدخله المستخدم. إذا ضغط المستخدم على "Cancel" أو أغلق النافذة، فإنها تُرجع null.
مهم جدًا:

دائمًا ما تُرجع `showInputDialog()` قيمة نصية (`String`). إذا كنت تتوقع من المستخدم إدخال رقم (عدد صحيح أو عشري)، فيجب عليك تحويل هذا النص إلى النوع الرقمي المناسب قبل إجراء أي عمليات حسابية عليه. سنتناول ذلك بالتفصيل في القسم التالي.

أمثلة على `showInputDialog()`:

import javax.swing.JOptionPane;

public class ShowInputExamples {
    public static void main(String[] args) {
        // 1. طلب اسم المستخدم
        String userName = JOptionPane.showInputDialog("من فضلك، أدخل اسمك:");
        if (userName != null && !userName.isEmpty()) { // التحقق من أن المستخدم لم يضغط Cancel وأن النص ليس فارغًا
            JOptionPane.showMessageDialog(null, "أهلاً بك يا: " + userName, "ترحيب", JOptionPane.INFORMATION_MESSAGE);
        } else {
            JOptionPane.showMessageDialog(null, "لم يتم إدخال الاسم.", "خطأ", JOptionPane.WARNING_MESSAGE);
        }

        // 2. طلب رقم هاتف
        String phoneNumber = JOptionPane.showInputDialog("أدخل رقم هاتفك:");
        if (phoneNumber != null && !phoneNumber.isEmpty()) {
            JOptionPane.showMessageDialog(null, "رقم هاتفك هو: " + phoneNumber, "معلومات", JOptionPane.PLAIN_MESSAGE);
        }
    }
}
---

تحويل أنواع البيانات (Data Conversion)

كما ذكرنا، `showInputDialog()` تُرجع دائمًا `String` (نص). لكن برامجنا غالبًا ما تحتاج إلى التعامل مع الأرقام لإجراء عمليات حسابية. لذلك، يجب علينا تحويل `String` الذي تم إدخاله إلى نوع رقمي (مثل `int` أو `double`).

تُسمى عملية تحويل نوع بيانات إلى نوع آخر بـ **"تحويل النوع" (Type Casting)** أو **"تحويل البيانات" (Data Conversion)**. في جافا، نستخدم فئات مساعدة (Wrapper Classes) لهذا الغرض.

1. تحويل `String` إلى `int`

لتحويل نص يمثل عددًا صحيحًا إلى نوع int، نستخدم الدالة الثابتة parseInt() من فئة Integer.

import javax.swing.JOptionPane;

public class StringToIntConversion {
    public static void main(String[] args) {
        String ageInput = JOptionPane.showInputDialog("أدخل عمرك كرقم:");
        
        try {
            int age = Integer.parseInt(ageInput); // هنا يتم التحويل
            JOptionPane.showMessageDialog(null, "عمرك هو: " + age + " سنة.", "العمر", JOptionPane.INFORMATION_MESSAGE);
        } catch (NumberFormatException e) {
            JOptionPane.showMessageDialog(null, "إدخال غير صالح. من فضلك أدخل رقماً صحيحاً.", "خطأ في الإدخال", JOptionPane.ERROR_MESSAGE);
        }
    }
}

2. تحويل `String` إلى `double`

لتحويل نص يمثل عددًا عشريًا إلى نوع double، نستخدم الدالة الثابتة parseDouble() من فئة Double.

import javax.swing.JOptionPane;

public class StringToDoubleConversion {
    public static void main(String[] args) {
        String priceInput = JOptionPane.showInputDialog("أدخل سعر المنتج كرقم عشري:");
        
        try {
            double price = Double.parseDouble(priceInput); // هنا يتم التحويل
            JOptionPane.showMessageDialog(null, "سعر المنتج: " + price + " ريال.", "السعر", JOptionPane.INFORMATION_MESSAGE);
        } catch (NumberFormatException e) {
            JOptionPane.showMessageDialog(null, "إدخال غير صالح. من فضلك أدخل رقماً عشرياً.", "خطأ في الإدخال", JOptionPane.ERROR_MESSAGE);
        }
    }
}
معالجة الأخطاء (Error Handling):

كما رأينا في الأمثلة أعلاه، من المهم جدًا استخدام `try-catch` عند محاولة تحويل النصوص إلى أرقام. إذا أدخل المستخدم نصًا لا يمكن تحويله إلى رقم (مثل "hello")، فستحدث مشكلة تسمى NumberFormatException. استخدام `try-catch` يسمح لبرنامجك بالتعامل مع هذا الخطأ بشكل رشيق بدلاً من الانهيار.

---

تطبيقات عملية (JOptionPane)

1. برنامج حاسبة بسيطة

لنقم بإنشاء برنامج حاسبة بسيطة تأخذ رقمين من المستخدم وتحسب مجموعهما وتُظهره في نافذة رسالة.

import javax.swing.JOptionPane;

public class SimpleCalculatorGUI {
    public static void main(String[] args) {
        // 1. طلب الرقم الأول كنص
        String num1String = JOptionPane.showInputDialog(null, "أدخل الرقم الأول:", "حاسبة الجمع", JOptionPane.QUESTION_MESSAGE);
        
        // 2. طلب الرقم الثاني كنص
        String num2String = JOptionPane.showInputDialog(null, "أدخل الرقم الثاني:", "حاسبة الجمع", JOptionPane.QUESTION_MESSAGE);
        
        // التحقق من أن المستخدم لم يضغط "Cancel" أو ترك الحقول فارغة
        if (num1String != null && !num1String.isEmpty() && num2String != null && !num2String.isEmpty()) {
            try {
                // 3. تحويل النصوص إلى أرقام عشرية (double)
                double num1 = Double.parseDouble(num1String);
                double num2 = Double.parseDouble(num2String);
                
                // 4. إجراء عملية الجمع
                double sum = num1 + num2;
                
                // 5. عرض النتيجة للمستخدم
                JOptionPane.showMessageDialog(null, "المجموع هو: " + sum, "النتيجة", JOptionPane.INFORMATION_MESSAGE);
            } catch (NumberFormatException e) {
                // معالجة خطأ إذا أدخل المستخدم نصًا بدلاً من رقم
                JOptionPane.showMessageDialog(null, "خطأ في الإدخال: من فضلك أدخل أرقامًا صحيحة أو عشرية.", "خطأ", JOptionPane.ERROR_MESSAGE);
            }
        } else {
            JOptionPane.showMessageDialog(null, "تم إلغاء العملية أو ترك أحد الحقول فارغًا.", "إلغاء", JOptionPane.WARNING_MESSAGE);
        }
    }
}

2. برنامج تحويل درجة الحرارة (مئوي إلى فهرنهايت)

برنامج يأخذ درجة الحرارة بالمئوية من المستخدم ويحولها إلى فهرنهايت.

import javax.swing.JOptionPane;

public class TempConverterGUI {
    public static void main(String[] args) {
        String celsiusString = JOptionPane.showInputDialog(null, "أدخل درجة الحرارة بالمئوي (°C):", "تحويل الحرارة", JOptionPane.QUESTION_MESSAGE);
        
        if (celsiusString != null && !celsiusString.isEmpty()) {
            try {
                double celsius = Double.parseDouble(celsiusString);
                // معادلة التحويل من مئوي إلى فهرنهايت: F = (C * 9/5) + 32
                double fahrenheit = (celsius * 9.0/5.0) + 32; // استخدم 9.0/5.0 لضمان القسمة العشرية
                
                JOptionPane.showMessageDialog(null, celsius + "°C يساوي " + fahrenheit + "°F", "النتيجة", JOptionPane.INFORMATION_MESSAGE);
            } catch (NumberFormatException e) {
                JOptionPane.showMessageDialog(null, "إدخال غير صالح. من فضلك أدخل رقماً لدرجة الحرارة.", "خطأ", JOptionPane.ERROR_MESSAGE);
            }
        } else {
            JOptionPane.showMessageDialog(null, "تم إلغاء عملية التحويل.", "إلغاء", JOptionPane.WARNING_MESSAGE);
        }
    }
}
---

الأخطاء الشائعة وحلولها

عند استخدام `Scanner` أو `JOptionPane`، قد تواجه بعض الأخطاء الشائعة. إليك أبرزها وكيفية التعامل معها:

1. `NullPointerException` عند إغلاق النافذة أو الضغط على "Cancel" (خاص بـ `JOptionPane`)

إذا ضغط المستخدم على زر "Cancel" أو أغلق نافذة `showInputDialog()`، فإن الدالة تُرجع قيمة `null` (أي لا شيء). إذا حاولت استخدام هذه القيمة `null` مباشرة (مثلاً لتحويلها إلى رقم)، فسيحدث خطأ `NullPointerException`.

String userInput = JOptionPane.showInputDialog("أدخل شيئًا:");
// int number = Integer.parseInt(userInput); // هذا سيسبب NullPointerException إذا كان userInput هو null

// الحل: تحقق دائمًا مما إذا كانت القيمة المرجعة ليست null قبل استخدامها
if (userInput != null) {
    // يمكنك الآن معالجة userInput بأمان
    System.out.println("المدخلات: " + userInput);
} else {
    System.out.println("المستخدم ألغى الإدخال.");
}

2. `NumberFormatException` عند تحويل نص غير رقمي (خاص بـ `JOptionPane`)

يحدث هذا الخطأ عندما تحاول تحويل نص إلى رقم باستخدام `Integer.parseInt()` أو `Double.parseDouble()`، ولكن النص لا يمثل رقمًا صالحًا (مثل إدخال "hello" بدلاً من "123").

String numberString = JOptionPane.showInputDialog("أدخل رقمًا:");
// int number = Integer.parseInt(numberString); // هذا سيسبب NumberFormatException إذا أدخل المستخدم نصًا

// الحل: استخدم try-catch block لمعالجة هذا الاستثناء (Exception)
try {
    int number = Integer.parseInt(numberString);
    System.out.println("الرقم المدخل: " + number);
} catch (NumberFormatException e) {
    JOptionPane.showMessageDialog(null, "خطأ: المدخلات ليست رقمًا صالحًا.", "خطأ في التحويل", JOptionPane.ERROR_MESSAGE);
}

3. `InputMismatchException` عند إدخال نوع بيانات خاطئ (خاص بـ `Scanner`)

يحدث هذا الخطأ عندما تحاول قراءة نوع بيانات معين باستخدام `Scanner` (مثلاً `nextInt()`)، ولكن المستخدم يدخل نوعًا آخر (مثلاً نص). على عكس `JOptionPane` حيث يجب التحويل يدوياً، `Scanner` يحاول تفسير المدخلات مباشرة.

import java.util.InputMismatchException; // استيراد الاستثناء
import java.util.Scanner;

public class InputMismatchExample {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.print("أدخل عددًا صحيحًا: ");

        try {
            int number = input.nextInt(); // محاولة قراءة عدد صحيح
            System.out.println("لقد أدخلت: " + number);
        } catch (InputMismatchException e) {
            System.err.println("خطأ في الإدخال: من فضلك أدخل عددًا صحيحًا.");
            input.nextLine(); // مهم لامتصاص المدخلات الخاطئة وتجنب حلقات لا نهائية في بعض الحالات
        } finally {
            input.close();
        }
    }
}

4. عدم استيراد المكتبة المطلوبة

إذا نسيت إضافة سطر الاستيراد اللازم (`import java.util.Scanner;` أو `import javax.swing.JOptionPane;`) في بداية ملف الجافا الخاص بك، فستحصل على خطأ في الترجمة يشير إلى أن الكلاس المطلوب لا يمكن العثور عليه.

// import java.util.Scanner; // إذا لم تكتب هذا السطر
// import javax.swing.JOptionPane; // أو هذا السطر
public class MyProgram {
    public static void main(String[] args) {
        // استخدام أي من الكلاسين هنا سيعطي خطأ في الترجمة
        // Scanner input = new Scanner(System.in); 
        // JOptionPane.showMessageDialog(null, "مرحبًا"); 
    }
}

الحل: أضف سطر الاستيراد المناسب في بداية الملف: import java.util.Scanner; و/أو import javax.swing.JOptionPane;.

---

تمارين وتحديات

1. تمرين: نافذة ترحيب مخصصة (باستخدام `JOptionPane`)

اكتب برنامج جافا يستخدم `JOptionPane` لطلب اسم المستخدم، ثم يعرض رسالة ترحيب مخصصة في نافذة رسالة (مثلاً: "أهلاً بك يا [اسم المستخدم] في برنامجنا الأول!"). تأكد من معالجة حالة إذا لم يدخل المستخدم أي اسم (أي إذا ضغط Cancel أو أدخل نصًا فارغًا).

import javax.swing.JOptionPane;

public class CustomWelcomeDialog {
    public static void main(String[] args) {
        // اكتب الكود هنا لطلب الاسم وعرض رسالة الترحيب
        // تلميح: استخدم if (userName != null && !userName.isEmpty())
    }
}

2. تمرين: حساب مؤشر كتلة الجسم (BMI) (باستخدام `Scanner`)

صمم برنامج جافا يأخذ وزن المستخدم (بالكيلوجرام) وطوله (بالمتر) كمدخلات عبر **الكونسول** باستخدام `Scanner`. احسب مؤشر كتلة الجسم (BMI) باستخدام المعادلة: $BMI = \frac{\text{الوزن (كجم)}}{\text{الطول (م)}^2}$. ثم اعرض نتيجة الـ BMI على الكونسول. تأكد من معالجة الأخطاء المحتملة إذا أدخل المستخدم قيمًا غير رقمية.

import java.util.Scanner;
import java.util.InputMismatchException;

public class BMICalculatorConsole {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        // اكتب الكود هنا لطلب الوزن والطول، حساب BMI وعرضه
        // تلميح: استخدم try-catch و hasNextDouble()
    }
}

3. تمرين تحدي: تحويل العملات (دمج `Scanner` و `JOptionPane`)

اكتب برنامج جافا يطلب من المستخدم:

  1. مبلغًا بالدولار عبر `JOptionPane`.
  2. سعر صرف الدولار مقابل عملة معينة (مثلاً الريال السعودي) عبر **الكونسول** باستخدام `Scanner`.
قم بتحويل المبلغ واعرض الناتج في نافذة رسالة باستخدام `JOptionPane`. يجب أن يقوم البرنامج بالتحقق من صحة المدخلات (أنها أرقام)، وأن يتعامل مع أي أخطاء محتملة في كلا حالتي الإدخال.

import javax.swing.JOptionPane;
import java.util.Scanner;
import java.util.InputMismatchException;

public class CurrencyConverterHybrid {
    public static void main(String[] args) {
        Scanner consoleInput = new Scanner(System.in);

        // الجزء الأول: إدخال المبلغ بالدولار باستخدام JOptionPane
        String dollarAmountStr = JOptionPane.showInputDialog(null, "أدخل المبلغ بالدولار:", "تحويل العملة", JOptionPane.QUESTION_MESSAGE);
        double dollarAmount = 0.0;
        boolean dollarInputValid = false;

        if (dollarAmountStr != null && !dollarAmountStr.isEmpty()) {
            try {
                dollarAmount = Double.parseDouble(dollarAmountStr);
                dollarInputValid = true;
            } catch (NumberFormatException e) {
                JOptionPane.showMessageDialog(null, "خطأ: المبلغ المدخل بالدولار ليس رقمًا صالحًا.", "خطأ في الإدخال", JOptionPane.ERROR_MESSAGE);
            }
        } else {
            JOptionPane.showMessageDialog(null, "تم إلغاء إدخال المبلغ بالدولار.", "إلغاء", JOptionPane.WARNING_MESSAGE);
        }

        // الجزء الثاني: إدخال سعر الصرف باستخدام Scanner (الكونسول)
        double exchangeRate = 0.0;
        boolean rateInputValid = false;

        if (dollarInputValid) { // فقط إذا كان إدخال الدولار صحيحاً
            System.out.print("أدخل سعر صرف الدولار مقابل عملتك المحلية (مثال: 3.75 للريال السعودي): ");
            try {
                exchangeRate = consoleInput.nextDouble();
                rateInputValid = true;
            } catch (InputMismatchException e) {
                System.err.println("خطأ في الإدخال: سعر الصرف المدخل ليس رقمًا صالحًا.");
                consoleInput.nextLine(); // امتصاص المدخلات الخاطئة
            }
        }
        consoleInput.close(); // إغلاق Scanner عند الانتهاء

        // الجزء الثالث: حساب وعرض النتيجة
        if (dollarInputValid && rateInputValid) {
            double convertedAmount = dollarAmount * exchangeRate;
            JOptionPane.showMessageDialog(null,
                String.format("%.2f دولار يساوي %.2f عملة محلية.", dollarAmount, convertedAmount),
                "نتيجة التحويل",
                JOptionPane.INFORMATION_MESSAGE);
        } else {
            JOptionPane.showMessageDialog(null, "لا يمكن إتمام التحويل بسبب مدخلات غير صحيحة.", "تحويل غير مكتمل", JOptionPane.WARNING_MESSAGE);
        }
    }
}
---

المراجع

  • موقع Oracle Java Documentation.
  • "Java: A Beginner's Guide" – Herbert Schildt.
  • "Head First Java" – Kathy Sierra, Bert Bates.
---

ملخص الوحدة

في هذه الوحدة الأساسية، اكتسبت مهارات حاسمة في **التفاعل مع المستخدم** في برامج جافا. لقد استكشفنا طريقتين رئيسيتين للتعامل مع المدخلات والمخرجات:

  • **المدخلات والمخرجات عبر الكونسول (`Scanner`):** تعلمت كيفية استخدام الكلاس `Scanner` لقراءة أنواع مختلفة من البيانات (أرقام، نصوص) من لوحة المفاتيح عبر سطر الأوامر. تعرفت على الدوال المختلفة مثل `nextInt()`, `nextLine()`, `hasNextInt()`، وأهمية إغلاق كائن `Scanner` وحل مشكلة `nextLine()` بعد قراءة الأرقام.
  • **المدخلات والمخرجات عبر الواجهات الرسومية البسيطة (`JOptionPane`):** استعرضنا كيفية استخدام `JOptionPane` لعرض رسائل جذابة (`showMessageDialog()`) وجمع المدخلات النصية من المستخدم في نوافذ منبثقة (`showInputDialog()`). والأهم من ذلك، تعلمت كيفية تحويل هذه المدخلات النصية إلى أرقام، وكيفية التعامل مع الأخطاء الشائعة في كلا الطريقتين.

بامتلاكك لهذه المهارات، يمكنك الآن بناء برامج جافا أكثر ديناميكية وتفاعلية، قادرة على استقبال البيانات من المستخدم والاستجابة لها. في الوحدة القادمة، سنتعمق في **الجمل الشرطية (`if-else`)**، وهي أدوات أساسية لجعل برامجك تتخذ القرارات وتنفذ مسارات مختلفة بناءً على شروط محددة.