القائمة الرئيسية

الصفحات

برمجة تطبيق عارض صور باستخدام NetBeans GUI Builder

How to Code NetBeans GUI Builder Image Viewer App، Java GUI Enhancements، NetBeans Swing ProgressBar، JMenuBar Java، Thumbnail Viewer Java، Image Zoom Swing، JFileChooser Extensions، Multithreading Java GUI، SwingWorker NetBeans، File Filtering Java، Java Image Viewer، NetBeans GUI Java، Swing Image Display، تطبيقات سطح المكتب Java، عرض الصور في Java، JPanel لعرض الصور، JFileChooser Java، تصميم واجهة رسومية Java، Java Swing، برمجة GUI NetBeans، برمجة واجهات المستخدم الرسومية (GUI) في NetBeans باستخدام Java (مكتبة Swing أو AWT)، Image Viewer، عرض الصور، برمجة واجهات المستخدم الرسومية GUI، NetBeans، باستخدام Java، مكتبة Swing أو AWT، واجهات المستخدم الرسومية (GUI) في NetBeans باستخدام Java مكتبة Swing أو AWT، Java GUI، برمجة تطبيق عارض صور باستخدام NetBeans و Java Swing، Java، إضافة شريط تقدم، تكبير/تصغير، معاينات، قوائم في NetBeans Java GUI،برمجة تطبيق عارض صور باستخدام NetBeans GUI Builder، مكتبة Swing أو AWT، Java Image Viewer، واجهات المستخدم، تطبيقات سطح المكتب،




برمجة تطبيق عارض صور باستخدام NetBeans GUI Builder



تُعد تطبيقات عارض الصور من الأمثلة الكلاسيكية والمفيدة لتعلم برمجة واجهات
 المستخدم الرسومية (GUI) في Java. إنها تجمع بين التعامل مع الملفات،
 ومعالجة الصور، وتحديث الواجهة الرسومية ديناميكياً. يوفر Apache NetBeans IDE، 
بفضل أداة بناء الواجهات الرسومية (GUI Builder) القوية، بيئة 
مثالية لإنشاء مثل هذه التطبيقات بكفاءة وسهولة.
في هذا المقال، سنتعلم كيفية بناء تطبيق عارض صور بسيط باستخدام Java Swing و NetBeans. 
سيسمح التطبيق للمستخدم باختيار مجلد، ثم تصفح الصور الموجودة فيه باستخدام أزرار "التالي" و "السابق".

خطوات برمجة تطبيق عارض صور باستخدام NetBeans GUI Builder


الخطوات الأساسية لبناء التطبيق :

الخطوة 1: إنشاء مشروع Java جديد في NetBeans

* سنبدأ بإنشاء مشروع Java فارغ، افتح NetBeans IDE.
- اذهب إلى File -> New Project....
- اختر Java with Ant (أو Java with Maven إذا كنت تفضل).
اختر Java Application، اضغط Next.
* في نافذة New Java Application:
Project Name: ImageViewerApp
- Project Location: اختر مجلد حفظ المشروع.
- تأكد من تحديد مربع Create Main Class.
- Main Class: com.yourcompany.imageviewerapp.Main 
(يمكنك تغيير yourcompany إلى اسمك أو اسم شركتك) ، اضغط Finish.

الخطوة 2: تصميم واجهة المستخدم الرسومية (GUI) باستخدام NetBeans GUI Builder

الآن سنقوم بتصميم الواجهة الرئيسية للتطبيق.

1* إنشاء JFrame Form جديد:

- في نافذة Projects، انقر بزر الماوس الأيمن على حزمة المصدر
 (com.yourcompany.imageviewerapp).
- اختر New -> JFrame Form....
Class Name: ImageViewerFrame
- Package: com.yourcompany.imageviewerapp (يجب أن تكون موجودة تلقائيًا).
- اضغط Finish.
- سيتم فتح ImageViewerFrame في وضع Design (التصميم).
2* إضافة المكونات إلى الواجهة:

من نافذة Palette (لوحة المكونات) على الجانب الأيمن، اسحب المكونات
- التالية وضعها على الـ JFrame:
- JPanel: هذا سيكون الحاوية الرئيسية لعرض الصورة.
- اسحب JPanel وضعه في منتصف الـ JFrame.
- غيّر Variable Name الخاص به إلى imagePanel.
- من نافذة Properties، ابحث عن خاصية border وانقر على الأيقونة الصغيرة بجانبها. 
اختر TitledBorder وأدخل "Image Display" كنص للحد (اختياري، لكنه مفيد بصريًا).
- JButton (زر لاختيار المجلد):
- اسحب JButton وضعه في الجزء العلوي من الـ JFrame.
- غيّر text الخاص به إلى Choose Folder.
- غيّر Variable Name الخاص به إلى chooseFolderButton.
- JButton (زر السابق):
- اسحب JButton وضعه أسفل imagePanel.
- غيّر text الخاص به إلى Previous.
- غيّر Variable Name الخاص به إلى previousButton.
- JButton (زر التالي):
- اسحب JButton وضعه بجانب previousButton.
- غيّر text الخاص به إلى Next.
- غيّر Variable Name الخاص به إلى nextButton.
* ترتيب المكونات: استخدم خطوط التخطيط الزرقاء التي تظهر في NetBeans لمحاذاة 
المكونات بشكل منظم. يمكنك سحب المكونات وتغيير حجمها لتناسب التصميم.
 تأكد من أن imagePanel كبير بما يكفي لعرض الصور بشكل جيد.

الخطوة 3: إضافة المنطق (Logic) إلى الكود

الآن سننتقل إلى نافذة Source (الكود المصدري) لإضافة المنطق لتطبيقنا.

1* التبديل إلى وضع Source:
انقر على علامة التبويب Source في الجزء العلوي من ImageViewerFrame.java.
2* تعريف المتغيرات اللازمة:
في بداية فئة ImageViewerFrame (بعد تعريف المكونات التي أنشأها
 NetBeans تلقائياً، وقبل Constructor initComponents())،
 أضف المتغيرات التالية:
Java



import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.ImageIcon; // لعرض الصور
import javax.swing.JFileChooser; // لاختيار المجلد
import javax.swing.JLabel; // لعرض الصورة داخل JPanel
import javax.swing.JOptionPane; // لعرض الرسائل
import java.awt.Image; // لتغيير حجم الصورة
import java.awt.Dimension; // للحصول على أبعاد المكون

// ... (المكونات التي تم إنشاؤها تلقائياً بواسطة NetBeans)

public class ImageViewerFrame extends javax.swing.JFrame {

    private List<File> imageFiles; // قائمة بملفات الصور الموجودة في المجلد
    private int currentImageIndex; // مؤشر الصورة الحالية التي يتم عرضها
    private JLabel imageDisplayLabel; // JLabel لعرض الصورة داخل imagePanel

    // Constructor
    public ImageViewerFrame() {
        initComponents();
        imageFiles = new ArrayList<>();
        currentImageIndex = -1; // لا توجد صورة محملة في البداية

        // تهيئة JLabel لعرض الصورة وتوسطه داخل imagePanel
        imageDisplayLabel = new JLabel();
        imageDisplayLabel.setHorizontalAlignment(JLabel.CENTER);
        imagePanel.setLayout(new java.awt.BorderLayout()); // استخدام BorderLayout لـ imagePanel
        imagePanel.add(imageDisplayLabel, java.awt.BorderLayout.CENTER);
    }

    // ... (بقية الكود الذي أنشأه NetBeans)


--

3* تنفيذ وظيفة زر Choose Folder:
-ارجع إلى وضع Design.
- انقر نقراً مزدوجاً على زر Choose Folder. سيقوم NetBeans بإنشاء دالة معالج الحدث chooseFolderButtonActionPerformed في وضع Source.



- أضف الكود التالي داخل هذه الدالة:
Java



private void chooseFolderButtonActionPerformed(java.awt.event.ActionEvent evt) {
    JFileChooser fileChooser = new JFileChooser();
    fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); // للسماح باختيار المجلدات فقط

    int result = fileChooser.showOpenDialog(this); // عرض مربع حوار اختيار المجلد

    if (result == JFileChooser.APPROVE_OPTION) {
        File selectedFolder = fileChooser.getSelectedFile();
        // تصفية الملفات للعثور على الصور فقط (يمكنك إضافة المزيد من الامتدادات)
        File[] files = selectedFolder.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.toLowerCase().endsWith(".jpg") ||
                       name.toLowerCase().endsWith(".jpeg") ||
                       name.toLowerCase().endsWith(".png") ||
                       name.toLowerCase().endsWith(".gif");
            }
        });

        if (files != null && files.length > 0) {
            imageFiles = new ArrayList<>(Arrays.asList(files));
            currentImageIndex = 0; // ابدأ من الصورة الأولى
            displayImage(); // اعرض أول صورة
            updateNavigationButtons(); // تحديث حالة الأزرار
        } else {
            imageFiles.clear();
            currentImageIndex = -1;
            imageDisplayLabel.setIcon(null); // مسح الصورة المعروضة
            JOptionPane.showMessageDialog(this, "لا توجد صور مدعومة في المجلد المحدد.", "لا توجد صور", JOptionPane.INFORMATION_MESSAGE);
            updateNavigationButtons(); // تحديث حالة الأزرار
        }
    }
}



--

4* تنفيذ وظيفة زر Previous:

- ارجع إلى وضع Design.
- انقر نقراً مزدوجاً على زر Previous. سيقوم NetBeans بإنشاء دالة معالج الحدث previousButtonActionPerformed.
- أضف الكود التالي داخل هذه الدالة:
Java

private void previousButtonActionPerformed(java.awt.event.ActionEvent evt) {
    if (currentImageIndex > 0) {
        currentImageIndex--;
        displayImage();
        updateNavigationButtons();
    }
}
--

5* تنفيذ وظيفة زر Next :

- ارجع إلى وضع Design.
- انقر نقراً مزدوجاً على زر Next. سيقوم NetBeans بإنشاء دالة معالج الحدث
 nextButtonActionPerformed.
- أضف الكود التالي داخل هذه الدالة:
Java

private void nextButtonActionPerformed(java.awt.event.ActionEvent evt) {
    if (currentImageIndex < imageFiles.size() - 1) {
        currentImageIndex++;
        displayImage();
        updateNavigationButtons();
    }
}
--

6* كتابة الدوال المساعدة (displayImage و updateNavigationButtons):
أضف هذه الدوال كـ private void بعد دوال معالجة الأحداث التي أنشأتها للتو.
Java



private void displayImage() {
    if (currentImageIndex >= 0 && currentImageIndex < imageFiles.size()) {
        File imageFile = imageFiles.get(currentImageIndex);
        ImageIcon originalIcon = new ImageIcon(imageFile.getAbsolutePath());

        // تغيير حجم الصورة لتناسب حجم imagePanel
        int panelWidth = imagePanel.getWidth();
        int panelHeight = imagePanel.getHeight();

        if (panelWidth <= 0 || panelHeight <= 0) {
            // إذا لم يكن اللوحة مرئية أو لم يتم تهيئتها بعد، استخدم حجماً افتراضياً
            panelWidth = 600; // مثال: حجم افتراضي
            panelHeight = 400; // مثال: حجم افتراضي
        }

        Image image = originalIcon.getImage();
        Image scaledImage = image.getScaledInstance(panelWidth, panelHeight, Image.SCALE_SMOOTH);
        imageDisplayLabel.setIcon(new ImageIcon(scaledImage));
    } else {
        imageDisplayLabel.setIcon(null); // مسح الصورة إذا لا توجد صور
    }
}

private void updateNavigationButtons() {
    // تفعيل/تعطيل زر "Previous"
    previousButton.setEnabled(currentImageIndex > 0);
    // تفعيل/تعطيل زر "Next"
    nextButton.setEnabled(currentImageIndex < imageFiles.size() - 1);
}



--

الخطوة 4: تهيئة التطبيق الرئيسي (Main Class)

سنحتاج إلى تعديل Main.java لكي يقوم بإنشاء وعرض إطار عارض الصور الخاص بنا.
- افتح Main.java (عادةً ما يكون في com.yourcompany.imageviewerapp الحزمة).
- تأكد من أن الكود يشبه ما يلي:
Java

package com.yourcompany.imageviewerapp;

public class Main {

    public static void main(String[] args) {
        // تشغيل الواجهة الرسومية على Event Dispatch Thread (EDT)
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                ImageViewerFrame frame = new ImageViewerFrame();
                frame.setTitle("عارض الصور البسيط"); // تعيين عنوان للنافذة
                frame.setSize(800, 600); // تعيين حجم افتراضي للنافذة
                frame.setLocationRelativeTo(null); // توسيط النافذة على الشاشة
                frame.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); // إغلاق التطبيق عند إغلاق النافذة
                frame.setVisible(true); // جعل النافذة مرئية
            }
        });
    }
}
--

الخطوة 5: تشغيل التطبيق واختباره

1* تأكد من حفظ جميع الملفات (ImageViewerFrame.java و Main.java).
2* لتشغيل التطبيق:
- انقر بزر الماوس الأيمن على Main.java في نافذة Projects.
- اختر Run File.
- بدلاً من ذلك، يمكنك النقر بزر الماوس الأيمن على المشروع نفسه (ImageViewerApp) 
واختيار Run.
3* الاختبار:
- سيظهر لك إطار "عارض الصور البسيط".
- انقر على زر Choose Folder.
- اختر مجلدًا يحتوي على بعض الصور (بصيغ JPG, JPEG, PNG, GIF).
- يجب أن تظهر الصورة الأولى في imagePanel.
- جرب أزرار Previous و Next لتصفح الصور.
- لاحظ أن أزرار التنقل يتم تفعيلها/تعطيلها بشكل صحيح عند الوصول إلى بداية أو نهاية قائمة الصور.

* شرح الأكواد والمفاهيم الرئيسية :
- JFileChooser: مكون يسمح للمستخدم باختيار ملفات أو مجلدات.
- setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY): 
يحدد أن المستخدم يمكنه اختيار مجلدات فقط.
showOpenDialog(this): يعرض مربع حوار "فتح".
- FilenameFilter: واجهة تُستخدم لتصفية الملفات بناءً على اسم الملف،
 استخدمناها هنا للتعرف على امتدادات الصور.
- ImageIcon: كائن يُستخدم لتحميل وعرض الصور في مكونات Swing مثل JLabel
 أو JButton.
- JLabel لعرض الصورة: على الرغم من أن JLabel يستخدم عادة لعرض النص،
 إلا أنه يمكنه أيضاً عرض الصور باستخدام setIcon(ImageIcon).
- Image.getScaledInstance(): تُستخدم لتغيير حجم الصورة لتناسب أبعاد JPanel،
 مما يضمن أن الصور لا تخرج عن نطاق العرض. Image.SCALE_SMOOTH 
لتحسين جودة الصورة بعد التغيير.
- imagePanel.setLayout(new java.awt.BorderLayout()) و 
imagePanel.add(imageDisplayLabel, java.awt.BorderLayout.CENTER):
قمنا بتعيين BorderLayout لـ imagePanel ثم أضفنا imageDisplayLabel إلى منتصفه. 
هذا يضمن أن imageDisplayLabel سيتمدد ليملأ imagePanel وأن الصورة ستكون في المنتصف.
- SwingUtilities.invokeLater(new Runnable() { ... }): 
هذه الممارسة القياسية في Swing لضمان أن جميع عمليات تحديث الواجهة الرسومية
 تتم على Event Dispatch Thread (EDT). هذا يمنع تجميد الواجهة ويضمن سلاسة التطبيق.


اضافات اخرى لتطوير عارض صور متقدم


بعد بناء عارض الصور الأساسي، حان الوقت لإضافة ميزات تعزز
 تجربة المستخدم وتجعل التطبيق أكثر قوة ومرونة ، في هذا الجزء، سنتناول ستة تحسينات رئيسية:
 شريط التقدم، عرض اسم الملف، تكبير/تصغير الصورة، دعم صيغ صور إضافية، 
معاينات الصور المصغرة، وشريط القائمة. ستتعلم كيفية استخدام مكونات Swing إضافية،
 وتطبيق منطق جديد، وحتى التعامل مع المهام الخلفية باستخدام SwingWorker 
للحفاظ على استجابة الواجهة الرسومية.
سنقوم بتعديل ملف ImageViewerFrame.java بشكل أساسي، وسنضيف بعض التعديلات
 البسيطة على Main.java إذا لزم الأمر.

الخطوة 0: تحديث المتغيرات واستيراد المكتبات (في ImageViewerFrame.java)

قبل البدء، قم بتحديث المتغيرات العامة في فئة ImageViewerFrame وأضف الاستيرادات اللازمة:
Java



package com.yourcompany.imageviewerapp;

import java.awt.BorderLayout; // لإدارة التخطيط
import java.awt.Image;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar; // جديد: لشريط التقدم
import javax.swing.JScrollPane; // جديد: لشريط التمرير (للمعاينة المصغرة)
import javax.swing.SwingWorker; // جديد: للعمليات الخلفية
import java.awt.event.ActionListener; // جديد: لأحداث القائمة
import java.awt.event.ActionEvent; // جديد: لأحداث القائمة
import javax.swing.JMenuBar; // جديد: شريط القائمة
import javax.swing.JMenu; // جديد: قائمة فرعية في شريط القائمة
import javax.swing.JMenuItem; // جديد: عناصر القائمة
import java.awt.FlowLayout; // جديد: لتخطيط لوحة المعاينة
import javax.swing.JPanel; // جديد: لوحة المعاينة

public class ImageViewerFrame extends javax.swing.JFrame {

    private List<File> imageFiles;
    private int currentImageIndex;
    private JLabel imageDisplayLabel; // يعرض الصورة الكبيرة
    private double currentZoomFactor = 1.0; // جديد: عامل التكبير/التصغير الافتراضي
    private final double ZOOM_STEP = 0.1; // جديد: خطوة التكبير/التصغير

    // جديد: متغيرات الميزات الإضافية
    private JLabel fileNameLabel; // لعرض اسم الملف
    private JProgressBar progressBar; // شريط التقدم
    private JPanel thumbnailPanel; // لوحة لعرض الصور المصغرة
    private JScrollPane thumbnailScrollPane; // شريط تمرير للمعاينة المصغرة

    // Constructor (سيبقى كما هو تقريباً، مع إضافات بسيطة)
    public ImageViewerFrame() {
        initComponents();
        imageFiles = new ArrayList<>();
        currentImageIndex = -1;

        // تهيئة JLabel لعرض الصورة وتوسطه داخل imagePanel
        imageDisplayLabel = new JLabel();
        imageDisplayLabel.setHorizontalAlignment(JLabel.CENTER);
        imagePanel.setLayout(new java.awt.BorderLayout());
        imagePanel.add(imageDisplayLabel, java.awt.BorderLayout.CENTER);

        // جديد: تهيئة fileNameLabel (سنضيفه في التصميم لاحقاً)
        fileNameLabel = new JLabel("لا يوجد مجلد محدد.");
        fileNameLabel.setHorizontalAlignment(JLabel.CENTER);
        fileNameLabel.setVerticalAlignment(JLabel.BOTTOM); // يوضع في أسفل imagePanel

        // جديد: إضافة fileNameLabel إلى imagePanel (أو لوحة منفصلة)
        // إذا أردت أن يظهر داخل نفس اللوحة مع الصورة
        // imagePanel.add(fileNameLabel, java.awt.BorderLayout.SOUTH);

        // نداء لدالة تهيئة الميزات الجديدة
        setupNewFeatures();
    }

    // جديد: دالة لتهيئة المكونات الإضافية (سيتم استدعاؤها في Constructor)
    private void setupNewFeatures() {
        // تهيئة شريط التقدم
        progressBar = new JProgressBar(0, 100);
        progressBar.setStringPainted(true); // لعرض النسبة المئوية
        progressBar.setVisible(false); // مخفي في البداية

        // تهيئة لوحة المعاينة المصغرة
        thumbnailPanel = new JPanel();
        thumbnailPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5)); // صور مصغرة تتدفق من اليسار
        thumbnailScrollPane = new JScrollPane(thumbnailPanel);
        thumbnailScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); // شريط تمرير أفقي فقط
        thumbnailScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        // ملاحظة: ستتم إضافة هذه المكونات إلى الواجهة من خلال NetBeans GUI Builder في الخطوات التالية.
        // فقط تهيئتها هنا.
    }

    // ... (بقية الكود الذي أنشأه NetBeans)
}



--

الخطوة 1: شريط القائمة (JMenuBar)

سنضيف شريط قائمة علوي يحتوي على خيارات "ملف" و "مساعدة".

1* تصميم القائمة:
- في وضع Design لـ ImageViewerFrame.java، اسحب JMenuBar من Palette
 (تحت فئة Swing Menus) وضعه في أعلى الـ JFrame. سيتم وضعه تلقائياً في مكانه الصحيح.
- انقر بزر الماوس الأيمن على JMenuBar في نافذة Inspector.
- اختر Add -> JMenu. غيّر text إلى File.
- انقر بزر الماوس الأيمن على قائمة File الجديدة.
- اختر Add -> JMenuItem. غيّر text إلى Choose Folder.... غيّر
 Variable Name إلى menuChooseFolder.
- كرر الخطوة وأضف JMenuItem آخر. غيّر text إلى Exit. غيّر Variable Name إلى menuExit.
- كرر الخطوة وأضف JMenu آخر إلى JMenuBar. غيّر text إلى Help.
- أضف JMenuItem إلى قائمة Help. غيّر text إلى About.... غيّر Variable Name إلى menuAbout.

2* ربط الأحداث بالقائمة:
- في وضع Design، انقر بزر الماوس الأيمن على menuChooseFolder (ضمن قائمة File).
- اختر Events -> Action -> actionPerformed.
داخل الدالة menuChooseFolderActionPerformed التي تم إنشاؤها،
 قم باستدعاء الدالة الخاصة بزر Choose Folder الموجودة مسبقاً:
Java

private void menuChooseFolderActionPerformed(java.awt.event.ActionEvent evt) {
    chooseFolderButtonActionPerformed(evt); // استدعاء دالة الزر الموجودة
}
--

* كرر العملية لـ menuExit. داخل الدالة menuExitActionPerformed، أضف:
Java

private void menuExitActionPerformed(java.awt.event.ActionEvent evt) {
    System.exit(0); // إغلاق التطبيق
}
--

* كرر العملية لـ menuAbout. داخل الدالة menuAboutActionPerformed، أضف:
Java

private void void menuAboutActionPerformed(java.awt.event.ActionEvent evt) {
    JOptionPane.showMessageDialog(this,
            "تطبيق عارض الصور البسيط\nالإصدار 1.0\nمقدم من [اسمك أو شركتك]",
            "حول عارض الصور", JOptionPane.INFORMATION_MESSAGE);
}
--

الخطوة 2: شريط التقدم (JProgressBar)

سنعرض شريط تقدم عند تحميل الصور من مجلد كبير، لضمان استجابة الواجهة. 
سنستخدم SwingWorker.
1* تصميم شريط التقدم:
في وضع Design، اسحب JProgressBar من Palette 
(تحت فئة Swing Controls) وضعه في الجزء السفلي من الـ JFrame.
غيّر Variable Name الخاص به إلى progressBar.
في نافذة Properties، قم بتعيين خاصية String Painted إلى true (هذا سيعرض النسبة المئوية على الشريط).
اجعله Visible إلى false مؤقتاً، حيث سيتم إظهاره وإخفاؤه برمجياً.

2* تعديل دالة chooseFolderButtonActionPerformed:
سنقوم بإنشاء SwingWorker لتحميل الصور في الخلفية وتحديث شريط التقدم.
Java




private void chooseFolderButtonActionPerformed(java.awt.event.ActionEvent evt) {
    JFileChooser fileChooser = new JFileChooser();
    fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

    int result = fileChooser.showOpenDialog(this);

    if (result == JFileChooser.APPROVE_OPTION) {
        final File selectedFolder = fileChooser.getSelectedFile();

        // إظهار شريط التقدم
        progressBar.setVisible(true);
        progressBar.setValue(0); // إعادة تعيين التقدم

        // تشغيل مهمة البحث عن الصور وتحميلها في خلفية باستخدام SwingWorker
        new SwingWorker<List<File>, Integer>() {
            @Override
            protected List<File> doInBackground() throws Exception {
                File[] files = selectedFolder.listFiles(new FilenameFilter() {
                    @Override
                    public boolean accept(File dir, String name) {
                        // تم تحديث هذا الجزء لدعم صيغ صور إضافية
                        return name.toLowerCase().matches(".*\\.(jpg|jpeg|png|gif|bmp|tiff|webp)$");
                    }
                });

                List<File> images = new ArrayList<>();
                if (files != null) {
                    for (int i = 0; i < files.length; i++) {
                        if (files[i].isFile()) { // تأكد أنه ملف وليس مجلد فرعي
                            images.add(files[i]);
                        }
                        // تحديث شريط التقدم
                        int progress = (int) ((i + 1) * 100.0 / files.length);
                        publish(progress); // لإرسال التقدم إلى process()
                    }
                }
                return images;
            }

            @Override
            protected void process(List<Integer> chunks) {
                // يتم تشغيله على EDT، ويستقبل تحديثات التقدم
                if (!chunks.isEmpty()) {
                    progressBar.setValue(chunks.get(chunks.size() - 1));
                }
            }

            @Override
            protected void done() {
                // يتم تشغيله على EDT بعد اكتمال المهمة أو إلغائها
                try {
                    imageFiles = get(); // الحصول على قائمة الصور المكتملة
                    if (!imageFiles.isEmpty()) {
                        currentImageIndex = 0;
                        displayImage();
                        updateNavigationButtons();
                        updateFileNameLabel(); // جديد: تحديث اسم الملف
                        buildThumbnails(); // جديد: بناء المعاينات المصغرة
                    } else {
                        imageFiles.clear();
                        currentImageIndex = -1;
                        imageDisplayLabel.setIcon(null);
                        fileNameLabel.setText("لا يوجد مجلد محدد."); // جديد
                        JOptionPane.showMessageDialog(ImageViewerFrame.this, "لا توجد صور مدعومة في المجلد المحدد.", "لا توجد صور", JOptionPane.INFORMATION_MESSAGE);
                        updateNavigationButtons();
                        clearThumbnails(); // جديد: مسح المعاينات
                    }
                } catch (Exception e) {
                    JOptionPane.showMessageDialog(ImageViewerFrame.this, "حدث خطأ أثناء تحميل الصور: " + e.getMessage(), "خطأ", JOptionPane.ERROR_MESSAGE);
                } finally {
                    progressBar.setVisible(false); // إخفاء شريط التقدم
                }
            }
        }.execute(); // بدء تشغيل مهمة SwingWorker
    }
}



--

الخطوة 3: عرض أسماء الملفات (JLabel)

سنضيف JLabel لعرض اسم الملف الحالي.




1*  تصميم JLabel:
- في وضع Design، اسحب JLabel من Palette (تحت فئة Swing Controls)
 وضعه في الجزء السفلي من الـ JFrame، فوق progressBar.
- غيّر Variable Name الخاص به إلى fileNameLabel.
- قم بتغيير text إلى لا يوجد مجلد محدد. (أو أي نص افتراضي).
- في نافذة Properties، قم بتعيين horizontalAlignment إلى Center.

2* تعديل دالة displayImage() (وإضافة updateFileNameLabel()):
سنقوم بتعديل displayImage() لاستدعاء دالة جديدة تقوم بتحديث fileNameLabel.
Java




private void displayImage() {
    if (currentImageIndex >= 0 && currentImageIndex < imageFiles.size()) {
        File imageFile = imageFiles.get(currentImageIndex);
        ImageIcon originalIcon = new ImageIcon(imageFile.getAbsolutePath());

        int panelWidth = imagePanel.getWidth();
        int panelHeight = imagePanel.getHeight();

        if (panelWidth <= 0 || panelHeight <= 0) {
            // إذا لم يكن اللوحة مرئية أو لم يتم تهيئتها بعد، استخدم حجماً افتراضياً
            panelWidth = imagePanel.getPreferredSize().width > 0 ? imagePanel.getPreferredSize().width : 600;
            panelHeight = imagePanel.getPreferredSize().height > 0 ? imagePanel.getPreferredSize().height : 400;
        }

        // تطبيق عامل التكبير/التصغير
        int scaledWidth = (int) (originalIcon.getIconWidth() * currentZoomFactor);
        int scaledHeight = (int) (originalIcon.getIconHeight() * currentZoomFactor);

        // تعديل حجم الصورة لتناسب اللوحة مع الحفاظ على نسبة الأبعاد
        if (scaledWidth > panelWidth || scaledHeight > panelHeight) {
            double scale = Math.min((double) panelWidth / scaledWidth, (double) panelHeight / scaledHeight);
            scaledWidth = (int) (scaledWidth * scale);
            scaledHeight = (int) (scaledHeight * scale);
        }

        Image image = originalIcon.getImage();
        Image scaledImage = image.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);
        imageDisplayLabel.setIcon(new ImageIcon(scaledImage));

        updateFileNameLabel(); // جديد: تحديث اسم الملف
    } else {
        imageDisplayLabel.setIcon(null);
        updateFileNameLabel(); // جديد: تحديث اسم الملف حتى لو لم يكن هناك شيء
    }
}

// دالة جديدة لتحديث اسم الملف المعروض
private void updateFileNameLabel() {
    if (currentImageIndex >= 0 && currentImageIndex < imageFiles.size()) {
        fileNameLabel.setText("الملف: " + imageFiles.get(currentImageIndex).getName() + " (" + (currentImageIndex + 1) + " من " + imageFiles.size() + ")");
    } else {
        fileNameLabel.setText("لا يوجد مجلد محدد.");
    }
}



--

الخطوة 4: تكبير/تصغير الصورة (Zoom In/Out)

سنضيف أزراراً للتكبير والتصغير.

1* تصميم الأزرار:
- في وضع Design، اسحب JButton آخر، غيّر text إلى Zoom In.
- غيّر Variable Name إلى zoomInButton.
- اسحب JButton آخر ، غيّر text إلى Zoom Out.
- غيّر Variable Name إلى zoomOutButton.
- ضع هذه الأزرار في مكان مناسب، ربما بجانب أزرار Previous و Next.

2* تنفيذ وظائف الأزرار:
- انقر نقراً مزدوجاً على زر zoomInButton في وضع Design. أضف الكود:
Java

private void zoomInButtonActionPerformed(java.awt.event.ActionEvent evt) {
    currentZoomFactor += ZOOM_STEP;
    displayImage(); // إعادة عرض الصورة بالحجم الجديد
}
--

- انقر نقراً مزدوجاً على زر zoomOutButton في وضع Design. أضف الكود:
Java

private void zoomOutButtonActionPerformed(java.awt.event.ActionEvent evt) {
    if (currentZoomFactor - ZOOM_STEP > 0.1) { // حد أدنى للتكبير/التصغير
        currentZoomFactor -= ZOOM_STEP;
        displayImage(); // إعادة عرض الصورة بالحجم الجديد
    } else {
        currentZoomFactor = 0.1; // لضمان عدم وجود حجم سلبي أو صغير جداً
        displayImage();
    }
}
--

* ملاحظة : تم تعديل displayImage() في الخطوة 3 ليتضمن currentZoomFactor.

الخطوة 5: دعم صيغ صور إضافية

تم تضمين هذا بالفعل في تعديل FilenameFilter داخل دالة 
chooseFolderButtonActionPerformed في الخطوة 2 (شريط التقدم):
Java

                return name.toLowerCase().matches(".*\\.(jpg|jpeg|png|gif|bmp|tiff|webp)$");
--

هذا التعبير النمطي .*\\.(jpg|jpeg|png|gif|bmp|tiff|webp)$ سيتعرف على جميع امتدادات الصور الشائعة.

الخطوة 6: معاينة الصور المصغرة (Thumbnails) في شريط جانبي

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

1* تصميم لوحة المعاينات المصغرة (JPanel و JScrollPane):

- في وضع Design، اسحب JPanel من Palette (تحت فئة Swing Containers) 
وضعه على الجانب الأيسر أو الأيمن من الـ JFrame.
- غيّر Variable Name الخاص به إلى thumbnailPanel.
- في نافذة Properties لـ thumbnailPanel، ابحث عن خاصية Preferred Size
 وقم بتعيين عرض مناسب لها (مثال: 150, 400 - 150 عرض و 400 ارتفاع).
* الأهم: لتوفير شريط تمرير إذا كانت هناك العديد من المعاينات، اسحب
 JScrollPane من Palette وضعه فوق thumbnailPanel.
- انقر بزر الماوس الأيمن على JScrollPane الجديد واختر 
Change Variable Name... ثم اكتب thumbnailScrollPane.
- انقر بزر الماوس الأيمن على thumbnailScrollPane واختر
 Set Layout -> BorderLayout.
- اسحب thumbnailPanel وضعها داخل thumbnailScrollPane
 (في منطقة Center لتخطيط BorderLayout).
- في نافذة Properties لـ thumbnailScrollPane، ابحث عن 
horizontalScrollBarPolicy واضبطها على AS_NEEDED.
- ابحث عن verticalScrollBarPolicy واضبطها على NEVER. 
(لجعل شريط التمرير أفقيًا فقط إذا كنت تريد عرضها كشريط أفقي)
- إذا كنت تريد أن تكون المعاينات عمودية، فافعل العكس: 
verticalScrollBarPolicy إلى AS_NEEDED و
 horizontalScrollBarPolicy إلى NEVER.

2* إضافة دوال لإنشاء وعرض المعاينات:

Java




// دالة جديدة لبناء وعرض المعاينات المصغرة
private void buildThumbnails() {
    thumbnailPanel.removeAll(); // مسح أي معاينات سابقة
    if (imageFiles.isEmpty()) {
        thumbnailPanel.revalidate();
        thumbnailPanel.repaint();
        return;
    }

    // استخدام SwingWorker لتحميل المعاينات في الخلفية
    new SwingWorker<Void, ImageIcon>() {
        @Override
        protected Void doInBackground() throws Exception {
            for (int i = 0; i < imageFiles.size(); i++) {
                File file = imageFiles.get(i);
                ImageIcon thumbnailIcon = createThumbnail(file, 100, 75); // حجم المعاينة
                if (thumbnailIcon != null) {
                    publish(thumbnailIcon); // إرسال المعاينة لعرضها على EDT
                }
            }
            return null;
        }

        @Override
        protected void process(List<ImageIcon> chunks) {
            // يتم تشغيله على EDT، ويستقبل المعاينات التي تم إنشاؤها
            for (ImageIcon icon : chunks) {
                JLabel thumbLabel = new JLabel(icon);
                thumbLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); // مؤشر يد عند المرور

                // إضافة حدث للنقر على المعاينة لعرض الصورة الكبيرة
                final int index = thumbnailPanel.getComponentCount(); // الحصول على index المعاينة
                thumbLabel.addMouseListener(new java.awt.event.MouseAdapter() {
                    @Override
                    public void mouseClicked(java.awt.event.MouseEvent e) {
                        currentImageIndex = index;
                        displayImage();
                        updateNavigationButtons();
                        // يمكن إضافة تسليط ضوئي على المعاينة المحددة
                    }
                });
                thumbnailPanel.add(thumbLabel);
            }
            thumbnailPanel.revalidate(); // إعادة التحقق من صحة التخطيط
            thumbnailPanel.repaint(); // إعادة رسم اللوحة
        }

        @Override
        protected void done() {
            // يمكن إضافة رسالة اكتمال التحميل أو إخفاء أي مؤشر تحميل هنا
        }
    }.execute();
}

// دالة مساعدة لإنشاء معاينة مصغرة من ملف صورة
private ImageIcon createThumbnail(File file, int width, int height) {
    try {
        ImageIcon icon = new ImageIcon(file.getAbsolutePath());
        Image image = icon.getImage();
        Image scaledImage = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        return new ImageIcon(scaledImage);
    } catch (Exception e) {
        System.err.println("خطأ في إنشاء المعاينة لـ " + file.getName() + ": " + e.getMessage());
        return null;
    }
}

// دالة لمسح المعاينات المصغرة
private void clearThumbnails() {
    thumbnailPanel.removeAll();
    thumbnailPanel.revalidate();
    thumbnailPanel.repaint();
}



--

3* ربط بناء المعاينات بحدث اختيار المجلد:

في دالة chooseFolderButtonActionPerformed
 (داخل SwingWorker في دالة done())، تأكد من وجود السطر:
Java

buildThumbnails(); // جديد: بناء المعاينات المصغرة
// ...
clearThumbnails(); // جديد: مسح المعاينات في حالة عدم وجود صور
--

الخطوة 7: تحديث دالة Main.java (إذا لزم الأمر)

لا توجد تعديلات جوهرية على Main.java بخلاف التأكد من أن حجم النافذة الأولي
 يسمح بظهور جميع المكونات الجديدة بشكل جيد.
Java

package com.yourcompany.imageviewerapp;

public class Main {

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                ImageViewerFrame frame = new ImageViewerFrame();
                frame.setTitle("عارض الصور المتقدم"); // تغيير العنوان
                frame.setSize(1000, 700); // زيادة الحجم لاستيعاب المعاينات
                frame.setLocationRelativeTo(null);
                frame.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
                frame.setVisible(true);
            }
        });
    }
}
--

** نقاط مهمة ومفاهيم متقدمة استخدمناها ** :

- SwingWorker: هذا هو المكون الأكثر أهمية هنا. يسمح لك بتشغيل مهام تستغرق وقتاً طويلاً (مثل البحث عن الملفات أو إنشاء المعاينات) في خيط منفصل (Background Thread)، بينما تبقى واجهة المستخدم الرسومية (UI) مستجيبة.
- doInBackground(): يتم تشغيل هذا الجزء في الخلفية. هنا نقوم بتحميل الملفات وإنشاء المعاينات.
- publish(V...): يُستخدم لإرسال تحديثات التقدم من doInBackground() إلى process().
- process(List<V> chunks): يتم تشغيله على Event Dispatch Thread (EDT) 
ويتلقى التحديثات من publish(). هنا نقوم بتحديث JProgressBar أو إضافة المعاينات إلى الواجهة.
- done(): يتم تشغيله على EDT بعد اكتمال مهمة doInBackground() أو إلغائها. 
هنا نقوم بإخفاء شريط التقدم، وتحديث القائمة الرئيسية للصور، وبناء المعاينات النهائية.
- JScrollPane: ضروري جداً لعرض المحتوى الذي قد يتجاوز حجم الشاشة،
 مثل قائمة طويلة من الصور المصغرة.
- FlowLayout: تخطيط بسيط يرتب المكونات في صفوف، من اليسار إلى اليمين. 
مناسب جداً لترتيب الصور المصغرة.
- JLabel للنقر: يمكن إضافة MouseListener إلى JLabel لجعلها قابلة للنقر، 
مما يتيح لك تحديد الصورة المصغرة لعرضها بالحجم الكامل.
- FilenameFilter مع التعبيرات النمطية (Regular Expressions): 
طريقة قوية لتصفية الملفات بناءً على أنماط معقدة لاسم الملف أو امتداده.
- Image.getScaledInstance(): مهمة جداً لتغيير حجم الصور بكفاءة دون
 استهلاك الكثير من الذاكرة أو تشويه الصورة.

* نصائح إضافية:
* اختبار الأداء: عند التعامل مع مجلدات كبيرة جداً من الصور، قد تلاحظ بعض البطء.
 يمكنك تحسين الأداء عن طريق:
- تحميل المعاينات عند الحاجة (Lazy Loading) بدلاً من جميعها دفعة واحدة.
تخزين المعاينات المؤقتة في الذاكرة (Caching).
* معالجة الأخطاء: إضافة المزيد من كتل try-catch للتعامل مع الأخطاء المحتملة مثل 
عدم القدرة على قراءة ملف الصورة.
* واجهة المستخدم: يمكن تحسين الواجهة باستخدام أنظمة تخطيط أكثر تعقيداً مثل
 GridBagLayout أو MigLayout (مكتبة خارجية) لتوفير تحكم أكبر في موضع وحجم المكونات.
* المظاهر (Look and Feel): يمكنك تغيير مظهر التطبيق بالكامل باستخدام UIManager.setLookAndFeel().
بهذه الإضافات، يصبح تطبيق عارض الصور أكثر قوة ومرونة، ويقدم تجربة مستخدم أفضل بكثير!

 
جدول المحتويات