Laboratorium 2

Podstawy tworzenia GUI - okienka, komponenty, obsługa przycisku.

 
Graficzny interfejs użytkownika czyli GUI (Graphical User Interface)

 

Jedną z charakterystycznych cech języka Java jest możliwość łatwego tworzenia graficznego interfejsu użytkownika (GUI). Wykorzystuje się do tego dwa podstawowe pakiety: java.awt i javax.swing.

Podstawowym obiektem, z którego zbudowane jest GUI jest okno, w którym umieszczone są wszystkie pozostałe elementy.

W pakiecie javax.swing klasą implementującą okno jest JFrame (https://docs.oracle.com/javase/8/docs/api/javax/swing/JFrame.html)

 

Przykład 1. Pierwsze okno

W środowisku Eclipse stwórz projekt pojava i dodaj do niego paczkę lab2 a w niej stwórz klasę o nazwie Example1 posiadającą metodę main(). Wewnątrz metody main() wpisz kolejno:

JFrame frame = new JFrame(); 
frame.setSize(640, 480); 
frame.setVisible(true);

Uruchomienie takiego programu spowoduje pojawienie się pustego okna o zadanym rozmiarze, jednak kliknięcie przycisku [X] nie spowoduje zatrzymania programu. Będzie to tematem pierwszego zadania.

 

Zadanie A - Dziedziczenie po JFrame. (1 pkt)

W środowisku Eclipce stwórz projekt pojava i dodaj do niego paczkę lab2, a w niej stwórz klasę o nazwie CloseableFrame, dziedziczącą po JFrame, zawierającą metodę main() oraz definicje konstruktorów z klasy JFrame. Poprawnie utworzona klasa powinna wyglądać tak:

public class CloseableFrame extends JFrame {
    public CloseableFrame() throws HeadlessException {
    }
    public CloseableFrame(GraphicsConfiguration gc) {
        super(gc);
    }
    public CloseableFrame(String title) throws HeadlessException {
        super(title);
    }
    public CloseableFrame(String title, GraphicsConfiguration gc) {
        super(title, gc);
    }
    public static void main(String[] args) {
    }
}

We wszystkich konstruktorach klasy CloseableFrame ustaw domyślny rozmiar okna jako (640,480) oraz dodaj instrukcję, która spowoduje zwolnienie zasobów i zatrzymanie programu po zamknięciu okna:

this.setSize(640,480);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);

Wewnątrz metody main() stwórz obiekt klasy CloseableFrame (korzystając z bezargumentowego konstruktora) i uczyń go widocznym.

Metoda super() widoczna wewnątrz konstruktorów służy do wywołania konstruktora klasy nadrzędnej (w tym wypadku JFrame). Znajduje się ono zawsze na początku konstruktora, dzięki czemu można po nim dodać dalszy kod, który sprawi, że klasa CloseableFrame będzie rozszerzeniem klasy JFrame.

 


Komponenty i layout managery

 

Samo okienko jest zaledwie ramką, w której umieszcza się kolejne komponenty, takie jak przyciski, etykiety, pola, listy rozwijane itp. Wymienione elementy można umieszczać bezpośrednio w oknie lub w specjalnym kontenerze implementowanym przez klasę JPanel z pakietu javax.swing.

 

Przykład 2. Umieszczanie komponentów bezpośrednio w oknie

W paczce lab2 stwórz klasę o nazwie Example2 posiadającą metodę main(). Wewnątrz metody main() wpisz kolejno:

CloseableFrame frame = new CloseableFrame();
		
JButton button1 = new JButton("Przycisk 1");
frame.add(button1);
		
JButton button2 = new JButton("Przycisk 2");
frame.add(button2);

frame.setVisible(true);

Po uruchomieniu programu powinno być widoczne okno z jednym dużym przyciskiem pokrywającym całe okno.  Dzieje się tak ponieważ do pozycjonowania obiektów w oknie używany jest tzw. layout manager, a domyślny layout manager dla klasy JFrame nazywa się BorderLayout, który przy najprostszym wywołaniu metody add(Component c) umieszcza komponent w centrum ramki i rozciąga go na całe okno. Dodanie drugiego przycisku w ten sam sposób spowoduje zasłonięcie pierwszego. BorderLayout wyróżnia 5 obszarów okna: górny (PAGE_START), dolny (PAGE_END), lewy (LINE_START), prawy (LINE_END) i centralny (CENTER), który jest obszarem domyślnym (zobacz https://docs.oracle.com/javase/tutorial/uiswing/layout/border.html). Aby komponenty nie przekrywały się, konieczne  jest przekazanie parametru do metodyadd(Component c, int index), który będzie informował, w którym obszarze ma zostać umieszczony komponent. Zmodyfikuj metodę main() w klasie Example2:

CloseableFrame frame = new CloseableFrame();
		
JButton button1 = new JButton("Przycisk 1");
frame.add(button1, BorderLayout.PAGE_START);
		
JButton button2 = new JButton("Przycisk 2");
frame.add(button2, BorderLayout.PAGE_END);

frame.setVisible(true);

Rodzaj rozmieszczenia komponentów można zmienić metodą setLayout(), podając jako argument jeden z dostępnych layout managerów. FlowLayout układa elementy jeden obok drugiego, opcjonalnie w kilku rzędach. Aby przetestować jego działanie można umieścić w oknie więcej elementów, np. etykietę z tekstem JLabel lub pole do wpisywania tekstu JTextField. Dodaj kolejne instrukcje do metody main() w klasie Example2:

JLabel label = new JLabel("To jest etykieta");
frame.add(label);
		
JTextField field = new JTextField("A to pole tekstowe");
frame.add(field);
		
frame.setLayout(new FlowLayout());

GridLayout jest bardzo wygodnym managerem, który dzieli okno na siatkę u ustalonej liczbie kolumn i wierszy. Każda komórka takiej siatki ma identyczny rozmiar.  W powyższym przykładzie zmień managera layoutu z FlowLayout na GridLayout(2,2) lub GridLayout(4,1). Prosty tutorial dotyczący layout managerów można znaleźć na stronie https://docs.oracle.com/javase/tutorial/uiswing/layout/index.html.

Więcej informacji na temat JLabel i JTextField oraz pełny opis ich metod można znaleźć w dokumentacji https://docs.oracle.com/javase/8/docs/api/.

Uwaga

Zwrot "umieszczanie komponentów bezpośrednio w oknie" jest skrótem myślowym. Uproszczenie to wynika z tego, że komponenty dodaje się do instancji klasy JFrame metodą add(). W rzeczywistości komponenty są umieszczane w kontenerze, który jest dodawany do okna JFrame już w momencie jego inicjalizacji. Kontenerem tym jest instancja klasy Container z biblioteki java.awt (jest to klasa nadrzędna względem klasy JPanel). Aby odwołać się do tego kontenera, np. w celu zmiany koloru tła okna, należy posłużyć się metodą getContentPane().

Dodaj poniższą instrukcję do metody main() klasy Example2 (aby efekt był widoczny trzeba ustawić FlowLayout w ramce):

frame.getContentPane().setBackground(Color.blue);

 

Przykład 3. Umieszczanie komponentów wewnątrz panelu

W praktyce bardzo rzadko umieszcza się komponenty bezpośrednio w oknie, które w zamyśle autorów pakietu ma być tylko ramką. Właściwszą metodą jest uprzednie dodanie do okna panelu (JPanel) pełniącego rolę kontenera, w którym następnie umieszcza się elementy. W jednej ramce może być wiele paneli, a każdy może mieć swój własny layout.

W paczce lab2 stwórz klasę o nazwie Example3 posiadającą metodę main(). Wewnątrz metody main() należy umieścić instrukcje:

CloseableFrame frame = new CloseableFrame();
frame.setLayout(new GridLayout(1,2));
		
JPanel panel1 = new JPanel();
panel1.setBackground(Color.white);
frame.add(panel1);
		
JPanel panel2 = new JPanel();
panel2.setBackground(Color.black);
frame.add(panel2);
		
JLabel leftLabel = new JLabel("Lewy panel");
panel1.add(leftLabel);
JLabel rightLabel = new JLabel("Prawy panel");
rightLabel.setForeground(Color.white);
panel2.add(rightLabel);

frame.setVisible(true);

Wykorzystano tutaj wygodny GridLayout, który pionowo podzielił ramkę na dwie równe części. W każdej połowie okna umieszczono jeden panel, a w każdym panelu jedną etykietę. Domyślnym layoutem obiektu klasy JPanel jest FlowLayout. Więcej informacji można znaleźć w dokumentacji https://docs.oracle.com/javase/8/docs/api/javax/swing/JPanel.html.

 

Przykład 4. Proste figury geometryczne wewnątrz panelu

Nadpisując metodę paintComponent() można umieszczać na panelu proste figury geometryczne o zadanym rozmiarze i kolorze.

W paczce lab2 stwórz klasę o nazwie DrawablePanel  dziedziczącą po klasie JPanel i posiadającą metodę main(). Dodaj do nowej klasy metodę paintComponent() zdefiniowaną poniżej. Wewnątrz metody main() zadeklaruj obiekt klasy DrawablePanel i umieść go wewnątrz okna CloseableFrame.

public void paintComponent(Graphics g) {
	super.paintComponent(g);
		
	g.setColor(Color.red);
        g.fillRect(50, 50, 150, 100);
		
	g.setColor(Color.blue);
 	g.fillOval(250, 250, 150, 150);
		
}
public static void main(String[] args) {
		
	CloseableFrame frame = new CloseableFrame();
		
	DrawablePanel panel = new DrawablePanel();
	panel.setBackground(Color.white);
	frame.add(panel);
  
        frame.setVisible(true);
}

Klasa JPanel posiada metodę paintComponent(), zatem definicja tej metody w klasie DrawablePanel "nadpisuje" oryginalną definicję. Schemat działania jest tutaj taki sam jak przy konstruktorach.

Jeśli jakaś metoda w klasie dziedziczącej ma rozszerzać metodę klasy oryginalnej to konieczne jest wywołanie oryginalnej metody przy użyciu instrukcji super.paintComponent(g) na początku definicji nowej metody.

W tym konkretnym przykładzie rozszerzona metoda paintComponent() rysuje na panelu dwie figury geometryczne: czerwony prostokąt i niebieskie koło.

Rysowanie odbywa się za pośrednictwem klasy Graphics, która jest automatycznie przekazywana do metody paintComponent().

 

Zadanie B - Figury w losowych kolorach. (2 pkt)

W paczce lab2 stwórz klasę ThreeShapesPanel, dziedziczącą po klasie JPanel i posiadającą metodę main(). Dodaj do nowej klasy metodę paintComponent i zdefiniuj ją w ten sposób aby w panelu rysowane były trzy różne figury geometryczne o różnych, losowych kolorach. W tym celu odszukaj w dokumentacji klasę służącą do generowania liczb losowych.

Zmodyfikuj metodę main() w ten sposób, aby w oknie CloseableFrame umieszczone były dwa panele tej samej wielkości, jeden klasy JPanel a drugi ThreeShapesPanel (i zajmowały całą powierzchnię ramki). W panelu klasy JPanel umieść dowolne cztery komponenty (guziki, labelki, textfieldy...) ustawione jeden nad drugim. Upewnij się, że kolory figur nie zmieniają się podczas zmieniania rozmiaru okna.

 


Obiekty nasłuchujące zdarzeń

 

Jednym z komponentów zaprezentowanych w Przykładzie 2 był JButton. Jednak samo dodanie przycisku do panelu nie powoduje automatycznie, że będzie on działał zgodnie z wolą programisty. Kliknięcie przycisku jest pewnym zdarzeniem wywołanym przez użytkownika i aby to zdarzenie wywołało jakąkolwiek reakcję muszą być spełnione dwa warunki:

1. Program musi wiedzieć o tym, że ma oczekiwać na kliknięcie przycisku. Innymi słowy potrzebny jest obiekt nasłuchujący zdarzeń (listener) na danym przycisku.

2. Obiekt nasłuchujący musi mieć podane uprzednio instrukcje, które ma wykonać w przypadku kliknięcia w przycisk.

Obiektami nasłuchującymi w Javie są tzw. listenery. Przykładowo, aby obsłużyć zdarzenie kliknięcia na przycisk, należy podłączyć do niego instancję interfejsu ActionListener.

 

Przykład 5. Obsługa przycisku

W paczce lab2 stwórz klasę o nazwie Example5 posiadającą metodę main(). Wewnątrz metody main() należy umieścić instrukcje:

CloseableFrame frame = new CloseableFrame();
JPanel panel = new JPanel();
frame.add(panel);

JButton exitButton = new JButton("Zakończ"); 
ActionListener exitListener = new ActionListener() {
	@Override
	public void actionPerformed(ActionEvent arg0) {
		System.exit(0);
				
	}	
};
exitButton.addActionListener(exitListener);
panel.add(exitButton);

frame.setVisible(true);

Interfejs ActionListener można definiować i inicjować w różny sposób i w różnych miejscach programu. Powyższy przykład prezentuje użycie tzw. klasy anonimowej, czyli takiej która zostaje zdefiniowana "w locie", w momencie jej inicjalizacji. Instrukcje, które mają zostać wykonane w następstwie kliknięcia na przycisk muszą być umieszczone wewnątrz metody actionPerformed(). Temat interfejsów i listenerów jest szerzej omówiony podczas Laboratorium 3.

 

Zadanie C - Okno z trzema przyciskami. (2 pkt)

W istniejącej paczce lab2 stwórz klasę o nazwie ThreeButtonFrame, dziedziczącą po JFrame, zawierającą metodę main() oraz definicje konstruktorów z klasy JFrame. W konstruktorze nowej klasy zainicjuj trzy przyciski i dodaj je do ramki ThreeButtonFrame. Każdy z przycisków powinien wykonywać inną akcję (np. zamykać program, zmieniać tytuł okna, tekst na etykiecie lub kolor jakiegoś elementu). Wewnątrz metody main() zainicjuj obiekt klasy ThreeButtonFrame i uczyń go widocznym.