
برمجة تطبيق عارض صور باستخدام 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; // لعرض الصورة داخل JPanelimport 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().
بهذه الإضافات، يصبح تطبيق عارض الصور أكثر قوة ومرونة، ويقدم تجربة مستخدم أفضل بكثير!