Smart-World Surf

יחידה 8: ממשקי משתמש גרפיים (GUI)

בניית ממשקי משתמש אינטראקטיביים באמצעות Swing/JavaFX.
רכיבי GUI בסיסייםמודל טיפול באירועיםLayout Managersבניית יישומים גרפיים

ברוכים הבאים ליחידת הלימוד על ממשקי משתמש גרפיים (GUI) ב-Java. יחידה זו חיונית לבניית יישומים מודרניים ואינטראקטיביים. נתמקד בספריית Swing, שהיא הבסיס לתכנות GUI ב-Java בקורס זה, ונלמד כיצד ליצור חלונות, להוסיף רכיבים, לסדר אותם בצורה יעילה ולטפל באינטראקציות של המשתמש. הבנה מעמיקה של עקרונות אלו תאפשר לכם לפתח יישומים גרפיים מורכבים ושימושיים.

מבוא לממשקי משתמש גרפיים ב-Java Swing

המעבר מתכנות קונסולה לגרפי

עד כה, התמקדנו בעיקר ביישומים מבוססי קונסולה, בהם האינטראקציה עם המשתמש מתבצעת באמצעות קלט/פלט טקסטואלי. ממשקי משתמש גרפיים (GUI) מציעים חווית משתמש עשירה ואינטואיטיבית יותר, על ידי שימוש בחלונות, כפתורים, תיבות טקסט ועוד. Java מספקת שתי ספריות עיקריות לבניית GUI: AWT (Abstract Window Toolkit) ו-Swing. Swing נבנתה על גבי AWT ומציעה סט עשיר יותר של רכיבים ("קל משקל" - lightweight components) וגמישות רבה יותר.

ארכיטקטורת Swing

Swing מבוססת על היררכיה של רכיבים. כל רכיב GUI הוא אובייקט, ורכיבים מסוימים יכולים להכיל רכיבים אחרים. הנה שני רכיבי יסוד:

JFrame: רכיב ברמה העליונה (top-level container) המייצג חלון יישום. זהו החלון הראשי שבו כל שאר רכיבי ה-GUI מוצגים.
JPanel: רכיב מיכל (container) המשמש לקבץ רכיבי GUI אחרים. הוא מאפשר ארגון לוגי וויזואלי של הרכיבים בתוך ה-JFrame. ניתן להוסיף מספר JPanel-ים ל-JFrame, ו-JPanel יכול להכיל JPanel-ים נוספים.

רכיבי GUI בסיסיים ומנהלי פריסה

רכיבים נפוצים

Swing מציעה מגוון רחב של רכיבים לבניית ממשקים. הנה כמה מהנפוצים ביותר:

  • JButton: כפתור לחיץ המפעיל פעולה מסוימת.
  • JLabel: רכיב להצגת טקסט או תמונה שאינו ניתן לעריכה על ידי המשתמש.
  • JTextField: שדה קלט טקסט חד-שורתי שבו המשתמש יכול להזין נתונים.
  • JTextArea: שדה קלט טקסט רב-שורתי.
  • JCheckBox: תיבת סימון (checkbox) המאפשרת למשתמש לבחור אפשרות אחת או יותר.
  • JRadioButton: כפתור בחירה (radio button) המאפשר למשתמש לבחור אפשרות אחת מתוך קבוצה.

מנהלי פריסה (Layout Managers)

מנהלי פריסה הם אובייקטים הקובעים כיצד רכיבי GUI מסודרים ומוצגים בתוך מיכל (כמו JFrame או JPanel). הם חיוניים ליצירת ממשקים רספונסיביים שמתאימים את עצמם לגדלים שונים של חלונות ומסכים. שימוש במנהלי פריסה עדיף על קביעת מיקום וגודל מוחלטים (null layout), מכיוון שהוא גמיש יותר וקל יותר לתחזוקה.

FlowLayout

מסדר רכיבים בשורה, משמאל לימין, ו"גולש" לשורה הבאה אם אין מספיק מקום, בדומה לאופן שבו טקסט מסודר בפסקה. זהו מנהל הפריסה ברירת המחדל עבור JPanel.

BorderLayout

מחלק את המיכל לחמישה אזורים: צפון (NORTH), דרום (SOUTH), מזרח (EAST), מערב (WEST) ומרכז (CENTER). ניתן להוסיף רכיב אחד לכל אזור. זהו מנהל הפריסה ברירת המחדל עבור JFrame.

GridLayout

מסדר רכיבים ברשת (grid) של שורות ועמודות בגודל שווה. כל רכיב תופס תא אחד ברשת, וכל התאים באותו גודל.

מודל טיפול באירועים (Event Handling Model)

יישומים גרפיים הם מונחי אירועים (event-driven). המשמעות היא שהם מגיבים לפעולות המשתמש (לחיצת כפתור, הקלדת טקסט, תנועת עכבר) ולא פועלים ברצף ליניארי קבוע. מודל טיפול האירועים ב-Java מבוסס על שלושה עקרונות מרכזיים:

מקור אירוע (Event Source): הרכיב שבו התרחש האירוע (לדוגמה, JButton שנלחץ, JTextField שבו הוזן טקסט).
אובייקט אירוע (Event Object): אובייקט המכיל מידע על האירוע שהתרחש (לדוגמה, ActionEvent עבור לחיצת כפתור, MouseEvent עבור פעולת עכבר).
מאזין אירועים (Event Listener): אובייקט שממתין לאירועים מסוג מסוים ומבצע פעולה כאשר האירוע מתרחש. מאזינים מיישמים ממשקים ספציפיים (לדוגמה, ActionListener, MouseListener).

יישום מאזינים

כדי שרכיב יגיב לאירוע, יש לרשום אובייקט מאזין אליו. לדוגמה, עבור JButton, נשתמש בממשק ActionListener. ניתן ליישם מאזינים בדרכים שונות, כולל מחלקות פנימיות אנונימיות (anonymous inner classes) או ביטויי למדא (lambda expressions) ב-Java 8 ומעלה, המפשטים את הקוד.

ה-Event Dispatch Thread (EDT): מדוע זה קריטי?

כל עדכוני ממשק המשתמש וטיפול באירועים ב-Swing חייבים להתבצע ב-EDT. גישה לרכיבי GUI מחוץ ל-EDT עלולה לגרום לבעיות תזמון (threading issues), חסימות (deadlocks) או התנהגות בלתי צפויה של הממשק. פעולות ארוכות שרצות ב-EDT יגרמו לממשק להיתקע ולא להגיב. לכן, פעולות ארוכות יש לבצע בשרשורים נפרדים (worker threads) ולעדכן את ה-GUI בחזרה דרך ה-EDT (לדוגמה, באמצעות SwingUtilities.invokeLater()).

בניית יישומים גרפיים: דוגמה מעשית

שלבי בנייה

בניית יישום GUI בסיסי כוללת בדרך כלל את השלבים הבאים:

  1. יצירת חלון ראשי (JFrame).
  2. הגדרת מאפייני החלון (כותרת, גודל, פעולת סגירה).
  3. יצירת רכיבי GUI (כפתורים, תיבות טקסט וכו').
  4. יצירת מיכלי עזר (JPanel) לארגון הרכיבים.
  5. הוספת רכיבים למיכלים והגדרת מנהלי פריסה.
  6. הוספת מאזיני אירועים לרכיבים הרלוונטיים.
  7. הגדרת החלון לגלוי (setVisible(true)).

דוגמה קצרה למבנה קוד:


import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MyFirstGUI extends JFrame {

    public MyFirstGUI() {
        super("My First GUI Application"); // Set frame title
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close operation
        setSize(400, 300); // Set frame size

        JPanel panel = new JPanel(); // Create a panel
        JButton button = new JButton("Click Me!"); // Create a button
        JLabel label = new JLabel("Hello, Swing!"); // Create a label

        button.addActionListener(e -> { // Add an ActionListener using lambda
            label.setText("Button Clicked!");
        });

        panel.add(button); // Add button to panel
        panel.add(label);  // Add label to panel
        add(panel);        // Add panel to frame

        setVisible(true); // Make the frame visible
    }

    public static void main(String[] args) {
        // Ensure GUI updates are done on the Event Dispatch Thread
        SwingUtilities.invokeLater(MyFirstGUI::new);
    }
}

שאלות לדיון

  • הסבר את ההבדל בין JFrame ל-JPanel ומתי נשתמש בכל אחד מהם.
  • תאר את מודל טיפול האירועים ב-Swing (מקור, אירוע, מאזין) והדגם כיצד תטפל בלחיצת כפתור.
  • השווה בין FlowLayout, BorderLayout ו-GridLayout. באילו מצבים תבחר להשתמש בכל אחד מהם?
  • מדוע חשוב לבצע עדכוני GUI ב-Event Dispatch Thread (EDT)? אילו בעיות עלולות להיווצר אם לא נקפיד על כך?

נקודות לתשובת מודל

  • JFrame vs. JPanel: JFrame הוא חלון ברמה העליונה, בעוד JPanel הוא מיכל המשמש לארגון רכיבים בתוך JFrame או JPanel אחר. JFrame מספק את המסגרת הבסיסית של היישום, בעוד JPanel מאפשר מודולריות וארגון ויזואלי.
  • מודל טיפול אירועים:
    • מקור: הרכיב שבו התרחש האירוע (לדוגמה, JButton).
    • אירוע: אובייקט המכיל מידע על האירוע (לדוגמה, ActionEvent).
    • מאזין: אובייקט המיישם ממשק מאזין (לדוגמה, ActionListener) ונרשם למקור האירוע. כאשר האירוע מתרחש, מתודת הטיפול באירוע של המאזין נקראת.
    • דוגמה: button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { /* code */ } }); או באמצעות למדא.
  • השוואת Layout Managers:
    • FlowLayout: רכיבים מסודרים בשורה, גולשים לשורה הבאה. מתאים למספר קטן של רכיבים ללא צורך ביישור מדויק.
    • BorderLayout: חמישה אזורים (צפון, דרום, מזרח, מערב, מרכז). מתאים למבנה כללי של חלון עם אזורים מוגדרים (לדוגמה, תפריט למעלה, סטטוס למטה, תוכן במרכז).
    • GridLayout: רכיבים מסודרים ברשת של תאים בגודל שווה. מתאים לממשקים כמו מחשבון או לוח משחק.
    • הבחירה תלויה במבנה הרצוי ובגמישות הנדרשת.
  • חשיבות ה-EDT:
    • Swing אינה Thread-safe. כל הגישה והעדכון של רכיבי GUI חייבים להתבצע ב-EDT כדי למנוע מצבי מירוץ (race conditions) ובעיות תזמון.
    • פעולות ארוכות שרצות ב-EDT יגרמו ל"הקפאת" הממשק וחוסר תגובה.
    • פתרון: לבצע פעולות ארוכות בשרשורים נפרדים (לדוגמה, באמצעות SwingWorker), ולעדכן את ה-GUI בחזרה דרך ה-EDT באמצעות SwingUtilities.invokeLater().
מצאתם טעות או שחסר משהו?
→ הקודמת
ריבוי תהליכונים (Multithreading)
הבאה ←
תכנות רשת