Petua Java 35: Buat jenis acara baru di Java

Walaupun JDK 1.1 sudah pasti menyelaraskan pengendalian acara dengan pengenalan model acara delegasi, itu tidak memudahkan para pembangun untuk membuat jenis acara mereka sendiri. Prosedur asas yang dinyatakan di sini sebenarnya agak mudah. Demi kesederhanaan, saya tidak akan membincangkan konsep pengaktifan acara dan topeng acara. Selain itu, anda harus tahu bahawa acara yang dibuat menggunakan prosedur ini tidak akan dihantar ke barisan acara dan hanya akan berfungsi dengan pendengar yang berdaftar.

Pada masa ini, inti Java terdiri daripada 12 jenis peristiwa yang ditentukan dalam java.awt.events:

  • TindakanEvent
  • PenyesuaianEvent
  • KomponenEvent
  • ContainerEvent
  • FocusEvent
  • InputEvent
  • ItemEvent
  • KeyEvent
  • MouseEvent
  • CatEvent
  • TextEvent
  • WindowEvent

Oleh kerana membuat jenis acara baru adalah tugas yang tidak remeh, anda harus memeriksa peristiwa yang merupakan sebahagian daripada inti Java. Sekiranya boleh, cuba gunakan jenis tersebut daripada membuat yang baru.

Akan ada waktunya, ketika jenis acara baru perlu dikembangkan untuk komponen baru. Untuk tujuan perbincangan ini, saya akan menggunakan contoh komponen ringkas, panel wizard, sebagai kaedah untuk menunjukkan cara membuat jenis acara baru.

Panel wizard menggunakan antara muka penyihir sederhana . Komponen ini terdiri daripada panel kad yang dapat dimajukan menggunakan butang BERIKUTNYA. Butang BACK membolehkan anda beralih ke panel sebelumnya. Butang FINISH dan CANCEL juga disediakan.

Untuk menjadikan komponen fleksibel, saya ingin memberikan kawalan penuh terhadap tindakan yang dilakukan oleh semua butang kepada pembangun yang menggunakannya. Contohnya, apabila butang BERIKUTNYA ditekan, pembangun harus memeriksa terlebih dahulu apakah data yang diperlukan dimasukkan pada komponen yang sedang dilihat sebelum beralih ke komponen berikutnya.

Terdapat lima tugas utama dalam membuat jenis acara anda sendiri:

  • Buat pendengar acara

  • Buat penyesuai pendengar

  • Buat kelas acara

  • Ubahsuai komponen

  • Menguruskan pelbagai pendengar

Kami akan memeriksa setiap tugas ini secara bergiliran dan kemudian menyatukannya.

Buat pendengar acara

Salah satu cara (dan ada banyak) untuk memberitahu objek bahawa tindakan tertentu telah terjadi adalah dengan membuat jenis peristiwa baru yang dapat disampaikan kepada pendengar yang terdaftar. Dalam kes panel sihir, pendengar harus menyokong empat kes acara yang berbeza, satu untuk setiap butang.

Saya mulakan dengan membuat antara muka pendengar. Untuk setiap butang, saya menentukan kaedah pendengar dengan cara berikut:

import java.util.EventListener; antara muka awam WizardListener meluaskan EventListener {public abstract void nextSelected (WizardEvent e); undian abstrak awam terpilih (WizardEvent e); batal abstrak awam batalPilih (WizardEvent e); kemasan kosong abstrak awamPilih (WizardEvent e); }

Setiap kaedah mengambil satu argumen: WizardEventyang akan ditentukan seterusnya. Perhatikan bahawa antara muka meluas EventListener, digunakan untuk mengenal pasti antara muka ini sebagai pendengar AWT.

Buat penyesuai pendengar

Membuat penyesuai pendengar adalah langkah pilihan. Dalam AWT, penyesuai pendengar adalah kelas yang menyediakan pelaksanaan lalai untuk semua kaedah jenis pendengar tertentu. Semua kelas penyesuai dalam java.awt.eventpakej menyediakan kaedah kosong yang tidak menghasilkan apa-apa. Berikut adalah kelas penyesuai untuk WizardListener:

kelas awam WizardAdapter mengimplementasikan WizardListener {public void nextSelected (WizardEvent e) {} public void backSelected (WizardEvent e) {} public void cancelSelected (WizardEvent e) {} public void finishSelected (WizardEvent e) {}} 

Semasa menulis kelas yang akan menjadi pendengar ahli sihir, adalah mungkin untuk memperluas WizardAdapterdan menyediakan pelaksanaan untuk (atau mengatasi) hanya kaedah pendengar yang menarik. Ini adalah kelas kemudahan.

Buat kelas acara

Langkah seterusnya adalah membuat Eventkelas sebenar di sini : WizardEvent.

import java.awt.AWTEvent; kelas awam WizardEvent meluaskan AWTEvent {public static final int WIZARD_FIRST = AWTEvent.RESERVED_ID_MAX + 1; int akhir statik awam NEXT_SELECTED = WIZARD_FIRST; int akhir statik awam BACK_SELECTED = WIZARD_FIRST + 1; int akhir statik awam CANCEL_SELECTED = WIZARD_FIRST + 2; final statik awam int FINISH_SELECTED = WIZARD_FIRST + 3; akhir statik awam WIZARD_LAST = WIZARD_FIRST + 3; public WizardEvent (sumber Wizard, int id) {super (sumber, id); }}

Dua pemalar, WIZARD_FIRSTdan WIZARD_LAST, tandakan rangkaian topeng inklusif yang digunakan oleh kelas Acara ini. Perhatikan bahawa ID peristiwa menggunakan RESERVED_ID_MAXpemalar kelas AWTEventuntuk menentukan julat ID yang tidak akan bertentangan dengan nilai ID peristiwa yang ditentukan oleh AWT. Oleh kerana semakin banyak komponen AWT ditambahkan, RESERVED_ID_MAXmungkin bertambah pada masa akan datang.

Empat pemalar selebihnya mewakili empat ID peristiwa, masing-masing sesuai dengan jenis tindakan yang berbeza, seperti yang ditentukan oleh fungsi ahli sihir.

ID peristiwa dan sumber peristiwa adalah dua argumen untuk pembangun acara penyihir. Sumber acara mestilah jenis Wizard- itu adalah jenis komponen yang ditentukan oleh peristiwa itu. Alasannya adalah bahawa hanya panel ahli sihir yang dapat menjadi sumber peristiwa penyihir. Perhatikan bahawa WizardEventkelas meluas AWTEvent.

Ubahsuai komponen

Langkah seterusnya adalah melengkapkan komponen kami dengan kaedah yang memungkinkan untuk mendaftar dan mengeluarkan pendengar untuk acara baru.

Untuk menyampaikan acara kepada pendengar, biasanya seseorang akan memanggil kaedah pendengar acara yang sesuai (bergantung pada topeng acara). Saya boleh mendaftarkan pendengar tindakan untuk menerima peristiwa aksi dari butang BERIKUTNYA dan menyampaikannya ke WizardListenerobjek yang didaftarkan . The actionPerformedkaedah pendengar tindakan bagi NEXT (atau tindakan lain) butang boleh dilaksanakan seperti berikut:

public void actionPerformed (ActionEvent e) {// tidak melakukan apa-apa sekiranya tidak ada pendengar yang didaftarkan sekiranya (wizardListener == null) kembali; WizardEvent w; Sumber ahli sihir = ini; jika (e.getSource () == nextButton) {w = WizardEvent baru (sumber, WizardEvent.NEXT_SELECTED); wizardListener.nextPilih (w); } // mengendalikan butang penyihir yang lain dengan cara yang serupa}

Catatan: Dalam contoh di atas, Wizardpanel itu sendiri adalah pendengar untuk butang BERIKUTNYA .

Apabila butang NEXT ditekan, yang baru WizardEventdibuat dengan sumber dan topeng yang sesuai yang sesuai dengan butang NEXT yang ditekan.

Dalam contohnya, garis

 wizardListener.nextPilih (w); 

merujuk kepada wizardListenerobjek yang merupakan pemboleh ubah anggota peribadi untuk Wizarddan adalah jenis WizardListener. Kami telah menentukan jenis ini sebagai langkah pertama dalam membuat acara komponen baru.

Pada pandangan pertama, kod di atas nampaknya mengehadkan bilangan pendengar untuk satu. Pemboleh ubah peribadi wizardListenerbukan tatasusunan, dan hanya satu nextSelectedpanggilan dibuat. Untuk menerangkan mengapa kod di atas sebenarnya tidak menimbulkan sekatan itu, mari kita periksa bagaimana pendengar ditambahkan.

Setiap komponen baru yang menghasilkan peristiwa (yang telah ditentukan atau baru) perlu menyediakan dua kaedah: satu untuk menyokong penambahan pendengar dan satu untuk menyokong penghapusan pendengar. Bagi Wizardkelas, kaedah ini adalah:

addWizardListener yang tidak diselaraskan awam (WizardListener l) {wizardListener = WizardEventMulticaster.add (wizardListener, l); } penghapusan kekosongan awam disingkirkanWizardListener (WizardListener l) {wizardListener = WizardEventMulticaster.remove (wizardListener, l); }

Kedua-dua kaedah membuat panggilan ke kaedah statik ahli kelas WizardEventMulticaster.

Menguruskan pelbagai pendengar

Walaupun ia adalah mungkin untuk menggunakan Vectoruntuk menguruskan beberapa pendengar, JDK 1.1 mentakrifkan kelas khas untuk mengekalkan senarai pendengar: AWTEventMulticaster. Satu contoh multicaster mengekalkan rujukan ke dua objek pendengar. Kerana multicaster juga pendengar itu sendiri (ia menerapkan semua antara muka pendengar), masing-masing dua pendengar yang dilacaknya juga boleh menjadi multicaster, sehingga mewujudkan rangkaian pendengar acara atau multicaster:

Sekiranya pendengar juga multicaster, maka itu mewakili pautan dalam rantaian. Jika tidak, ia hanyalah pendengar dan merupakan elemen terakhir dalam rantai.

Sayangnya, tidak mungkin hanya menggunakan semula AWTEventMulticasteruntuk mengendalikan acara multicasting untuk jenis acara baru. Yang terbaik yang boleh dilakukan adalah memperluas AWT multicaster, walaupun operasi ini agak dipersoalkan. AWTEventMulticastermengandungi 56 kaedah. Dari jumlah tersebut, 51 kaedah memberikan sokongan untuk 12 jenis acara dan pendengar yang sesuai yang merupakan sebahagian daripada AWT. Sekiranya anda subkelas AWTEventMulticaster, anda tidak akan menggunakannya. Dari lima kaedah yang tinggal addInternal(EventListener, EventListener), dan remove(EventListener)perlu dikodkan semula. (Saya katakan direkodkan kerana di AWTEventMulticaster, addInternaladalah kaedah statik dan oleh itu tidak boleh dibebani secara berlebihan. Atas sebab-sebab yang tidak diketahui oleh saya pada masa ini, removemembuat panggilan ke addInternaldan perlu kelebihan beban.)

Dua kaedah, savedan saveInternal, memberikan sokongan untuk streaming objek dan dapat digunakan kembali di kelas multicaster baru. Kaedah terakhir yang menyokong rutin menghapus pendengar removeInternal, juga dapat digunakan kembali, asalkan versi baru removedan addInternaltelah dilaksanakan.

Demi kesederhanaan, saya akan subkelas AWTEventMulticaster, tetapi dengan usaha yang sedikit, ia adalah mungkin untuk kod remove, savedan saveInternaldan mempunyai berfungsi sepenuhnya, multicaster acara berdiri sendiri.

Berikut adalah acara multicaster yang dilaksanakan untuk menangani WizardEvent:

import java.awt.AWTEventMulticaster; import java.util.EventListener; kelas awam WizardEventMulticaster meluaskan AWTEventMulticaster mengimplementasikan WizardListener {dilindungi WizardEventMulticaster (EventListener a, EventListener b) {super (a, b); } tambah WizardListener statik awam (WizardListener a, WizardListener b) {return (WizardListener) addInternal (a, b); } WizardListener statik awam hapus (WizardListener l, WizardListener oldl) {return (WizardListener) removeInternal (l, oldl); } public void nextSelected (WizardEvent e) {// pengecualian casting tidak akan berlaku dalam kes ini // casting _is_ diperlukan kerana multicaster ini boleh // menangani lebih daripada satu pendengar sekiranya (a! = null) ((WizardListener) a). seterusnyaPilih (e); jika (b! = null) ((WizardListener) b). sebelah dipilih (e); } kekosongan awam terpilih (WizardEvent e) {if (a! = null) ((WizardListener) a).belakangPilih (e); jika (b! = null) ((WizardListener) b) .backSelected (e); } pembatalan tidak sah awamSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .cancelSelected (e); jika (b! = null) ((WizardListener) b) .cancelPilih (e); } penamat kosong awamPilih (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); jika (b! = null) ((WizardListener) b) .finishPilih (e); } EventListener statik dilindungi addInternal (EventListener a, EventListener b) {if (a == null) kembali b; jika (b == null) kembalikan a; mengembalikan WizardEventMulticaster baru (a, b); } EventListener dilindungi hapus (EventListener oldl) {if (oldl == a) kembali b; jika (oldl == b) kembalikan a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); jika (a2 == a && b2 == b) kembalikan ini; return addInternal (a2, b2); }}= null) ((WizardListener) b) .backSelected (e); } pembatalan tidak sah awamSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .cancelSelected (e); jika (b! = null) ((WizardListener) b) .cancelPilih (e); } penamat kosong awamPilih (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); jika (b! = null) ((WizardListener) b) .finishPilih (e); } EventListener statik dilindungi addInternal (EventListener a, EventListener b) {if (a == null) kembali b; jika (b == null) kembalikan a; mengembalikan WizardEventMulticaster baru (a, b); } EventListener dilindungi hapus (EventListener oldl) {if (oldl == a) kembali b; jika (oldl == b) kembalikan a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); jika (a2 == a && b2 == b) kembalikan ini; return addInternal (a2, b2); }}= null) ((WizardListener) b) .backSelected (e); } pembatalan tidak sah awamSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .cancelSelected (e); jika (b! = null) ((WizardListener) b) .cancelPilih (e); } penamat kosong awamPilih (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); jika (b! = null) ((WizardListener) b) .finishPilih (e); } EventListener statik dilindungi addInternal (EventListener a, EventListener b) {if (a == null) kembali b; jika (b == null) kembalikan a; mengembalikan WizardEventMulticaster baru (a, b); } EventListener dilindungi hapus (EventListener oldl) {if (oldl == a) kembali b; jika (oldl == b) kembalikan a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); jika (a2 == a && b2 == b) kembalikan ini; return addInternal (a2, b2); }}} pembatalan tidak sah awamSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .cancelSelected (e); jika (b! = null) ((WizardListener) b) .cancelPilih (e); } penamat kosong awamPilih (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); jika (b! = null) ((WizardListener) b) .finishPilih (e); } EventListener statik dilindungi addInternal (EventListener a, EventListener b) {if (a == null) kembali b; jika (b == null) kembalikan a; mengembalikan WizardEventMulticaster baru (a, b); } EventListener dilindungi hapus (EventListener oldl) {if (oldl == a) kembali b; jika (oldl == b) kembalikan a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); jika (a2 == a && b2 == b) kembalikan ini; return addInternal (a2, b2); }}} pembatalan tidak sah awamSelected (WizardEvent e) {if (a! = null) ((WizardListener) a) .cancelSelected (e); jika (b! = null) ((WizardListener) b) .cancelPilih (e); } penamat kosong awamPilih (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); jika (b! = null) ((WizardListener) b) .finishPilih (e); } EventListener statik dilindungi addInternal (EventListener a, EventListener b) {if (a == null) kembali b; jika (b == null) kembalikan a; mengembalikan WizardEventMulticaster baru (a, b); } EventListener dilindungi hapus (EventListener oldl) {if (oldl == a) kembali b; jika (oldl == b) kembalikan a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); jika (a2 == a && b2 == b) kembalikan ini; return addInternal (a2, b2); }}batalPilih (e); } penamat kosong awamPilih (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); jika (b! = null) ((WizardListener) b) .finishPilih (e); } EventListener statik dilindungi addInternal (EventListener a, EventListener b) {if (a == null) kembali b; jika (b == null) kembalikan a; mengembalikan WizardEventMulticaster baru (a, b); } EventListener dilindungi hapus (EventListener oldl) {if (oldl == a) kembali b; jika (oldl == b) kembalikan a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); jika (a2 == a && b2 == b) kembalikan ini; return addInternal (a2, b2); }}batalPilih (e); } penamat kosong awamPilih (WizardEvent e) {if (a! = null) ((WizardListener) a) .finishSelected (e); jika (b! = null) ((WizardListener) b) .finishPilih (e); } EventListener statik dilindungi addInternal (EventListener a, EventListener b) {if (a == null) kembali b; jika (b == null) kembalikan a; mengembalikan WizardEventMulticaster baru (a, b); } dilindungi EventListener remove (EventListener oldl) {if (oldl == a) kembali b; jika (oldl == b) kembalikan a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); jika (a2 == a && b2 == b) kembalikan ini; return addInternal (a2, b2); }}EventListener b) {if (a == null) kembali b; jika (b == null) kembalikan a; mengembalikan WizardEventMulticaster baru (a, b); } EventListener dilindungi hapus (EventListener oldl) {if (oldl == a) kembali b; jika (oldl == b) kembalikan a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); jika (a2 == a && b2 == b) kembalikan ini; return addInternal (a2, b2); }}EventListener b) {if (a == null) kembali b; jika (b == null) kembalikan a; mengembalikan WizardEventMulticaster baru (a, b); } dilindungi EventListener remove (EventListener oldl) {if (oldl == a) kembali b; jika (oldl == b) kembalikan a; EventListener a2 = removeInternal (a, oldl); EventListener b2 = removeInternal (b, oldl); jika (a2 == a && b2 == b) kembalikan ini; return addInternal (a2, b2); }}

Kaedah di kelas multicaster: Satu tinjauan

Mari kaji kaedah yang menjadi sebahagian daripada kelas multicaster di atas. Pembina dilindungi, dan untuk mendapatkan yang baru WizardEventMulticaster, add(WizardListener, WizardListener)kaedah statik mesti dipanggil. Diperlukan dua pendengar sebagai hujah yang mewakili dua bahagian rantai pendengar untuk dihubungkan:

  • Untuk memulakan rangkaian baru, gunakan null sebagai argumen pertama.

  • Untuk menambahkan pendengar baru, gunakan pendengar yang ada sebagai argumen pertama dan pendengar baru sebagai argumen kedua.

Sebenarnya, inilah yang telah dilakukan dalam kod kelas Wizardyang telah kita kaji.

Rutin statik lain adalah remove(WizardListener, WizardListener). Argumen pertama adalah pendengar (atau pendengar multicaster), dan yang kedua adalah pendengar yang akan dikeluarkan.

Four public, non-static methods were added to support event propagation through the event chain. For each WizardEvent case (that is, next, back, cancel, and finish selected) there is one method. These methods must be implemented since the WizardEventMulticaster implements WizardListener, which in turn requires the four methods to be present.

How it all works together

Let's now examine how the multicaster actually is used by the Wizard. Let's suppose a wizard object is constructed and three listeners are added, creating a listener chain.

Pada mulanya, pemboleh ubah peribadi wizardListenerkelas Wizardadalah nol. Oleh itu, apabila panggilan dibuat WizardEventMulticaster.add(WizardListener, WizardListener), argumen pertama wizardListener, adalah nol dan yang kedua tidak (tidak masuk akal untuk menambah pendengar yang tidak sah). Yang addkaedah, seterusnya, panggilan addInternal. Oleh kerana salah satu argumen adalah sia-sia, kembalinya addInternaladalah pendengar yang tidak batal. Return disebarkan ke addkaedah yang mengembalikan pendengar yang tidak nol ke addWizardListenerkaedah tersebut. Di sana wizardListenerpemboleh ubah ditetapkan ke pendengar baru yang ditambahkan.