Jason. Cała informatyka w jednym miejscu!

I to już będzie ostatnia część na dzisiejszy dzień, zatem powracamy do tego samego pytania: "jak zaprogramować zdarzenie do przycisku tak, aby po jego naciśnięciu coś się wydarzyło?". Dłuży się to niemiłosiernie, natomiast trzeba aż tak wielu wyjaśnień, aby korzystać z tego świadomie i rozsądnie. Znamy już klasy wewnętrzne, ich budowę, dowiemy się teraz jak mogą nam pomóc w rozwiązaniu problemu. Samo tworzenie zdarzeń w "Swing" określane jest jako "obsługa zdarzeń przycisków" tak jak to mieliśmy z wyjątkami. Przechodzimy do tematu.

OBSŁUGA ZDARZEŃ PRZYCISKÓW W "SWING". PREZENTUJĘ DWA ROZWIĄZANIA!

Pierwszym z dwóch rozwiązań będzie umieszczenie klasy wewnętrznej posiadającej metodę, która z kolei będzie wywoływana po naciśnięciu przycisku. Tylko jest mały niuans: tutaj cały czas nawijam o klasach wewnętrznych, a ostatni raz gdy było o bibliotece "Swing", to nie wspomniałem od czego się to w ogóle zaczyna! Moi drodzy, od zaimplementowania interfejsu "ActionListener" do danej klasy.

Gdy mamy do czynienia tylko z jednym przyciskiem, to mogliśmy sobie darować to całe rozgrzebywanie tematu klas wewnętrznych. Domyślam się jednak, że 99% Waszych programów będzie wymagało więcej niż jednego przycisku. CHWILA! Przecież w Javie możemy dorzucić tyle interfejsów ile chcemy! Zgadza się, ale nie w tym tkwi problem. Problem polega na tym, że interfejs o tej samej nazwie, może obsługiwać TYLKO JEDEN przycisk na interfejs a to z kolei oznacza, że możemy "podpiąć" tylko jeden przycisk na klasę. O to tutaj chodzi!!!

PRZYKŁAD KODU ŹRÓDŁOWEGO

W porządku, jeśli nie pokapowaliście do końca, to może spróbujcie na to spojrzeć z programistycznego punktu widzenia. Obsługa zdarzeń dwóch przycisków na jednej "ramce" czyli oknie i przedstawienie implementacji interfejsów we właściwe miejsca. Wyjaśnienia wszystkich nowości zostawiam poniżej:

KLASA "MAIN"

public class Main {
	public static void main(String[] args) {
		new SwingButton();
	}
}

KLASA "SWINGWINDOW"

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

public class SwingButton extends JFrame implements ActionListener {
	private JButton buttonA, buttonB;

	public SwingButton() {
		createInstances();
		addButtonListeners();
		addToFrame();
		configure();
	}

	private void createInstances() {
		buttonA = new JButton("Kliknij mnie!");
		buttonB = new JButton("Najpierw mnie!");
	}

	private void addButtonListeners() {
		buttonA.addActionListener(this);
		buttonB.addActionListener(new ButtonBListener());
	}

	private void addToFrame() {
		Container pane = getContentPane();

		pane.add(BorderLayout.NORTH, buttonA);
		pane.add(BorderLayout.SOUTH, buttonB);
	}

	private void configure() {
		setTitle("Obsługa zdarzeń przycisków w Swing");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setVisible(true);
		setResizable(false);
		setSize(512, 256);
		setLocationRelativeTo(null);
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		System.out.println("PRZYCISK A ZOSTAŁ KLIKNIĘTY!");
	}

	private class ButtonBListener implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			System.out.println("PRZYCISK B ZOSTAŁ KLIKNIĘTY!");
		}
	}
}

Do całej operacji potrzeba szeregu importów widocznych na górze, których nie ma sensu maglować i tutaj. Obsługa zdarzeń sama w sobie nie wymaga aż tak wiele. Jak powiedziałem, interfejs "ActionListener", który żąda zdefiniowania metody "actionPerformed", a w środku niej podajecie instrukcje. A jak połączyć jedno z drugim? Teraz mogę Wam zdradzić. Za pomocą metody "addActionListener" poprzez instancję przycisku. Za parametr podajecie klasę implementującą wyżej wymieniony interfejs albo tworzycie nową, taka sytuacja również została przedstawiona powyżej.

Jeszcze jeden znak zapytania może powstać u Was patrząc na metodę "addToFrame". Aby uniknąć ciągłego odwoływania się do metody "add" poprzez "getContentPane" (tak, to jest konieczne w przypadku klasy "JFrame") można zostawić sobie zmienną lokalną przechowującą referencję typu "Container". Dalej, co to "BorderLayout.NORTH" i "BorderLayout.SOUTH" (składowe typu wyliczeniowego)? To są dwa z kilku dostępnych obszarów układu "BorderLayout", których w całym układzie jest pięć.

Tak się rozwiązywało problem przed wprowadzeniem Javy 8. Obsługa zdarzeń wymagała niekonwencjonalnego podejścia. A jak wygląda to teraz? Mamy klasy wewnętrzne, a teraz wyrażenie lambda przybliży nas do celu. Gdy zobaczycie jak je zastosować do przycisku, nie będę musiał się nawet pytać która z metod wydaje się Wam przyjemniejsza. Bez ceregieli. Obsługa zdarzeń za pomocą wyrażenia lambda!

OBSŁUGA ZDARZEŃ PRZYCISKÓW W "SWING" "PO NOWEMU"

Wyrażenia lambda są doskonałe, jeśli chodzi o konieczność dodania zdarzenia nie tylko do przycisku, ale do każdego komponentu biblioteki "Swing". Dzięki skróconej formie zapisu, funkcja anonimowa pozwala uniknąć tworzenia dodatkowych odrębnych klas tylko dla tego jedynego przypadku. Tam, gdzie trzeba było implementować konkretny interfejs, tutaj nie trzeba żadnych implementacji! Funkcja sama się "zaopatrzy" w konieczny interfejs przez co nasz kod się pięknie skróci.

Obsługa zdarzeń dzięki wyrażeniom lambda jest bardzo przyjemna, bo kończy się tylko na dołożeniu funkcji tylko w jednym miejscu. Nadal trzeba korzystać z metody "addActionListener", natomiast za parametr podajemy nasze wyrażenie lambda zamiast klasy implementującej interfejs.

PRZYKŁAD KODU ŹRÓDŁOWEGO

Nie ma co się dalej rozpisywać. Obsługa zdarzeń dwóch przycisków na jednej "ramce" pełniącej rolę okna aplikacji, ale tym razem z użyciem samych wyrażeń lambda. Porównajcie sobie ten kod z powyższym i zobaczcie jaka jest różnica obszerności:

KLASA "MAIN"

public class Main {
	public static void main(String[] args) {
		new SwingButton();
	}
}

KLASA "SWINGWINDOW"

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

public class SwingButton extends JFrame {
	private JButton buttonA, buttonB;

	public SwingButton() {
		createInstances();
		addButtonListeners();
		addToFrame();
		configure();
	}

	private void createInstances() {
		buttonA = new JButton("Kliknij mnie!");
		buttonB = new JButton("Najpierw mnie!");
	}

	private void addButtonListeners() {
		buttonA.addActionListener(e -> System.out.println("PRZYCISK A ZOSTAŁ KLIKNIĘTY!"));
		buttonB.addActionListener(e -> {
			System.out.println("PRZYCISK B ZOSTAŁ KLIKNIĘTY!");
			buttonA.setEnabled(false);
		});
	}

	private void addToFrame() {
		Container pane = getContentPane();

		pane.add(BorderLayout.NORTH, buttonA);
		pane.add(BorderLayout.SOUTH, buttonB);
	}

	private void configure() {
		setTitle("Obsługa zdarzeń przycisków w Swing");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setVisible(true);
		setResizable(false);
		setSize(512, 256);
		setLocationRelativeTo(null);
	}
}

Widzicie różnice? Po pierwsze, połowa importów mniej. Po drugie, żadnych interfejsów wymagających zaimplementowania. Po trzecie, żadnych klas wewnętrznych umieszczanych w innym miejscu. Wszystko znajduje się tylko tam, gdzie jest to konieczne. Obsługa zdarzeń od razu staje się prostsza! Zmieniłem tylko zdarzenie przycisku B dodając do komunikatu metodę kontrolującą aktywność przycisku A, metodę "setEnabled". W tym przypadku podałem "false", a z praktycznego punktu widzenia spowoduje to "wyłączenie" drugiego przycisku (nie utożsamiać z metodą "setVisible", która kontroluje widoczność przycisku!). Chciałem Wam pokazać jak wygląda ta wersja "klamerkowa" używana dla dwóch lub więcej instrukcji. Taka forma jest jak najbardziej poprawna, o ile piszemy używając Javy co najmniej w wersji 8.


Teraz dopiero ogłaszam koniec tego długiego tematu. Dzięki za uwagę.

PODOBNE ARTYKUŁY