Utwórz projekt lab1 zgodnie z instrukcją IntelliJ IDEA. Swing jest w JDK — nie dodawaj osobnej biblioteki. W przykładzie Main dopisz pakiet zgodny z projektem.
Swing jest częścią standardowej biblioteki Javy. W laboratorium poznajemy JFrame, panele, układy, podstawowe kontrolki i obsługę zdarzeń. Zbudujemy prosty kalkulator oraz przygotujemy miejsce na kolejne widoki.
import javax.swing.*;
import java.awt.*;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Lab 1");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new BorderLayout(8, 8));
JLabel label = new JLabel("Witaj w Swing", SwingConstants.CENTER);
JButton button = new JButton("Kliknij");
button.addActionListener(event ->
label.setText("Przycisk został kliknięty"));
panel.add(label, BorderLayout.CENTER);
panel.add(button, BorderLayout.SOUTH);
frame.setContentPane(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
Kod interfejsu uruchamiamy w wątku zdarzeń Swing, czyli przez SwingUtilities.invokeLater. Układ (BorderLayout, GridLayout, BoxLayout) powinien odpowiadać strukturze widoku; unikaj ustawiania pozycji każdego komponentu za pomocą setBounds.
Kontener, np. JPanel, powierza menedżerowi układu obliczenie pozycji i rozmiarów swoich dzieci. Wybierz go według struktury widoku — nie ustawiaj współrzędnych każdego przycisku ręcznie.
FlowLayout układa elementy kolejno. Domyślnie wyśrodkowuje je w wierszu; w pokazanym przykładzie użyto FlowLayout.LEFT, więc nowy wiersz zaczyna się od lewej. Gdy brakuje szerokości, przenosi kolejny komponent do nowego wiersza; nie rozciąga go, by wypełnić wolne miejsce. To domyślny układ JPanel i dobry wybór dla krótkiego paska akcji.
FlowLayout.LEFT · pasek narzędziFioletowym tłem oznaczono pasek narzędzi — kontener z FlowLayout.LEFT. Przyciski „Dane”, „Wykres” i „Zapisz” są jego elementami.
Gdy zwężysz okno i przyciski nie zmieszczą się w jednym wierszu, „Zapisz” przejdzie na początek następnego wiersza, wyrównanego do lewej. Przyciski zachowają swój rozmiar; po poszerzeniu okna wrócą do jednego wiersza.
Przeciągnij uchwyt w prawym dolnym rogu okna, aby zmienić jego szerokość.
Dane miesięczne · 2025
Sprzedaż detaliczna
FlowLayout.LEFT
BoxLayout układa elementy wzdłuż jednej osi (X_AXIS albo Y_AXIS) i nie zawija ich do kolejnego wiersza. Rozmiar każdego dziecka zależy od jego minimalnego, preferowanego i maksymalnego rozmiaru. Użyj go np. dla pionowej listy kroków lub formularza.
BorderLayout dzieli kontener na NORTH, SOUTH, EAST, WEST i CENTER. Pasy przy krawędziach korzystają z rozmiaru preferowanego, a CENTER otrzymuje pozostałe miejsce. Do każdego obszaru dodaj jeden komponent; kilka kontrolek zgrupuj najpierw w osobnym JPanel.
GridLayout tworzy regularną siatkę równych komórek — nadaje się np. na klawiaturę kalkulatora. Ponieważ wszystkie pola są równe, zwykle nie jest dobrym wyborem do par etykieta–pole.
GridBagLayout pozwala tworzyć nierówne wiersze i kolumny. gridx/gridy wskazują pozycję, gridwidth/gridheight określają liczbę zajętych komórek, weightx/weighty dzielą dodatkowe miejsce, a fill mówi, czy komponent ma je wypełnić. Samo fill=HORIZONTAL nie sprawi, że kolumna dostanie dodatkową szerokość — ustaw też weightx.
JPanel form = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(4, 4, 4, 4);
c.gridx = 0; c.gridy = 0;
c.anchor = GridBagConstraints.LINE_END;
form.add(new JLabel("Zbiór danych:"), c);
c.gridx = 1;
c.weightx = 1.0;
c.fill = GridBagConstraints.HORIZONTAL;
form.add(new JTextField(20), c);
CardLayout przechowuje kilka paneli w tym samym obszarze i pokazuje jeden z nich. Przydaje się do przełączania np. widoku danych i analizy bez tworzenia kolejnych okien.
CardLayout cards = new CardLayout();
JPanel views = new JPanel(cards);
views.add(new JPanel(), "dane");
views.add(new JPanel(), "analiza");
// W listenerze menu:
cards.show(views, "analiza");
W praktycznym oknie łącz kilka menedżerów, zagnieżdżając panele — np. BorderLayout dla głównego widoku, GridLayout dla klawiatury i FlowLayout dla przycisków akcji. Otwórz galerię Swing, zmieniaj rozmiar okna i sprawdź, który element się zawija, rozciąga lub zachowuje swój rozmiar.
Unikaj setLayout(null) i ręcznego setBounds: takie okno źle reaguje na zmianę rozmiaru, skalowanie HiDPI i dłuższe napisy.
Najczęściej używane elementy to JLabel, JButton, JTextField, JTextArea, JCheckBox, JComboBox, JTable i JOptionPane.
JTextField first = new JTextField(10);
JTextField second = new JTextField(10);
JLabel result = new JLabel("Wynik");
JButton add = new JButton("Dodaj");
add.addActionListener(event -> {
try {
double a = Double.parseDouble(first.getText());
double b = Double.parseDouble(second.getText());
result.setText(String.valueOf(a + b));
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(null,
"Wprowadź poprawne liczby", "Błąd", JOptionPane.ERROR_MESSAGE);
}
});
W większej aplikacji warto oddzielić model danych i logikę obliczeń od kodu budującego widok. Sam listener powinien wywoływać metodę serwisu, a nie zawierać całą logikę biznesową.
Plik i pozycjami Nowy, Otwórz, Zapisz, Wyjdź. Każda pozycja ma wykonać działanie albo wyświetlić komunikat.C i =, nieedytowalnym wynikiem oraz historią. Wykonaj ćwiczenie „Responsywny kalkulator, HiDPI i dwa motywy” poniżej.JPanel rysujący szachownicę przez przesłonięcie paintComponent(Graphics). Użyj Graphics2D i fillRect.Student w JTable. Dodaj przyciski odświeżania, usuwania i filtrowania.JPanel od JFrame?| Etap | Praca | Punkt kontrolny |
|---|---|---|
| Podstawy GUI | okno, EDT, porównanie layoutów, menu i kalkulator | zmiana rozmiaru nie ukrywa przycisków, menu reaguje |
| Formularz | walidacja i CardLayout |
błąd nie kasuje wpisanych danych, menu przełącza widok |
| Dane i komponenty | tabela, filtrowanie, rysowanie i porządkowanie klas | usuwany jest właściwy rekord po sortowaniu |
Zadania z listy realizuj w tej kolejności. Rejestracja i lista studentów ćwiczą kontrolki; we własnym projekcie zastosuj je do obserwacji, zmiennych i zbiorów danych ekonomicznych.
flowchart TD
F[JFrame] -->|setJMenuBar| B[JMenuBar]
B --> P["JMenu: Plik"]
B --> W["JMenu: Widok"]
P --> N["JMenuItem: Nowy"]
P --> O["JMenuItem: Otwórz"]
P --> X["JMenuItem: Wyjdź"]
O -->|ActionListener| D[JFileChooser]
D -->|APPROVE_OPTION| S["serwis importu → odświeżenie tabeli"]
Fragment wstaw wewnątrz tworzenia okna, gdy zmienna frame już istnieje:
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("Plik");
JMenuItem open = new JMenuItem("Otwórz...");
open.setAccelerator(KeyStroke.getKeyStroke("control O"));
open.addActionListener(event -> {
JFileChooser chooser = new JFileChooser();
if (chooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) {
JOptionPane.showMessageDialog(frame,
"Wybrano: " + chooser.getSelectedFile().getName());
// W kolejnym etapie przekazujemy plik do serwisu importu.
}
});
JMenuItem exit = new JMenuItem("Wyjdź");
exit.addActionListener(event -> frame.dispose());
fileMenu.add(open);
fileMenu.addSeparator();
fileMenu.add(exit);
menuBar.add(fileMenu);
frame.setJMenuBar(menuBar);
Efekt: u góry okna jest pasek Plik; rozwinięcie pokazuje pozycje menu, a Otwórz uruchamia okno wyboru pliku. Anulowanie nie zmienia danych.


Ilustracje są renderami rzeczywistych komponentów Swing wygenerowanymi przez kod galerii, bez ramki systemowego okna. To nie makiety narysowane ręcznie. Galerię można uruchomić w IntelliJ, zmieniać rozmiar okna i klikać menu.
Model danych tabeli oddziel od widoku, np. przez AbstractTableModel. JTable umieść w JScrollPane. Po sortowaniu numer zaznaczonego wiersza jest indeksem widoku:
int selected = table.getSelectedRow();
if (selected >= 0) {
int modelRow = table.convertRowIndexToModel(selected);
// Usuń rekord z modelu pod modelRow, następnie zgłoś zmianę modelu.
}
W paintComponent najpierw wywołaj super.paintComponent(g). Rysuj według aktualnych getWidth() i getHeight(). Zmianę obrazu zlecaj przez repaint(), nie przez bezpośrednie wywołanie paintComponent.
Długie odczyty pliku i zapytania do bazy wykonuj przez SwingWorker: doInBackground() pracuje w tle, a done() aktualizuje GUI w EDT. Nie wywołuj blokującego get() w listenerze przed zakończeniem zadania.
Obowiązuje również macierz testów responsywności, HiDPI i motywów.
| Próba | Oczekiwany wynik |
|---|---|
| zmniejszenie i powiększenie okna | komponenty zachowują sensowny układ; tabela ma przewijanie |
puste pole, abc, dzielenie przez zero |
czytelny błąd bez zamknięcia aplikacji |
liczba 2,5 w kalkulatorze |
jawna obsługa polskiego separatora, np. zamiana przecinka na kropkę przed parsowaniem |
| sortowanie tabeli i usunięcie wiersza | znika wskazany obiekt, nie obiekt z innego indeksu |
| anulowanie wyboru pliku | dotychczasowy stan pozostaje |
Oddaj projekt Maven, krótki opis zastosowanych layoutów i po jednym zrzucie formularza, tabeli oraz rozwiniętego menu. Dodaj opis dwóch wykrytych błędów i sposobu ich obsługi. Zastąp dane studentów obserwacjami ekonomicznymi i pokaż średnią wybranej kolumny.
Rozwijaj kalkulator w kolejnych etapach. Nie jest to drugi projekt: dodajesz adaptacyjny układ, styl i testy do tej samej aplikacji.
GridLayout, a historię w przewijanym panelu. Przyciski mają wypełniać dostępne komórki.flowchart TD
R[Zmiana szerokości obszaru treści] --> Q{Szerokość poniżej progu?}
Q -->|tak| N["Wynik → klawiatura → historia poniżej lub zwinięta"]
Q -->|nie| W["Wynik u góry; klawiatura i historia obok siebie"]
N --> M[Ten sam model obliczeń i historii]
W --> M
W ComponentAdapter.componentResized porównaj szerokość panelu z progiem. Zmieniaj układ tylko wtedy, gdy zmienia się wariant wąski/szeroki, po czym wywołaj revalidate() i repaint(). Nie wykonuj pack() przy każdym zdarzeniu — użytkownik powinien zachować kontrolę nad rozmiarem okna.
W tym ćwiczeniu użyj FlatLaf, który udostępnia jasny i ciemny wygląd oraz obsługę HiDPI. Dodaj do dependencies projektu kalkulatora poniższą ustaloną wersję i zsynchronizuj Maven. Podstawowy Swing i wcześniejsza galeria nadal nie wymagają tej biblioteki. Dokumentacja FlatLaf.
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.7</version>
</dependency>
Przed utworzeniem komponentów w EDT ustaw początkowy motyw przez FlatLightLaf.setup() albo FlatDarkLaf.setup(), zgodnie z zapisaną preferencją. Poniższą metodę umieść w klasie pomocniczej i wywołuj z listenera menu, czyli również w EDT:
static void setDarkTheme(boolean dark) {
boolean installed = dark
? com.formdev.flatlaf.FlatDarkLaf.setup()
: com.formdev.flatlaf.FlatLightLaf.setup();
if (!installed) {
throw new IllegalStateException("Nie udało się zmienić motywu");
}
com.formdev.flatlaf.FlatLaf.updateUI();
java.util.prefs.Preferences.userRoot()
.node("pl/uczelnia/lab1/calculator").putBoolean("dark", dark);
}
Nie ustawiaj na każdej kontrolce stałego Color.WHITE ani Color.BLACK. Własne rysowanie odczytuje aktualne kolory z UIManager, np. Panel.background i Label.foreground; nie przechowuj starego koloru na zawsze. Dodatkowe role kolorów definiuj osobno dla obu motywów. Konfiguracja kolorów FlatLaf.
Odbiór ćwiczenia: kalkulator działa w obu układach i motywach, zachowuje stan, ma czytelne przyciski i wynik przy 200%, a historia i błędy są dostępne także w najmniejszym obsługiwanym oknie. Do oddania dołącz macierz testów i cztery zrzuty zgodnie ze wspólną instrukcją.