Petua Java 142: Mendorong JButtonGroup

Swing mempunyai banyak kelas berguna yang memudahkan pembangunan antara muka pengguna grafik (GUI). Sebilangan kelas ini, bagaimanapun, tidak dilaksanakan dengan baik. Salah satu contoh kelas sedemikian adalah ButtonGroup. Artikel ini menerangkan mengapa ButtonGroupdirancang dengan buruk dan menawarkan kelas pengganti JButtonGroup, yang mewarisi ButtonGroupdan memperbaiki beberapa masalahnya.

Catatan: Anda boleh memuat turun kod sumber artikel ini dari Sumber.

Lubang Kumpulan Butang

Berikut adalah senario umum dalam pengembangan Swing GUI: Anda membuat borang untuk mengumpulkan data mengenai item yang akan dimasukkan seseorang ke dalam pangkalan data atau menyimpan ke fail. Bentuknya mungkin mengandungi kotak teks, kotak centang, butang radio, dan widget lain. Anda menggunakan ButtonGroupkelas untuk mengumpulkan semua butang radio yang memerlukan satu pilihan. Apabila reka bentuk borang siap, anda mula melaksanakan data borang. Anda menemui sekumpulan butang radio, dan anda perlu mengetahui butang mana dalam kumpulan yang dipilih sehingga anda dapat menyimpan maklumat yang sesuai ke dalam pangkalan data atau fail. Anda sekarang buntu. Kenapa? The ButtonGroupkelas tidak memberikan anda satu merujuk kepada butang yang sedang dipilih dalam kumpulan.

ButtonGroupmempunyai getSelection()kaedah yang mengembalikan model butang yang dipilih (sebagai ButtonModeljenis), bukan butang itu sendiri. Sekarang, ini mungkin baik-baik saja jika anda dapat memperoleh rujukan butang dari modelnya, tetapi anda tidak dapat. Antara ButtonModelmuka dan kelas pelaksanaannya tidak membolehkan anda mengambil rujukan butang dari modelnya. Jadi apa yang anda lakukan? Anda melihat ButtonGroupdokumentasi dan melihat getActionCommand()kaedahnya. Kamu ingat bahawa jika anda memberi contoh yang JRadioButtondengan Stringteks yang dipaparkan di sebelah butang, dan kemudian anda memanggil getActionCommand()pada butang, teks dalam pulangan pembina. Anda mungkin berfikir anda masih boleh meneruskan kod tersebut kerana walaupun anda tidak mempunyai rujukan butang sekurang-kurangnya anda mempunyai teksnya dan masih mengetahui butang yang dipilih.

Baiklah, kejutan! Kod anda rosak pada waktu runtime dengan a NullPointerException. Kenapa? Kerana getActionCommand()dalam ButtonModelpulangan null. Jika anda buat taruhan (seperti yang saya lakukan) yang getActionCommand()menghasilkan keputusan yang sama sama ada menggesa butang atau kepada model (yang halnya dengan banyak kaedah lain, seperti isSelected(), isEnabled()atau getMnemonic()), anda hilang. Sekiranya anda tidak memanggil setActionCommand()butang secara eksplisit , anda tidak menetapkan perintah tindakan dalam modelnya, dan kaedah mendapatkan kembali nulluntuk model tersebut. Walau bagaimanapun, kaedah getter yang tidak kembali teks butang apabila dipanggil pada butang. Inilah getActionCommand()kaedah di AbstractButton, yang diwarisi oleh semua kelas butang di Swing:

public String getActionCommand () {String ac = getModel (). getActionCommand (); jika (ac == null) {ac = getText (); } kembali ac; }

Ketidakkonsistenan dalam menetapkan dan mendapatkan perintah tindakan tidak dapat diterima. Anda boleh mengelakkan situasi ini jika setText()dalam AbstractButtonmenetapkan perintah tindakan model ke teks butang ketika perintah tindakan tidak ada. Selepas semua, melainkan setActionCommand()dipanggil jelas dengan beberapa Stringhujah (tidak batal), teks butang adalah dianggap arahan tindakan dengan butang itu sendiri. Mengapa model itu mesti berlainan?

Apabila kod anda memerlukan rujukan ke butang yang sedang dipilih di ButtonGroup, Anda perlu mengikuti langkah-langkah ini, yang tidak melibatkan panggilan getSelection():

  • Memanggil getElements()pada ButtonGroup, yang mengembalikanEnumeration
  • Ulangi melalui Enumerationuntuk mendapatkan rujukan ke setiap butang
  • Panggil isSelected()setiap butang untuk menentukan sama ada ia dipilih
  • Kembalikan rujukan ke butang yang kembali benar
  • Atau, jika anda memerlukan arahan tindakan, getActionCommand()tekan butang

Sekiranya ini seperti banyak langkah untuk mendapatkan rujukan butang, baca terus. Saya yakin ButtonGrouppelaksanaannya pada dasarnya salah. ButtonGroupmenyimpan rujukan pada model butang yang dipilih sedangkan ia sebenarnya harus merujuk pada butang itu sendiri. Selanjutnya, kerana getSelection()mengambil kaedah butang yang dipilih, anda mungkin menganggap kaedah setter yang sesuai adalah setSelection(), tetapi tidak: itu setSelected(). Sekarang, setSelected()mempunyai masalah besar. Argumennya adalah ButtonModeldan boolean. Jika anda membuat panggilan setSelected()pada ButtonGroupdan lulus model butang ini yang bukan sebahagian daripada kumpulan dan truesebagai hujah, maka yang menjadi butang dipilih dan semua butang dalam kumpulan menjadi tidak dipilih. Dalam kata lain,ButtonGroupmempunyai kekuatan untuk memilih atau tidak memilih mana-mana butang yang diteruskan ke kaedahnya, walaupun butang itu tidak ada kaitan dengan kumpulan itu. Tingkah laku ini berlaku kerana setSelected()dalam ButtonGrouptidak memeriksa sama ada ButtonModelrujukan yang diterima sebagai argumen mewakili butang dalam kumpulan. Dan kerana kaedah ini menerapkan pilihan tunggal, ia sebenarnya memilih butang sendiri untuk memilih yang tidak berkaitan dengan kumpulan.

Ketentuan dalam ButtonGroupdokumentasi ini lebih menarik:

Tidak ada cara untuk menghidupkan butang secara terprogram menjadi 'mati' untuk membersihkan kumpulan butang. Untuk memberikan penampilan 'tidak ada yang dipilih', tambahkan butang radio yang tidak kelihatan ke kumpulan dan kemudian pilih butang itu secara terprogram untuk mematikan semua butang radio yang dipaparkan. Sebagai contoh, butang biasa dengan label 'tidak ada' boleh disambungkan untuk memilih butang radio yang tidak kelihatan.

Tidak juga. Anda boleh menggunakan butang apa pun, duduk di mana sahaja dalam aplikasi anda, kelihatan atau tidak, malah dilumpuhkan. Ya, anda bahkan boleh menggunakan kumpulan butang untuk memilih butang yang dilumpuhkan di luar kumpulan, dan ia masih akan membatalkan pilihan semua butangnya. Untuk mendapatkan rujukan ke semua butang dalam kumpulan, anda mesti memanggilnya tidak masuk akal getElements(). Apa yang ada kaitan dengan "unsur" ButtonGroupadalah sangkaan sesiapa sahaja. Nama itu mungkin diilhamkan oleh Enumerationkaedah ( hasMoreElements()dan nextElement()) kelas, tetapi getElements()jelas harus dinamakan getButtons(). Butang kumpulan mengumpulkan butang, bukan elemen.

Penyelesaian: JButtonGroup

Atas sebab-sebab ini saya ingin menerapkan kelas baru yang akan memperbaiki kesalahan ButtonGroupdan memberikan beberapa fungsi dan kemudahan kepada pengguna. Saya harus memutuskan sama ada kelas itu harus kelas baru atau waris dari ButtonGroup. Semua hujah sebelumnya mencadangkan membuat kelas baru dan bukannya ButtonGroupsubkelas. Walau bagaimanapun, ButtonModelantara muka memerlukan kaedah setGroup()yang memerlukan ButtonGroupargumen. Kecuali saya sudah bersedia untuk mengimplementasikan semula model butang juga, satu-satunya pilihan saya adalah untuk melakukan subkelas ButtonGroupdan mengatasi kebanyakan kaedahnya. Bercakap mengenai ButtonModelantara muka, perhatikan tidak adanya kaedah yang disebut getGroup().

Satu masalah lain yang belum saya sebutkan adalah bahawa secara ButtonGroupdalaman menyimpan rujukan pada butang di a Vector. Oleh itu, ia tidak perlu mendapat Vectoroverhead yang disegerakkan , ketika harus menggunakan ArrayList, kerana kelas itu sendiri tidak selamat untuk benang dan Swing tetap berulir tunggal. Namun, pemboleh ubah yang dilindungi buttonsdinyatakan sebagai Vectorjenis, dan tidak Listseperti yang anda harapkan dari gaya pengaturcaraan yang baik. Oleh itu, saya tidak dapat mengimplementasikan semula pemboleh ubah sebagai ArrayList; dan kerana saya mahu memanggil super.add()dan super.remove(), saya tidak dapat menyembunyikan pemboleh ubah superclass. Oleh itu, saya meninggalkan isu ini.

Saya mencadangkan kelas JButtonGroup, dengan nada sebilangan besar nama kelas Swing. Kelas mengatasi kebanyakan kaedah ButtonGroupdan menyediakan kaedah kemudahan tambahan. Ini menyimpan rujukan ke butang yang sedang dipilih, yang dapat anda dapatkan dengan panggilan mudah getSelected(). Berkat ButtonGroupimplementasi yang kurang baik, saya dapat menamakan kaedah saya getSelected(), kerana getSelection()merupakan kaedah yang mengembalikan model butang.

Berikut adalah JButtonGroupkaedah.

Pertama, saya membuat dua pengubahsuaian add()kaedah: Sekiranya butang yang akan ditambahkan sudah ada dalam kumpulan, kaedah akan kembali. Oleh itu, anda tidak dapat menambahkan butang ke kumpulan lebih dari sekali. Dengan ButtonGroup, anda boleh membuat JRadioButtondan menambahkannya 10 kali ke kumpulan. Panggilan getButtonCount()kemudian akan kembali 10. Ini tidak seharusnya berlaku, jadi saya tidak membenarkan rujukan pendua. Kemudian, jika butang yang ditambahkan sebelumnya dipilih, ia menjadi butang yang dipilih (ini adalah tingkah laku lalai ButtonGroup, yang wajar, jadi saya tidak menimpanya). Yang selectedButtonberubah-ubah adalah merujuk kepada butang yang sedang dipilih dalam kumpulan:

kekosongan awam menambah butang (butang AbstractButton). mengandungi (butang)) kembali; super.add (butang); jika (getSelection () == button.getModel ()) dipilihButton = butang;  

add()Kaedah yang terlalu banyak menambah sebilangan butang ke kumpulan. Ia berguna apabila anda menyimpan rujukan butang dalam larik untuk pemprosesan blok (iaitu, menetapkan sempadan, menambahkan pendengar tindakan, dll.):

kekosongan awam tambah (butang AbstractButton []) {if (butang == null) kembali; untuk (int i = 0; i
   
    

Dua kaedah berikut mengeluarkan butang atau pelbagai butang dari kumpulan:

kekosongan awam hapus (butang AbstractButton) {if (butang! = null) {if (dipilihButton == butang) dipilihButton = null; super.remove (butang); }} pembatalan kekosongan awam (butang AbstractButton []) {if (butang == null) kembali; untuk (int i = 0; i
     
      

Hereafter, the first setSelected() method lets you set a button's selection state by passing the button reference instead of its model. The second method overrides the corresponding setSelected() in ButtonGroup to assure that the group can only select or unselect a button that belongs to the group:

public void setSelected(AbstractButton button, boolean selected) { if (button != null && buttons.contains(button)) { setSelected(button.getModel(), selected); if (getSelection() == button.getModel()) selectedButton = button; } } public void setSelected(ButtonModel model, boolean selected) { AbstractButton button = getButton(model); if (buttons.contains(button)) super.setSelected(model, selected); } 

The getButton() method retrieves a reference to the button whose model is given. setSelected() uses this method to retrieve the button to be selected given its model. If the model passed to the method belongs to a button outside the group, null is returned. This method should exist in the ButtonModel implementations, but unfortunately it does not:

public AbstractButton getButton(ButtonModel model) { Iterator it = buttons.iterator(); while (it.hasNext()) { AbstractButton ab = (AbstractButton)it.next(); if (ab.getModel() == model) return ab; } return null; } 

getSelected() and isSelected() are the simplest and probably most useful methods of the JButtonGroup class. getSelected() returns a reference to the selected button, and isSelected() overloads the method of the same name in ButtonGroup to take a button reference:

public AbstractButton getSelected() { return selectedButton; } public boolean isSelected(AbstractButton button) { return button == selectedButton; } 

This method checks whether a button is part of the group:

public boolean contains(AbstractButton button) { return buttons.contains(button); } 

You would expect a method named getButtons() in a ButtonGroup class. It returns an immutable list containing references to the buttons in the group. The immutable list prevents button addition or removal without going through the button group's methods. getElements() in ButtonGroup not only has a totally uninspired name, but it returns an Enumeration, which is an obsolete class you shouldn't use. The Collections Framework provides everything you need to avoid enumerations. This is how getButtons() returns an immutable list:

public List getButtons() { return Collections.unmodifiableList(buttons); } 

Improve ButtonGroup

The JButtonGroup class offers a better and more convenient alternative to the Swing ButtonGroup class, while preserving all of the superclass's functionality.

Daniel Tofan is as a postdoctoral associate in the Chemistry Department at State University of New York, Stony Brook. His work involves developing the core part of a course management system with application in chemistry. He is a Sun Certified Programmer for the Java 2 Platform and holds a PhD in chemistry.

Learn more about this topic

  • Download the source code that accompanies this article

    //images.techhive.com/downloads/idge/imported/article/jvw/2003/09/jw-javatip142.zip

  • Sun Microsystems' Java Foundation Classes homepage

    //java.sun.com/products/jfc/

  • Java 2 Platform, Standard Edition (J2SE) 1.4.2 API documentation

    //java.sun.com/j2se/1.4.2/docs/api/

  • ButtonGroup class

    //java.sun.com/j2se/1.4.2/docs/api/javax/swing/ButtonGroup.html

  • View all previous Java Tips and submit your own

    //www.javaworld.com/columns/jw-tips-index.shtml

  • Browse the AWT/Swing section of JavaWorld's Topical Index

    //www.javaworld.com/channel_content/jw-awt-index.shtml

  • Browse the Foundation Classes section of JavaWorld's Topical Index

    //www.javaworld.com/channel_content/jw-foundation-index.shtml

  • Browse the User Interface Design section of JavaWorld's Topical Index

    //www.javaworld.com/channel_content/jw-ui-index.shtml

  • Visit the JavaWorld Forum

    //www.javaworld.com/javaforums/ubbthreads.php?Cat=&C=2

  • Sign up for JavaWorld's free weekly email newsletters

    //www.javaworld.com/subscribe

This story, "Java Tip 142: Pushing JButtonGroup" was originally published by JavaWorld .