Java savjet 35: Stvorite nove vrste događaja u Javi

Iako je JDK 1.1 zasigurno pojednostavio upravljanje događajima uvođenjem modela događaja delegiranja, programerima ne olakšava stvaranje vlastitih vrsta događaja. Ovdje opisani osnovni postupak zapravo je prilično jednostavan. Radi jednostavnosti, neću raspravljati o konceptima omogućavanja događaja i maskama događaja. Osim toga, trebali biste znati da događaji stvoreni ovim postupkom neće biti objavljeni u redu događaja i radit će samo s registriranim slušateljima.

Trenutno se Java jezgra sastoji od 12 vrsta događaja definiranih u java.awt.events:

  • ActionEvent
  • AdjustmentEvent
  • ComponentEvent
  • ContainerEvent
  • FocusEvent
  • InputEvent
  • ItemEvent
  • KeyEvent
  • MouseEvent
  • PaintEvent
  • TextEvent
  • WindowEvent

Budući da je stvaranje novih vrsta događaja netrivijalni zadatak, trebali biste ispitati događaje koji su dio jezgre Java. Ako je moguće, pokušajte koristiti te vrste, a ne stvarati nove.

Međutim, bit će trenutaka kada će trebati razviti novi tip događaja za novu komponentu. U svrhe ove rasprave, poslužit ću se primjerom jednostavne komponente, čarobnjačke ploče, kao sredstvo da pokažem kako stvoriti novi tip događaja.

Ploča čarobnjaka implementira jednostavno sučelje čarobnjaka . Komponenta se sastoji od ploče s karticom koju možete unaprijediti pomoću gumba NEXT. Gumb BACK omogućuje prebacivanje na prethodnu ploču. Također su predviđene tipke FINISH i CANCEL.

Kako bih komponentu učinio fleksibilnom, programeru koji je koristi želio sam pružiti potpunu kontrolu nad radnjama svih gumba. Na primjer, kada se pritisne gumb NEXT, programer bi trebao moći provjeriti jesu li potrebni podaci uneseni na komponentu koja je trenutno vidljiva prije prelaska na sljedeću komponentu.

Pet je glavnih zadataka u stvaranju vlastite vrste događaja:

  • Stvorite slušatelj događaja

  • Stvorite adapter slušatelja

  • Stvorite klasu događaja

  • Izmijenite komponentu

  • Upravljanje više slušatelja

Ispitat ćemo redom svaki od ovih zadataka, a zatim ćemo ih sve sastaviti.

Stvorite slušatelj događaja

Jedan od načina (a ima ih mnogo) da se objekti obavijeste da se dogodila određena radnja jest stvaranje nove vrste događaja koja bi se mogla isporučiti registriranim slušateljima. U slučaju ploče čarobnjaka, slušatelj bi trebao podržati četiri različita slučaja događaja, po jedan za svaki gumb.

Počinjem s izradom sučelja slušatelja. Za svaki gumb definiram metodu slušatelja na sljedeći način:

import java.util.EventListener; javno sučelje WizardListener proširuje EventListener {javna sažetak void nextSelected (WizardEvent e); javni sažetak void backSelected (WizardEvent e); javni sažetak void cancelSelected (WizardEvent e); javna sažetak void finishSelected (WizardEvent e); }

Svaka metoda uzima jedan argument:, WizardEventkoji je definiran sljedeći. Imajte na umu da se sučelje proširuje EventListeneri koristi se za identificiranje ovog sučelja kao AWT slušatelja.

Stvorite adapter slušatelja

Stvaranje adaptera slušatelja neobavezan je korak. U AWT-u je adapter slušatelja klasa koja pruža zadanu implementaciju za sve metode određene vrste slušatelja. Sve klase adaptora u java.awt.eventpaketu pružaju prazne metode koje ne rade ništa. Evo klase adaptora za WizardListener:

javna klasa WizardAdapter implementira WizardListener {javna praznina nextSelected (WizardEvent e) {} javna praznina backSelected (WizardEvent e) {} javna praznina cancelSelected (WizardEvent e) {} javna praznina finishSelected (WizardEvent e) {}} 

Pri pisanju klase koja će biti slušatelj čarobnjaka, moguće je proširiti WizardAdapteri osigurati implementaciju (ili poništiti) samo one metode slušatelja koje su od interesa. Ovo je strogo praktična klasa.

Stvorite klasu događaja

Sljedeći korak je stvoriti stvarne Eventklase ovdje: WizardEvent.

import java.awt.AWTEvent; javna klasa WizardEvent proširuje AWTEvent {javni statički konačni int WIZARD_FIRST = AWTEvent.RESERVED_ID_MAX + 1; javni statički konačni int NEXT_SELECTED = WIZARD_FIRST; javni statički konačni int BACK_SELECTED = WIZARD_FIRST + 1; javni statički konačni int CANCEL_SELECTED = WIZARD_FIRST + 2; javni statički konačni int FINISH_SELECTED = WIZARD_FIRST + 3; javni statički konačni int WIZARD_LAST = WIZARD_FIRST + 3; javni WizardEvent (izvor čarobnjaka, int id) {super (izvor, id); }}

Dvije konstante WIZARD_FIRSTi WIZARD_LASToznačavaju sveobuhvatan raspon maski koje koristi ova klasa događaja. Imajte na umu da ID-ovi događaja koriste RESERVED_ID_MAXkonstantu klase AWTEventza određivanje raspona ID-ova koji se neće sukobiti s vrijednostima ID-a događaja definiranim AWT-om. Kako se dodaje više komponenata AWT, to RESERVED_ID_MAXće se u budućnosti možda povećati.

Preostale četiri konstante predstavljaju četiri ID-a događaja, svaki koji odgovara različitom tipu radnje, kako je definirano čarobnjakovom funkcionalnošću.

ID događaja i izvor događaja dva su argumenta za konstruktor događaja čarobnjaka. Izvor događaja mora biti tipa Wizard- to je tip komponente za koji je događaj definiran. Obrazloženje je da samo čarobnjakova ploča može biti izvor čarobnjačkih događaja. Imajte na umu da se WizardEventklasa proteže AWTEvent.

Izmijenite komponentu

Sljedeći je korak opremanje naše komponente metodama koje joj omogućuju registraciju i uklanjanje slušatelja za novi događaj.

Da bi se slušatelju isporučio događaj, obično bi se pozvala odgovarajuća metoda slušatelja događaja (ovisno o maski događaja). Mogu registrirati slušatelj akcije za primanje događaja radnje pomoću gumba NEXT i prenošenje na registrirane WizardListenerobjekte. actionPerformedMetoda akcijskog slušatelja za sljedeću (ili druge radnje) tipka može se provesti na sljedeći način:

public void actionPerformed (ActionEvent e) {// ne poduzimati ništa ako nije registriran nijedan slušatelj if (wizardListener == null) return; WizardEvent w; Izvor čarobnjaka = ovo; if (e.getSource () == nextButton) {w = novi WizardEvent (izvor, WizardEvent.NEXT_SELECTED); wizardListener.nextSelected (w); } // na sličan način rukovati ostatkom čarobnjaka}

Napomena: U gornjem primjeru, Wizardsama ploča je preslušavač gumba NEXT .

Kad se pritisne tipka NEXT, WizardEventkreira se nova s odgovarajućim izvorom i maskom koja odgovara tipki NEXT koja se pritiska.

U primjeru linija

 wizardListener.nextSelected (w); 

odnosi se na wizardListenerobjekt koji je varijabla privatnog člana Wizardi tipa je WizardListener. Ovu smo vrstu definirali kao prvi korak u stvaranju novog događaja komponente.

Na prvi pogled čini se da gornji kod ograničava broj slušatelja na jednog. Privatna varijabla wizardListenernije niz, a upućuje se samo jedan nextSelectedpoziv. Da bismo objasnili zašto gornji kod zapravo ne predstavlja to ograničenje, ispitajmo kako se dodaju slušatelji.

Svaka nova komponenta koja generira događaje (unaprijed definirane ili nove) mora osigurati dvije metode: jednu za podršku dodavanju slušatelja i drugu za uklanjanje slušatelja. U slučaju Wizardklase, ove metode su:

javna sinkronizirana praznina addWizardListener (WizardListener l) {wizardListener = WizardEventMulticaster.add (wizardListener, l); } javna sinkronizirana praznina removeWizardListener (WizardListener l) {wizardListener = WizardEventMulticaster.remove (wizardListener, l); }

Obje metode upućuju poziv statičkim članovima metode klase WizardEventMulticaster.

Upravljanje više slušatelja

Iako je moguće da se koristiti Vectorza upravljanje s više slušatelja, JDK 1.1 definira posebna klasa za održavanje popisa slušatelja: AWTEventMulticaster. Jedna multicaster instanca održava reference na dva objekta slušatelja. Budući da je multicaster i sam slušatelj (implementira sva sučelja slušatelja), svaki od dva slušatelja koje prati također može biti multicaster, stvarajući tako lanac slušatelja događaja ili multicastera:

Ako je slušatelj također multicaster, to predstavlja kariku u lancu. Inače je samo slušatelj i stoga je posljednji element u lancu.

Nažalost, nije moguće jednostavno ponovno koristiti AWTEventMulticasterza obradu višestrukog slanja događaja za nove vrste događaja. Najbolje što se može učiniti je proširiti AWT multicaster, iako je ova operacija prilično upitna. AWTEventMulticastersadrži 56 metoda. Od toga 51 metoda pruža podršku za 12 vrsta događaja i odgovarajuće slušatelje koji su dio AWT-a. Ako podrazredite AWTEventMulticaster, ionako ih nikada nećete koristiti. Od preostalih pet metoda addInternal(EventListener, EventListener), i remove(EventListener)trebaju se kodirati. (Kažem da je kodirano, jer je in AWTEventMulticaster, addInternalstatična je metoda i stoga se ne može preopteretiti. Iz trenutno meni nepoznatih razloga removeupućuje poziv addInternali treba ga preopteretiti.)

Dvije metode savei saveInternalpružaju podršku za strujanje objekata i mogu se ponovno koristiti u novoj klasi multicaster. Posljednja metoda koja podržava rutine uklanjanja slušatelja removeInternal, također se može ponovno upotrijebiti pod uvjetom da su implementirane nove verzije removei addInternal.

Radi jednostavnosti, ja ću podrazreda AWTEventMulticaster, ali s vrlo malo truda, moguće je da se kod remove, savei saveInternalte imaju potpuno funkcionalan, samostalni događaj multicaster.

Evo multicastera događaja kako je implementiran za rukovanje WizardEvent:

import java.awt.AWTEventMulticaster; import java.util.EventListener; javna klasa WizardEventMulticaster proširuje AWTEventMulticaster implementira WizardListener {zaštićen WizardEventMulticaster (EventListener a, EventListener b) {super (a, b); } javni statični WizardListener add (WizardListener a, WizardListener b) {return (WizardListener) addInternal (a, b); } javni statični WizardListener ukloni (WizardListener l, WizardListener oldl) {return (WizardListener) removeInternal (l, oldl); } public void nextSelected (WizardEvent e) {// u ovom slučaju nikada se neće pojaviti iznimka lijevanja // lijevanje _je potrebno_ jer ovaj multicaster može // obrađivati ​​više od jednog slušatelja ako (a! = null) ((WizardListener) a) nextSelected (e); if (b! = null) ((WizardListener) b) .nextSelected (e); } public void backSelected (WizardEvent e) {if (a! = null) ((WizardListener) a).natragOdabrano (e); if (b! = null) ((WizardListener) b) .backSelected (e); } javna void cancelSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .cancelSelected (e); if (b! = null) ((WizardListener) b) .cancelSelected (e); } javna praznina finishSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); if (b! = null) ((WizardListener) b) .finishSelected (e); } zaštićeni statički EventListener addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; vratiti novi WizardEventMulticaster (a, b); } zaštićeni EventListener ukloniti (EventListener oldl) {if (oldl == a) return b; if (oldl == b) vrati a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); ako (a2 == a && b2 == b) vrati ovo; vrati addInternal (a2, b2); }}= null) ((WizardListener) b) .backSelected (e); } javna void cancelSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .cancelSelected (e); if (b! = null) ((WizardListener) b) .cancelSelected (e); } javna praznina finishSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); if (b! = null) ((WizardListener) b) .finishSelected (e); } zaštićeni statički EventListener addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; vratiti novi WizardEventMulticaster (a, b); } zaštićeni EventListener ukloniti (EventListener oldl) {if (oldl == a) return b; if (oldl == b) vrati a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); ako (a2 == a && b2 == b) vrati ovo; vrati addInternal (a2, b2); }}= null) ((WizardListener) b) .backSelected (e); } javna void cancelSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .cancelSelected (e); if (b! = null) ((WizardListener) b) .cancelSelected (e); } javna praznina finishSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); if (b! = null) ((WizardListener) b) .finishSelected (e); } zaštićeni statički EventListener addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; vratiti novi WizardEventMulticaster (a, b); } zaštićeni EventListener ukloniti (EventListener oldl) {if (oldl == a) return b; if (oldl == b) vrati a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); ako (a2 == a && b2 == b) vrati ovo; vrati addInternal (a2, b2); }}} javna void cancelSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .cancelSelected (e); if (b! = null) ((WizardListener) b) .cancelSelected (e); } javna praznina finishSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); if (b! = null) ((WizardListener) b) .finishSelected (e); } zaštićeni statički EventListener addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; vratiti novi WizardEventMulticaster (a, b); } zaštićeni EventListener ukloniti (EventListener oldl) {if (oldl == a) return b; if (oldl == b) vrati a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); ako (a2 == a && b2 == b) vrati ovo; vrati addInternal (a2, b2); }}} javna void cancelSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .cancelSelected (e); if (b! = null) ((WizardListener) b) .cancelSelected (e); } javna praznina finishSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); if (b! = null) ((WizardListener) b) .finishSelected (e); } zaštićeni statički EventListener addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; vratiti novi WizardEventMulticaster (a, b); } zaštićeni EventListener ukloniti (EventListener oldl) {if (oldl == a) return b; if (oldl == b) vrati a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); ako (a2 == a && b2 == b) vrati ovo; vrati addInternal (a2, b2); }}cancelSelected (e); } javna praznina finishSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); if (b! = null) ((WizardListener) b) .finishSelected (e); } zaštićeni statički EventListener addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; vratiti novi WizardEventMulticaster (a, b); } zaštićeni EventListener ukloniti (EventListener oldl) {if (oldl == a) return b; if (oldl == b) vrati a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); ako (a2 == a && b2 == b) vrati ovo; vrati addInternal (a2, b2); }}cancelSelected (e); } javna praznina finishSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); if (b! = null) ((WizardListener) b) .finishSelected (e); } zaštićeni statički EventListener addInternal (EventListener a, EventListener b) {if (a == null) return b; if (b == null) return a; vratiti novi WizardEventMulticaster (a, b); } zaštićeni EventListener ukloniti (EventListener oldl) {if (oldl == a) return b; if (oldl == b) vrati a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); ako (a2 == a && b2 == b) vrati ovo; vrati addInternal (a2, b2); }}EventListener b) {if (a == null) return b; if (b == null) return a; vratiti novi WizardEventMulticaster (a, b); } zaštićeni EventListener ukloniti (EventListener oldl) {if (oldl == a) return b; if (oldl == b) vrati a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); ako (a2 == a && b2 == b) vrati ovo; vrati addInternal (a2, b2); }}EventListener b) {if (a == null) return b; if (b == null) return a; vratiti novi WizardEventMulticaster (a, b); } zaštićeni EventListener ukloniti (EventListener oldl) {if (oldl == a) return b; if (oldl == b) vrati a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); ako (a2 == a && b2 == b) vrati ovo; vrati addInternal (a2, b2); }}

Metode u klasi multicaster: Pregled

Pregledajmo metode koje su dio klase multicaster gore. Konstruktor je zaštićen, a da bi se dobila nova WizardEventMulticaster, add(WizardListener, WizardListener)mora se pozvati statička metoda. Kao argumenti koji predstavljaju dva dijela lanca slušatelja trebaju se povezati dva slušatelja:

  • Da biste započeli novi lanac, upotrijebite null kao prvi argument.

  • Da biste dodali novog slušatelja, koristite postojeći slušatelj kao prvi argument, a novog slušatelja kao drugi argument.

To je, zapravo, učinjeno u kodu za klasu Wizardkoji smo već ispitali.

Druga statična rutina je remove(WizardListener, WizardListener). Prvi je argument slušatelj (ili multicaster slušatelja), a drugi slušatelj kojeg treba ukloniti.

Dodane su četiri javne, nestatične metode koje podržavaju širenje događaja kroz lanac događaja. Za svaki WizardEventslučaj (odnosno odabrani sljedeći, povratak, otkazivanje i završetak) postoji jedna metoda. Te se metode moraju implementirati od WizardEventMulticasterimplementacija WizardListener, što zauzvrat zahtijeva prisutnost četiri metode.

Kako to sve skupa funkcionira

Idemo sada ispitati kako multicaster zapravo koristi Wizard. Pretpostavimo da je izgrađen čarobnjački objekt i dodana su tri slušatelja, stvarajući lanac slušatelja.

U početku je privatna varijabla wizardListenerklase Wizardnull. Dakle, kad se uputi poziv WizardEventMulticaster.add(WizardListener, WizardListener), prvi argument, wizardListenerje null, a drugi nije (nema smisla dodavati null listener). addMetoda, pak, poziva addInternal. Budući da je jedan od argumenata null, povratak addInternalje non-null listener. Povratak se širi na addmetodu koja vraća ne-null slušatelja na addWizardListenermetodu. Tamo je wizardListenervarijabla postavljena na novog slušatelja koji se dodaje.