CodeGym /Kursy /JAVA 25 SELF /Zdarzenia w Swing i AWT: podstawy, przykłady

Zdarzenia w Swing i AWT: podstawy, przykłady

JAVA 25 SELF
Poziom 50 , Lekcja 1
Dostępny

1. Bardzo krótkie wprowadzenie do Swing i AWT

Gdyby Java była samochodem, Swing i AWT — to jej „panel przyrządów” i „kierownica”. AWT (Abstract Window Toolkit) — pierwsza biblioteka do tworzenia aplikacji okienkowych w Javie. Używa „natywnych” elementów sterujących systemu operacyjnego, dlatego wygląda różnie na Windows, Linux i Mac.

Swing — nowocześniejsza nakładka na AWT, napisana w całości w Javie. Swing jest ładniejszy, bardziej elastyczny, wspiera mnóstwo dodatkowych możliwości i wygląda tak samo na wszystkich platformach (no, prawie). W obu przypadkach wszystkie elementy interfejsu (przyciski, pola tekstowe, checkboxy itd.) obsługują zdarzenia.

W tej lekcji będziemy używać Swing: jest prostszy dla początkujących i częściej spotykany w przykładach edukacyjnych. Przyjrzyjmy się, jak działa obsługa zdarzeń na przykładzie najpopularniejszego zadania — reakcji na kliknięcie przycisku.

Utworzenie przycisku

W Swing przycisk tworzy się tak:

JButton button = new JButton("Kliknij mnie!");

Tutaj „Kliknij mnie!” to napis na przycisku.

Dodawanie słuchacza

Aby reagować na kliknięcie, trzeba „zapisać” słuchacza na zdarzenie:

button.addActionListener(new MyActionListener());

Kim właściwie jest ten MyActionListener? To klasa, która implementuje interfejs ActionListener:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MyActionListener implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Przycisk został naciśnięty!");
    }
}

Kiedy użytkownik klika przycisk, Swing wywołuje metodę actionPerformed u wszystkich słuchaczy zarejestrowanych przez addActionListener.

Przykład w całości: proste okno z przyciskiem

Złóżmy to wszystko w całość i utwórzmy działającą aplikację:

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

public class SimpleButtonApp {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Przykład zdarzenia");
        JButton button = new JButton("Kliknij mnie!");

        // Dodajemy słuchacza do przycisku
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Przycisk został naciśnięty!");
                JOptionPane.showMessageDialog(frame, "Hurra! Przycisk naciśnięty!");
            }
        });

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(button);
        frame.setSize(300, 200);
        frame.setVisible(true);
    }
}

Co się tutaj dzieje:

  • Tworzymy okno (JFrame).
  • Tworzymy przycisk (JButton).
  • Dodajemy słuchacza (klasa anonimowa, aby nie mnożyć osobnych plików).
  • Wewnątrz actionPerformed wypisujemy komunikat w konsoli i pokazujemy okno dialogowe (JOptionPane).
  • Dodajemy przycisk do okna i pokazujemy okno użytkownikowi.

Spróbuj samodzielnie skopiować ten kod, uruchomić — i kliknąć przycisk!

2. Klasy anonimowe i wyrażenia lambda

We współczesnym kodzie Java pisanie osobnych klas dla jednego handlera to jak jechać czołgiem po chleb. Znacznie wygodniej użyć klas anonimowych albo wyrażeń lambda.

Klasa anonimowa

Widzieliśmy to już wyżej:

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // kod obsługi
    }
});

Tutaj klasa jest deklarowana w miejscu, bez nazwy.

Wyrażenie lambda (Java 8+)

Jeśli interfejs słuchacza jest funkcjonalny (czyli zawiera tylko jedną metodę abstrakcyjną), można użyć lambdy:

button.addActionListener(e -> {
    System.out.println("Przycisk naciśnięty za pomocą lambdy!");
});

Albo nawet krócej, jeśli ciało jest jednoliniowe:

button.addActionListener(e -> System.out.println("Lambda: przycisk naciśnięty!"));

To nie tylko skraca kod, ale i czyni go bardziej czytelnym. Fakt dla ciekawych: interfejs ActionListener jest funkcjonalny, ponieważ ma tylko jedną metodę actionPerformed.

4. Inne typy zdarzeń

Świat nie kończy się na przyciskach! W Swing i AWT prawie każdy element interfejsu obsługuje własne zdarzenia. Oto najpopularniejsze:

Zdarzenia myszy: MouseListener i MouseAdapter

Jeśli chcesz reagować na kliknięcia myszy, użyj MouseListener:

button.addMouseListener(new MouseListener() {
    @Override
    public void mouseClicked(MouseEvent e) {
        System.out.println("Kliknięcie myszą na przycisku!");
    }
    // Pozostałe metody można zostawić puste
    @Override public void mousePressed(MouseEvent e) {}
    @Override public void mouseReleased(MouseEvent e) {}
    @Override public void mouseEntered(MouseEvent e) {}
    @Override public void mouseExited(MouseEvent e) {}
});

Pisanie pięciu pustych metod dla jednego handlera nie jest wygodne. Do tego służy MouseAdapter:

button.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
        System.out.println("Kliknięcie myszą na przycisku (przez adapter)!");
    }
});

MouseAdapter implementuje wszystkie metody interfejsu, a ty nadpisujesz tylko potrzebne.

Zdarzenia klawiatury: KeyListener/KeyAdapter

Jeśli trzeba przechwytywać naciśnięcia klawiszy:

button.addKeyListener(new KeyAdapter() {
    @Override
    public void keyPressed(KeyEvent e) {
        System.out.println("Klawisz naciśnięty: " + e.getKeyChar());
    }
});

Zdarzenia zmiany tekstu: DocumentListener

Dla pól tekstowych (JTextField, JTextArea) istnieją specjalni słuchacze:

JTextField textField = new JTextField();
textField.getDocument().addDocumentListener(new DocumentListener() {
    @Override
    public void insertUpdate(DocumentEvent e) {
        System.out.println("Tekst zmieniony: " + textField.getText());
    }
    @Override public void removeUpdate(DocumentEvent e) {}
    @Override public void changedUpdate(DocumentEvent e) {}
});

Swoją drogą, dla większości zdarzeń są dostępne adaptery, aby nie pisać pustych metod.

5. Praktyka: mini-przykład z oknem i przyciskiem

Zróbmy najprostsze GUI: okno z przyciskiem, po którego naciśnięciu zwiększa się licznik i wyświetlany jest komunikat.

Przykład: licznik kliknięć

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

public class ClickCounterApp {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Licznik kliknięć");
        JButton button = new JButton("Kliknij mnie!");
        JLabel label = new JLabel("Kliknięć: 0");
        // Licznik kliknięć (musi być final lub effectively final)
        final int[] count = {0};

        button.addActionListener(e -> {
            count[0]++;
            label.setText("Kliknięć: " + count[0]);
        });

        frame.setLayout(new FlowLayout());
        frame.add(button);
        frame.add(label);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(250, 100);
        frame.setVisible(true);
    }
}

Wyjaśnienie:

  • Tworzymy okno, przycisk i etykietę do wyświetlania licznika.
  • Używamy tablicy jednoelementowej (final int[] count = {0};), aby obejść ograniczenie lambdy dotyczące zmiennych final/„effectively final”.
  • Za każdym kliknięciem zwiększamy licznik i aktualizujemy tekst etykiety.

Pomysł do praktyki: spróbuj zmieniać kolor przycisku przy każdym piątym naciśnięciu!

6. Jak działa model zdarzeń od środka

Zajrzyjmy na chwilę pod maskę. Gdy wywołujesz:

button.addActionListener(listener);

przycisk (obiekt JButton) dodaje twojego słuchacza do wewnętrznej listy. Gdy użytkownik klika przycisk, wewnątrz dzieje się następujące:

  1. Przycisk tworzy obiekt zdarzenia (ActionEvent).
  2. Przycisk iteruje po liście słuchaczy i wywołuje u każdego metodę actionPerformed.
  3. Twój handler wykonuje potrzebne działania (na przykład aktualizuje etykietę).

Najważniejsze: każde zdarzenie może mieć dowolną liczbę słuchaczy — możesz dodać kilka handlerów do jednego przycisku, zostaną wywołane po kolei.

7. Schemat wizualny: obsługa zdarzenia

graph TD
    A[Użytkownik kliknął przycisk] --> B{JButton}
    B --> C[Utwórz ActionEvent]
    C --> D[Wywołaj actionPerformed u wszystkich słuchaczy]
    D --> E[Obsługa wykonuje działania]

7. Typowe błędy przy pracy ze zdarzeniami w Swing i AWT

Błąd nr 1: zapomniano zarejestrować słuchacza. Przycisk nie reaguje na kliknięcia, ponieważ nie wywołano addActionListener.

Błąd nr 2: ciężka praca w obsłudze. Jeśli w handlerze zdarzenia uruchamiasz długą pętlę lub pobierasz coś z internetu, interfejs „zawiesi się”. Do długich zadań używaj oddzielnych wątków lub SwingWorker.

Błąd nr 3: próba zmiany zmiennej z lambdy bez final/„effectively final”. Wewnątrz lambdy można używać tylko zmiennych final lub „effectively final”. Do liczników używaj tablic lub specjalnych klas (AtomicInteger, jeśli chcesz na poważnie).

Błąd nr 4: zapomniano usunąć słuchacza. Jeśli usuwasz komponent, ale nie usuniesz słuchacza, możliwy jest wyciek pamięci. Usuwaj rejestracje, gdy komponent nie jest już potrzebny.

Błąd nr 5: nie zaimplementowano wszystkich metod interfejsu. Jeśli implementujesz na przykład MouseListener bezpośrednio, nie zapomnij o wszystkich metodach! Lepiej użyj adapterów (MouseAdapter, KeyAdapter).

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION