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:
- Przycisk tworzy obiekt zdarzenia (ActionEvent).
- Przycisk iteruje po liście słuchaczy i wywołuje u każdego metodę actionPerformed.
- 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).
GO TO FULL VERSION