Obsługa zdarzeń - Klawiatura

Kontynuując rozważania na temat przechwytywania akcji generowanych przez użytkownika naszego programu przejdźmy do obsługi klawiatury. Do tej pory w naszych programach interakcja polegała co najwyżej na tym, że odbieraliśmy w konsoli jakieś proste dane od użytkownika i zatwierdzaliśmy je enterem. W konsoli bardziej nie poszalejemy, jednak w aplikacji okienkowej można do naszego programu dodać już sporo funkcjonalności. Możemy w czasie rzeczywistym odczytywać to co wciska na klawiaturze użytkownik, a dzięki temu stworzyć na przykład menu, które będzie się zmieniało po wciśnięciu spacji, lub kombinacji klawiszy. Możliwości takie daje nam interfejs KeyListener posiadający trzy metody, które będziemy musieli przeciążyć, gdy nasza klasa go implementuje:
  • keyPressed(KeyEvent arg) - wywoływana, gdy wciśniemy przycisk
  • keyReleased(KeyEvent arg) - wywoływana, gdy puścimy przycisk
  • keyTyped(KeyEvent arg) - w zasadzie to samo co keyPressed, tyle, że obsługuje jedynie klawisze posiadające odpowiednik Unicode. Nie zareaguje np. na przyciski funkcyjne.
Każda z nich otrzymuje argument w postaci obiektu klasy KeyEvent. Przechowuje on różne cenne informacje, które pomogą nam w obsłudze konkretnego zdarzenia. Możemy z niego odczytać m.in. znak klawisza, kod tego znaku w kodzie ASCII. KeyEvent przechowuje również stałe dla klawiszy standardowej klawiatury qwerty. Oczywiście słuchacza, podobnie jak w przypadku obsługi standardowych akcji musimy powiązać z obiektem generującym jakieś akcje. Robimy to używając metody addKeyListener(Keylistener arg). Łatwiej będzie to wszystko zobaczyć na przykładzie. Napiszmy więc bardzo prosty program składający się tylko z ramki JFrame oraz klasy ją tworzącej. Ramka będzie odpowiedzialna zarówno za nasłuch, jak i obsługę wprowadzanych danych. Zadaniem programu będzie zmiana swojej etykiety, jeśli użytkownik wprowadzi sekretne hasło, brzmiące secret.
import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JFrame;

public class KeyTest extends JFrame implements KeyListener {

	private int counter = 0;
	private String userInput = "";
	private final String secret = "secret";

	public KeyTest() {
		super("KeyListener Test");
		setPreferredSize(new Dimension(300, 100));
		addKeyListener(this);

		pack();
		setVisible(true);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	@Override
	public void keyPressed(KeyEvent evt) {
	}

	@Override
	public void keyReleased(KeyEvent evt) {
		char c = evt.getKeyChar();
		if(counter < secret.length())
			checkSecret(c);
	}

	@Override
	public void keyTyped(KeyEvent evt) {

	}

	private void checkSecret(char c) {
		if(c == secret.charAt(counter)) {
			counter++;
			userInput = userInput+c;
		}
		else {
			counter = 0;
			userInput = "";
		}

		if(userInput.equals(secret))
			setTitle("Sekretne hasło");
	}
}
Nasza klasa ramki ma być słuchaczek musi więc implementować interfejs KeyListener. Co się z tym wiąże przeciążamy w niej wspomniane na początku metody. Oczywiście możemy je równie dobrze pozostawić puste, dodać implementację tylko do jednej z nich, lub obsługiwać wszystkie zdarzenia. My wykorzystamy tylko jedną z nich - w naszym przypadku najlepsza będzie keyReleased, ponieważ użytkownik może przez pomyłkę przytrzymać klawisz, a w takim wypadku metody keyPressed i keyTyped byłyby wywoływana kilka razy. Na początku utworzono trzy zmienne:
  • counter - liczy, na którym znaku hasła się znajdujemy. Jeśli wprowadzona litera nie odpowiada tej w haśle, całą sekwencję "secret" trzeba wprowadzić od początku, zmienna counter jest ustawiana na 0.
  • userInput - string budowany na bieżąco podczas wciskania kolejnych klawiszy przez użytkownika. Podobnie jak w przypadku zmiennej counter, jest zerowana, gdy użytkownik wprowadzi niepoprawną literę.
  • secret - nasze sekretne hasło, wzorzec
W metodzie keyReleased możemy pobrać z obiektu KeyEvent jaki znak został wprowadzony. Odbywa się to przy użyciu metody getKeyChar(). Jeżeli licznik jest mniejszy od długości hasła to sprawdzamy, czy jest to kolejna litera z hasła w metodzie checkSecret, przekazując jej odpowiedni znak. Przy sprawdzaniu istotne jest to, że Stringi są tablicami znaków, więc znaki są liczone od indeksu 0, co jest istotne przy używanej przez nas metodzie CharAt(int index). Na samym końcu metody checkSecret() dodano jeszcze porównanie sprawdzające, czy użytkownik wprowadził już całe poprawne hasło, jeśli tak, to wywołujemy metodę setTitle() naszej ramki i ustawiamy etykietę na "Sekretne hasło".
import java.awt.EventQueue;

public class Test {
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			@Override
			public void run() {
				new KeyTest();
			}
		});
	}
}
I jeszcze klasa, w której stworzyliśmy naszą ramkę. Z lekcji tej należy więc zapamiętać, że gdy chcemy odczytywać to co użytkownik wprowadza z klawiatury musimy najpierw utworzyć obiekt nasłuchujący takie zdarzenia, który musi implementować interfejs KeyListsnser. Najczęściej będzie to wyglądało po prostu tak, że tworzymy nową klasę rozszerzającą jakiś komponent i dodatkowo dodajemy do niego nasłuch na samego siebie (na przykład walidacja podczas wprowadzania danych przez użytkownika w polu tekstowym). Implementując interfejs KeyListener musimy przeciążyć jego trzy metody, które udostępnią nam obiekt KeyEvent dostarczający informacje o wciskanych klawiszach. Zadania do samodzielnego wykonania: 4.3 Przepisz powyższy przykład tak, aby działanie pozostało niezmienione, ale nie możesz wykorzystać metody keyReleased().

<- Poprzednia Lekcja | Następna Lekcja ->

Komentarze

Paweł

program chyba nie działa?

Slawek

Chyba jednak działa :)

janek

@Override public void keyTyped(KeyEvent evt) { char c = evt.getKeyChar(); if(counter < secret.length()) checkSecret(c); }

Jarek

Działa, jak najbardziej. Mam krótkie pytanko: czy muszę pisać to @Override, bo i bez tego program się kompiluje i uruchamia ?

Slawek

Nie musisz. W eclipse jest to po prostu przydatne bo chroni przed błędami. Adnotacja Override oznacza, że przesłaniamy metodę klasy z której dziedziczymy, chroni to przed literówkami, oraz pomaga czytać kod osobie, która wcześniej go nie widziała. Dla próby spróbuj sobie dodać taką adnotację nad metodą napisaną przez siebie w dowolnej klasie np public class Test { @Override public void asdf() { //coś tam } } Eclipse pokaże błąd, ponieważ zauważy, że nad klasa (w tym przypadku Object) nie posiada metody asdf().

Jacek

Mam problem: - pisze mi, że nie odnaleziono metody 'main' Jak mam napisać tą metodę? :) public static void main(String[] args) { } tyle wiem ale nie wiem jak napisać zawartość :D

agachocz

Czy panel JPanel też może być słuchaczem?

Slawek

JPanel nie może być słuchaczem. Możesz stworzyć klasę, która rozszerza JPanel i implementuje jakiegoś Listenera - wtedy twoja klasa będzie słuchaczem.

l3wy

Przecież możesz stworzyć obiekt typu JPanel, a następnie dodać do niego KeyListener za pomocą funkcji addKeyListener... JPanel panel = new JPanel(); panel.addKeyListener(new KeyListener() { ... } );

qm4r

Spróbuj po g2d.drawLine(0, 0, 1200, 1200); dodać repaint(); Jeśli nie zadziała, to wklej cały kod. Coś wymyślimy :)

Robert

Skoro używamy tylko metody keyReleased() to czy muszę przeciążać pozostałe dwie? Nie mogę ich po prostu pominąć?

Sławek Ludwiczak

implementując interfejs musisz zaimplementować wszystkie metody danego interfejsu. Można to ominąć korzystając z klas adapterów, KeyAdapter, MouseAdapter itp. Minus jest taki, że nie możesz wtedy dziedziczyć już z innej klasy.

Kostek

Dlaczego kiedy łącze program o wykrywaniu hasła razem z programem o przyciskach to całość nie działa. package pl.Przyciski; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import javax.swing.JFrame; import javax.swing.JPanel; public class Frame extends JFrame implements KeyListener { private final String secret="marian"; private String wyraz=""; private int index=0; public Frame(){ super("Buttony plus klawisze"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(300,300); setVisible(true); addKeyListener(this); JPanel Panel = new ButtonPanel(); add(Panel); } @Override public void keyPressed(KeyEvent evt) { } @Override public void keyTyped(KeyEvent evt) { } @Override public void keyReleased(KeyEvent evt) { char c=evt.getKeyChar(); if(index<secret.length()) checkSecret(c); } private void checkSecret(char c){ if(c==secret.charAt(index)){ index++; wyraz=wyraz+c; System.out.println("dobrze"); } else{ wyraz=""; index=0; } if(wyraz.equals(secret)) setTitle("Sekretne haslo to: " + wyraz); } } Jeśli te linie dodam w komentarz // JPanel Panel = new ButtonPanel(); // add(Panel); to wszystko jest OK. Jeśli natomiast dodaje ten ButtonPanel do programu, to w ogóle nie wczytuje danych z klawiatury. Tak, jakby te przyciski przesłaniały wczytywanie z klawiatury...

PawEł

"W konsoli bardziej nie poszalejemy" nie wiem czemu, ale bardzo mnie to śmieszy

rarar

ten kurs to DNO, miał być od podstaw, a autorowi nie chce się nawet wytłumaczyć napisanego kodu. Funkcje które zostały użyte w kodzie nie zostały nawet skomentowane... DNO nie polecam

aleksanderwiel

Potrzebuję pomocy w modyfikacji kodu. Chciałem przerobić program tak, aby znalazł się w oddzielnej klasie. Dotychczas na lekcjach robiliśmy programy składające się z 3 klas i dlatego chciałem program na ich wzór.

Piotrek

Jak mam zrobić żeby JPanel był słuchaczem? Proszę o kod.