Predavanje br. index|1|2|3|4|5|6|7|8|9|10|11|12|13|14|HOME
Proceduralni programi – rep događaja (event queue) –
događaji niske razine (low level events) – događaji visoke razine (high level
events) – hijerarhija klasa događaja – obrada događaja – različiti
EventListeneri – primjer uporabe MouseListenera – događaji vezani uz tipke (key
events) – kodovi tipki – različite tipke i njihovi kodovi – modifikatori
(modifier keys) – modifikatori uz tipke miša – događaji vezani uz fokus (focus
events) – događaji vezani uz komponentu (component events) – adapteri – primjer
uporabe adaptera – apsorbiranje događaja – prebacivanje događaja s niske na
visoku razinu – rad s repom događaja
Tradicionalno, proceduralni programi imaju jedinstven poredak izvršavanja. Kontrola se prebacuje linearno od prve naredbe na drugu, od druge na treću i tako dalje, sa eventualnim petljama i grananjima. Korisnik unosi podatke samo u točno određenim trenucima, kad je računalo spremno primiti te podatke.
Programi koji podržavaju GUI (Graphical User Interface) ne mogu funkcionirati na takav način. Korisniku mora biti omogućeno da praktički u svakom trenutku klikne mišem, odabere stavku izbornika, unese tekst i slično.
Umjesto proceduralnog, GUI modeli zahtijevaju događajni
pristup. Svaka korisnikova akcija, bio to klik, pritisak tipke ili nešto drugo,
stavlja se u tzv. rep događaja (event queue), onako kako se
događaji pojavljuju. Uobičajeno je da se todogađa na razini operacijskog
sustava. Program uklanja događaje iz repa i obrađuje ih, jedan po jedan.
Općenito, jedna beskonačna while
petlja
čita događaje iz repa, a dugačka switch
naredba
raspodjeljuje ih odgovarajućim dijelovima programa koji će ih obraditi..
Ovisno o arhitekturi sustava, možemo imati jedan veliki sistemski rep događaja ili pak svaka aplikacija može imati vlastiti rep događaja. Operacijski sustav mora osigurati da pravi događaji stignu do pravih programa.
U aplikacijama kojima se mi bavimo, dakle u Java programima, svaka virtualna mašina ima jedan glavni AWT rep događaja. Također je gotovo sigurno prisutan i native rep događaja, no mi ćemo promatrati samo Javin rep. Java program će jedino u tom repu vidjeti događaje koje mu šalje native korisničko sučelje.
Događaji niske razine predstavljaju direktnu komunikaciju s korisnikom. To može biti pritisak na tipku, klikanje mišem i slično. Uključene su sljedeće klase:
komponenta pomaknuta, promijenjena joj je veličina itd.
komponenta dobila ili izgubila fokus
tipka pritisnuta, tipka otpuštena itd.
tipka miša pritisnuta, podignuta, kliknuta, miš pomaknut, miš povučen itd.
komponenta je dodana kontejneru ili uklonjena iz njega
prozor aktiviran, deaktiviran, otvoren, zatvoren, ikonificiran, deikonificiran
java.lang.Object
|
+--java.util.EventObject
|
+--java.awt.AWTEvent
|
+--java.awt.event.
ComponentEvent
|
+--java.awt.event.InputEvent
| |
| +--java.awt.event.
KeyEvent
| |
| +--java.awt.event.
MouseEvent
|
+--java.awt.event.
FocusEvent
|
+--java.awt.event.
ContainerEvent
|
+--java.awt.event.
WindowEvent
Događaji visoke razine ili semantički događaji sadrže u sebi značenje koje je pridijeljeno komponenti korisničkog sučelja. Klase koje ovdje igraju ulogu su:
izvršena je naredba
java.awt.event.AdjustmentEvent
prilagođena je vrijednost (npr. scrollbarom)
stanje stavke se promijenilo
vrijednost tekstualnog objekta se promijenila
Na
primjer, kad korisnik klikne mišem na button i zatim ga otpusti, button će
dobiti tri odvojena događaja niže razine, tipa MouseEvent
(jedan za pritisak tipke miša, mouse down, drugi za otpuštanje tipke,
mouse up, treći eventualno za povlačenje miša, mouse drag, ako je
nastupilo). Button će nakon toga ispaliti jedan događaj više razine, tipa
ActionEvent
.
Ako korisnik klikne mišem na button, povuče miša izvan buttona i onda ga otpusti, button će dobiti dva odvojena događaja niže razine (jedan za mouse down, drugi za mouse drag). U tom slučaju ignorirat će ih i neće učiniti ništa.
Svaka
klasa događaja visoke razine podklasa je od java.awt.AWTEvent
.
java.lang.Object
|
+--java.util.EventObject
|
+--java.awt.AWTEvent
|
+--java.awt.event.
ActionEvent
|
+--java.awt.event.
ItemEvent
|
+--java.awt.event.
AdjustmentEvent
|
+--java.awt.event.
TextEvent
|
+---java.awt.event.ComponentEvent
|
+--java.awt.event.InputEvent
| |
| +--java.awt.event.KeyEvent
| |
| +--java.awt.event.MouseEvent
|
+--java.awt.event.FocusEvent
|
+--java.awt.event.ContainerEvent
|
+--java.awt.event.WindowEvent
Za
obradu događaja iz repa zadužena je Java runtime. Posebno, ona osigurava
da svaki događaj niske razine dospije do odgovarajuće komponente. Ne morate se
brinuti kojoj je komponenti koji događaj namijenjen. To sustav rješava
automatski. Specijalno, runtime prosljeđuje događaj metodi processEvent()
klase java.awt.Component
:
protected void
processEvent(AWTEvent e)
Metoda
processEvent()
prepoznaje tip događaja i prosljeđuje ga jednoj od pet drugih metoda iz iste
klase:
protected void processComponentEvent(ComponentEvent e)
protected void processFocusEvent(FocusEvent e)
protected void processKeyEvent(KeyEvent e)
protected void processMouseEvent(MouseEvent e)
protected void processMouseMotionEvent(MouseEvent e)
Svaka od tih metoda pokušava vidjeti je li neki listener odgovarajućeg tipa registriran za tu komponentu. Ako jest, događaj se prosljeđuje svakom od registriranih listenera nepredvidljivim poretkom.
Interno,
ove metode koriste objekt tipa java.awt.AWTEventMulticaster
kako bi vodile popis registracija listenera uz svaku komponentu.
Da
biste omogućili odgovaranje na događaje koje komponenta dobiva, pridružujete
komponenti (registrirate) event listener odgovarajućeg tipa. Event
listener je svaki objekt koji implementira sučelje java.util.EventListener
.
AWT definira više njegovih podsučelja, po jedno za svaki tip događaja. Npr.:
java.awt.event.ComponentListener
java.awt.event.ContainerListener
java.awt.event.FocusListener
java.awt.event.KeyListener
java.awt.event.MouseListener
java.awt.event.MouseMotionListener
java.awt.event.WindowListener
java.awt.event.ActionListener
java.awt.event.AdjustmentListener
java.awt.event.ItemListener
java.awt.event.TextListener
java.awt.event.AWTEventListener
java.awt.event.HierarchyBoundsListener
java.awt.event.HierarchyListener
java.awt.event.InputMethodListener
Svako
od tih sučelja definira događaje na koje event listener tog tipa mora biti
spreman odgovoriti. Na primjer, sučelje MouseListener
deklarira sljedeće metode:
public abstract void mouseClicked(MouseEvent e)
public abstract void mousePressed(MouseEvent e)
public abstract void mouseReleased(MouseEvent e)
public abstract void mouseEntered(MouseEvent e)
public abstract void mouseExited(MouseEvent e)
Kada,
na primjer, komponenta dobije događaj tipa MouseEvent
,
njena metoda processMouseEvent()
provjerava njegov ID da bi ustanovila da li je miš kliknut, pritisnut, otpušten,
uveden ili izveden. Tada poziva odgovarajuću metodu u svakom registriranom
objektu koji implementira MouseListener
.
Na
primjer, pretpostavimo da želimo applet koji crta crveni krug oko mjesta gdje
korisnik klikne mišem. Prisjetite se da je java.applet.Applet
podklasa od java.awt.Component
.
Prema tome, da bi applet odgovarao na klikanje mišem, uz njega mora biti
registriran odgovarajući MouseListener
.
U takvim appletima najjednostavnije je da sam applet bude MouseListener
.
Koordinatni sustav se odnosi na komponentu kojoj je događaj namijenjen, ne nužno
na cijeli applet (no to je u ovom slučaju isto).
import
java.applet.*;
import
java.awt.*;
import
java.awt.event.*;
public
class Dots extends Applet implements
MouseListener
{
int x;
int y;
int numClicks = 0;
public void init()
{
this.addMouseListener(this);
}
public void mouseClicked(MouseEvent e)
{
x = (int)
e.getX();
y = (int)
e.getY();
numClicks = numClicks +
1;
this.repaint();
}
// You have to implement these methods,
but they don't need
// to do anything.
public void mousePressed(MouseEvent e)
{}
public void mouseReleased(MouseEvent e)
{}
public void mouseEntered(MouseEvent e)
{}
public void mouseExited(MouseEvent e)
{}
// paint the dots
public void paint(Graphics g)
{
g.setColor(Color.red);
if ( numClicks > 0 )
{
g.fillOval(x, y,
30, 30);
}
}
}
<APPLET
CODE="Dots.class"
CODEBASE="http://student.math.hr/~vedris/java/classes"
WIDTH=200
HEIGHT=200>
</APPLET>
Događaj
(objekt) tipa java.awt.event.KeyEvent
šalje se komponenti ako korisnik pritisne tipku dok komponenta ima fokus. Ti su
događaji predstavljeni po jednom cjelobrojnom konstantom:
Tipka je pritisnuta | |
Tipka je otpuštena | |
Tipka je pritisnuta i zatim otpuštena |
Uglavnom
ćete imati posla sa ovim posljednjim događajem vezanim uz tipke, KeyEvent.KEY_TYPED
.
Glavna
stvar koju obično trebate doznati uz neki KeyEvent
je
koja je tipka pritisnuta. Tu informaciju dobijete pomoću metode getKeyChar()
:
public char
getKeyChar()
Ona će vam vratiti Unicode znak koji odgovara pritisnutoj tipki.
Događaji
kao KEY_PRESSED
ili KEY_RELEASED
ne nose samo znak. Oni također imaju i kod tipke. (Za razliku od njih, događaji
KEY_TYPED
nemaju kod, odnosno, kod im je nedefiniran). Ako za neki KeyEvent
trebate doznati koja tipka je pritisnuta, a ne koji je znak upisan, doznat ćete
to pomoću metode getKeyCode()
:
public int
getKeyCode()
Možete
to i konvertirati u lokalizirani string kao na primjer "END", "F4" ili "Q"
pomoću statičke metode KeyEvent.getKeyText()
:
public static String
getKeyText(int keyCode)
U
pravilu na događaje vezane uz tipke odgovarate u samoj komponenti, tako da uz
nju registrirate neki KeyListener
.
Sučelje KeyListener
deklarira sljedeće metode, po jednu za svaki tip KeyEvent
a.
public abstract void keyTyped(KeyEvent e)
public abstract void keyPressed(KeyEvent e)
public abstract void keyReleased(KeyEvent e)
Nisu sve tastature napravljene na isti način. Mac ima tipke za naredbe i opcije. PC ima Alt tipke. Neki imaju tipku Windows 95, neki nemaju. Neki imaju numeričku tastaturu, neki ne. Emacs očekuje Meta tipku koja rijetko zasebno postoji, no obično je pridružena Escape tipki (koju također neke tastature nemaju). Nekonzistentnost tastatura je je jedan od problema s kojima se cross-platform environment mora na neki način pozabaviti.
Klasa
Java.awt.event.KeyEvent
definira nešto više od stotinu virtualnih kodova za tipke koji se mapiraju na
različite, uvijek prisutne, tipke. KeyEvent.VK_0
do KeyEvent.VK_9
su iste kao znakovi ASCII '0' do '9' (0x30 - 0x39)
KeyEvent.VK_0
KeyEvent.VK_1
KeyEvent.VK_2
KeyEvent.VK_3
KeyEvent.VK_4
KeyEvent.VK_5
KeyEvent.VK_6
KeyEvent.VK_7
KeyEvent.VK_8
KeyEvent.VK_9
KeyEvent.VK_A
do KeyEvent.VK_Z
su isti kao ASCII 'A' do 'Z'; dakle imamo, KeyEvent.VK_A
,
KeyEvent.VK_B
,
KeyEvent.VK_C
,
KeyEvent.VK_D
,
KeyEvent.VK_E
,
KeyEvent.VK_F
itd.
Od interesa su i sljedeće tipke, odnosno njihovi kodovi:
|
|
|
Obje
klase, KeyEvent
i
MouseEvent
su
podklase od java.awt.event.InputEvent
.
Njezina glavna uloga je testiranje dodatnih uvjeta kao, na primjer, da li je ALT
tipka bila pritisnuta zajedno s nekom drugom tipkom, ili SHIFT tipka istovremeno
s mišem i slično.
Sljedeće četiri metode kažu vam da li je ili nije određena tipka bila pritisnuta u trenutku kad je događaj poslan.
public boolean isShiftDown()
public boolean isControlDown()
public boolean isMetaDown()
public boolean isAltDown()
Sve
se one mogu pozivati i uz MouseEvent
i
uz KeyEvent
objekte.
Tu
je također i metoda getWhen()
koja vraća vrijeme kad je događaj nastao. Vrijeme je dano u milisekundama od
ponoći 1. siječnja 1970. UTC.
public long
getWhen()
Klase
java.util.Date
i java.util.Calendar
imaju metode pomoću kojih to možete konvertirati u običan datum i vrijeme. No
najčešće će vas zanimati samo vremenska razlika između dva događaja.
Događaji
tipa InputEvent
spremaju se kao int
vrijednosti. Svaki bit u takvom broju je flag koji odgovara određenom
modifikatoru. Vrijednosti tih flagova su dane pomoću int
konstanti koje su public final static
unutar
InputEvent
klase:
InputEvent.SHIFT_MASK
InputEvent.CTRL_MASK
InputEvent.META_MASK
InputEvent.ALT_MASK
InputEvent.ALT_GRAPH_MASK
InputEvent.BUTTON1_MASK
InputEvent.BUTTON2_MASK
InputEvent.BUTTON3_MASK
Možete
ih doznati pomoću metode getModifiers()
:
public int
getModifiers()
Koristite
bitovski operator &
kad
želite testirati da li je neki flag podignut. Na primjer,
if (
e.getModifiers() & InputEvent.BUTTON2_MASK != 0
) {
System.out.println("Button 2 was pressed");
}
U svakom trenutku točno jedna komponenta u appletu ima fokus, odnosno mogućnost primanja inputa s tastature ili miša. Događaji niske razine bit će, dakle, u tom trenutku usmjereni prema toj komponenti.
Fokus je moguće podesiti na više načina. Na primjer, kad korisnik pritisne tipku Tab, fokus će općenito biti premješten s dotadašnje komponente na sljedeću. Ako pritisne Shift-Tab, fokus će se vratiti na prethodnu komponentu. Ako selektira komponentu mišem, ona će dobiti fokus.
Bez
obzira na koji način komponenta dobije ili izgubi fokus, ona emitira događaj
tipa java.awt.event.FocusEvent
.
Promjena fokusa može biti permanentna ili privremena. Permanentna promjena
nastaje kad je fokus direktno premješten s jedne komponente na drugu, bilo
pozivanjem komponentine metode requestFocus()
ili akcijom korisnika, npr. pomoću Tab tipke. Privremena promjena fokusa nastaje
kao indirektni rezultat druge operacije, na primjer deaktiviranja prozora. U tom
slučaju originalno stanje fokusa će biti automatski restaurirano kad se
operacija završi ili prozor opet aktivira.
Metoda
isTemporary()
vraća true
ako je
promjena fokusa privremena, a false
ako je
permanentna:
public boolean
isTemporary()
Na
događaje vezane uz promjenu fokusa možete odgovoriti ako uz komponentu
instalirate sučelje java.awt.event.FocusListener
.
To sučelje deklarira dvije metode:
public abstract void focusGained(FocusEvent e)
public abstract void focusLost(FocusEvent e)
Klasa
java.awt.event.ComponentEvent
je nadklasa svim klasama događaja koje smo do sad vidjeli. Ona također ima i
nekoliko svojih vlastitih događaja koji vam omogućuju da reagirate kad se
komponenta prikaže, sakrije, pomakne ili joj se promijeni veličina. Oni su
reprezentirani konstantama:
ComponentEvent.COMPONENT_MOVED
ComponentEvent.COMPONENT_RESIZED
ComponentEvent.COMPONENT_SHOWN
ComponentEvent.COMPONENT_HIDDEN
Kao
i obično, na te događaje možete odgovarati ako uz svoju komponentu registrirate
odgovarajući objekt iz klase koja implementira sučelje java.awt.event.ComponentListener
.
To sučelje deklarira četiri metode:
public abstract void componentResized(ComponentEvent e)
public abstract void componentMoved(ComponentEvent e)
public abstract void componentShown(ComponentEvent e)
public abstract void componentHidden(ComponentEvent e)
Klasa
java.awt.event.ComponentEvent
nema nekih vlastitih posebno korisnih metoda, ali možete koristiti razne metode
iz klase java.awt.Component
kad želite ustanoviti kamo je komponenta pomaknuta ili na koju veličinu je
preoblikovana.
Adapteri
su klase koje implementiraju pojedina sučelja tako da sve deklarirane metode iz
sučelja prekriju metodama koje ne čine ništa. AWT osigurava više adapterskih
klasa za različite vrste EventListener
a.
To su:
ComponentAdapter
ContainerAdapter
FocusAdapter
KeyAdapter
MouseAdapter
MouseMotionAdapter
WindowAdapter
Na primjer, znamo da sučelje MouseListener deklarira sljedećih pet metoda:
public abstract void mouseClicked(MouseEvent e)
public abstract void mousePressed(MouseEvent e)
public abstract void mouseReleased(MouseEvent e)
public abstract void mouseEntered(MouseEvent e)
public abstract void mouseExited(MouseEvent e)
Zato
odgovarajući adapter, java.awt.event.MouseAdapter
izgleda ovako:
package java.awt.event;
import java.awt.*;
import java.awt.event.*;
public class MouseAdapter implements MouseListener {
public void mouseClicked(MouseEvent e) {}
public void mousePressed(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
}
Ako
sada napravite podklasu klase MouseAdapter
umjesto da direktno implementirate MouseListener
,
izbjeći ćete pisanje metoda koje vam zapravo ne trebaju. Pregazit ćete jedino
one metode koje stvarno želite implementirati. Adapteri su svojevrsno
pojednostavnjenje posla prilikom implementiranja sučelja. Možete ih koristiti
ako želite, ali i ne morate.
Ovdje imamo adapter za miša koji proizvodi beep signal kad kliknete mišem.
import
java.applet.Applet;
public
class MouseBeepApplet extends Applet {
public void init()
{
MouseBeeper mb = new
MouseBeeper();
this.addMouseListener(mb);
}
}
import java.awt.*;
import java.awt.event.*;
public class MouseBeeper
extends MouseAdapter
{
public void mouseClicked(MouseEvent e) {
Toolkit.getDefaultToolkit().beep();
}
}
<APPLET
CODE="MouseBeepApplet.class"
CODEBASE="http://student.math.hr/~vedris/java/classes"
ARCHIVE="MouseBeep.jar"
WIDTH=200
HEIGHT=200>
</APPLET>
Kad
MouseBeeper
ne
bi bio podklasa od MouseAdapter
morao bi izgledati ovako:
import java.awt.*;
import java.awt.event.*;
public class MouseBeeper
implements MouseListener
{
public void mouseClicked(MouseEvent e) {
Toolkit.getDefaultToolkit().beep();
}
public void mousePressed(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
}
Ponekad
je potrebno neke događaje zadržati kako ih komponenta ne bi procesirala na
uobičajen način. Na primjer, program za izradu vizualnog sučelja možda treba
dozvoliti korisniku povlačenje miša po ekranu. U tom slučaju ne želite da običan
klik na tipku miša aktivira tu funkciju. Zato komponenta može apsorbirati
(consume) događaje tipa InputEvent
,
dakle MouseEvent
ili
a KeyEvent
, tako
da pozove njihovu metodu consume()
:
public void
consume()
Jednom
kad je događaj (objekt) tipa InputEvent
apsorbiran, izvorna komponenta ga neće procesirati, ali će taj događaj svejedno
biti distribuiran svim registriranim listenerima.
Svaki native operacijski sustav i korisničko sučelje šalje samo događaje niske razine. Ono neće poslati ActionEvent, TextEvent, ItemEvent ili AdjustmentEvent. Umjesto toga, svaka komponenta koja lansira takve događaje prvo sluša određene događaje niske razine. Kad ugleda odgovarajući događaj ili kombinaciju događaja niske razine, ona ih apsorbira i lansira novi događaj visoke razine.
Klasa
java.awt.EventQueue
predstavlja rep događaja koji čekaju na procesiranje. Možete kreirati vlastite
instance ove klase uz pomoć sljedećeg konstruktora:
public
EventQueue()
Na primjer:
EventQueue MyQueue = new
EventQueue()
;
Ipak,
uglavnom će vas više zanimati sistemski rep događaja. Taj se kreira automatski.
Možete dobiti referencu na njega pomoću metode getSystemEventQueue()
iz klase java.awt.Toolkit
ovako
:
EventQueue systemQueue =
Toolkit.getDefaultToolkit().
getSystemEventQueue()
;
Applet neće moći pozvati ovu metodu a da pritom ne generira SecurityException.
Jednom kad dobijete referencu na sistemski rep događaja, možete manipulirati s njim pomoću sljedećih metoda:
public synchronized void postEvent(AWTEvent e)
public synchronized AWTEvent getNextEvent()
public synchronized AWTEvent peekEvent()
public synchronized AWTEvent peekEvent(int n)
Metoda
postEvent()
omogućuje vam da stavite događaj u rep. Metoda getNextEvent()
vraća gornji događaj iz repa i uklanja ga odande. Metoda peekEvent()
vraća gornji događaj iz repa, ali ga ne uklanja. Metoda peekEvent(int
n)
vraća nti događaj iz repa.