Tip Java 142: Mendorong JButtonGroup

Swing memiliki banyak kelas berguna yang memudahkan pengembangan antarmuka pengguna grafis (GUI). Namun, beberapa dari kelas ini tidak diimplementasikan dengan baik. Salah satu contoh kelas semacam itu adalah ButtonGroup. Artikel ini menjelaskan mengapa ButtonGroupdirancang dengan buruk dan menawarkan kelas pengganti JButtonGroup, yang mewarisi ButtonGroupdan memperbaiki beberapa masalahnya.

Catatan: Anda dapat mengunduh kode sumber artikel ini dari Sumber.

Lubang ButtonGroup

Berikut skenario umum dalam pengembangan GUI Swing: Anda membangun formulir untuk mengumpulkan data tentang item yang akan dimasukkan seseorang ke dalam database atau disimpan ke file. Formulir tersebut mungkin berisi kotak teks, kotak centang, tombol radio, dan widget lainnya. Anda menggunakan ButtonGroupkelas untuk mengelompokkan semua tombol radio yang membutuhkan satu pilihan. Saat desain formulir sudah siap, Anda mulai menerapkan data formulir. Anda menemukan sekumpulan tombol radio, dan Anda perlu mengetahui tombol mana dalam grup yang dipilih sehingga Anda dapat menyimpan informasi yang sesuai ke dalam database atau file. Anda sekarang terjebak. Mengapa? The ButtonGroupkelas tidak memberikan referensi ke tombol yang dipilih saat ini dalam kelompok.

ButtonGroupmemiliki getSelection()metode yang mengembalikan model tombol yang dipilih (sebagai ButtonModeltipe), bukan tombol itu sendiri. Sekarang, ini mungkin baik-baik saja jika Anda bisa mendapatkan referensi tombol dari modelnya, tetapi Anda tidak bisa. The ButtonModelinterface dan kelas pelaksanaannya tidak memungkinkan Anda untuk mengambil referensi tombol dari modelnya. Jadi apa yang kamu lakukan? Anda melihat ButtonGroupdokumentasi dan melihat getActionCommand()metodenya. Anda ingat bahwa jika Anda membuat contoh a JRadioButtondengan a Stringuntuk teks yang ditampilkan di sebelah tombol, dan kemudian Anda memanggil getActionCommand()tombol tersebut, teks dalam konstruktor kembali. Anda mungkin berpikir Anda masih dapat melanjutkan dengan kode karena meskipun Anda tidak memiliki referensi tombol setidaknya Anda memiliki teksnya dan masih mengetahui tombol yang dipilih.

Nah, kejutan! Kode Anda rusak saat runtime dengan NullPointerException. Mengapa? Karena getActionCommand()di ButtonModelkembali null. Jika Anda bertaruh (seperti yang saya lakukan) yang getActionCommand()menghasilkan hasil yang sama apakah yang disebut pada tombol atau pada model (yang merupakan kasus dengan banyak metode lain, seperti isSelected(), isEnabled(), atau getMnemonic()), Anda kehilangan. Jika Anda tidak secara eksplisit memanggil setActionCommand()tombol, Anda tidak menyetel perintah aksi dalam modelnya, dan metode pengambil kembali nulluntuk model tersebut. Namun, metode getter tidak kembali tombol teks saat dipanggil pada tombol. Berikut adalah getActionCommand()metode masuk AbstractButton, yang diwarisi oleh semua kelas tombol di Swing:

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

Ketidakkonsistenan dalam menetapkan dan mendapatkan perintah tindakan ini tidak dapat diterima. Anda dapat menghindari situasi ini jika setText()dalam AbstractButtonmenyetel perintah tindakan model ke teks tombol saat perintah tindakan adalah null. Setelah semua, kecuali setActionCommand()disebut secara eksplisit dengan beberapa Stringargumen (bukan nol), teks tombol yang dianggap perintah tindakan dengan tombol itu sendiri. Mengapa model berperilaku berbeda?

Ketika kode Anda membutuhkan referensi ke tombol yang saat ini dipilih di ButtonGroup, Anda perlu mengikuti langkah-langkah ini, tidak ada yang melibatkan pemanggilan getSelection():

  • Panggil getElements()pada ButtonGroup, yang mengembalikan sebuahEnumeration
  • Iterasi Enumerationuntuk mendapatkan referensi ke setiap tombol
  • Panggil isSelected()setiap tombol untuk menentukan apakah itu dipilih
  • Kembalikan referensi ke tombol yang mengembalikan nilai true
  • Atau, jika Anda memerlukan perintah tindakan, panggil getActionCommand()tombol tersebut

Jika ini terlihat seperti banyak langkah hanya untuk mendapatkan referensi tombol, baca terus. Saya yakin ButtonGrouppenerapannya salah secara fundamental. ButtonGrouptetap mengacu pada model tombol yang dipilih, padahal seharusnya tetap mengacu pada tombol itu sendiri. Selanjutnya, karena getSelection()mengambil metode tombol yang dipilih, Anda mungkin berpikir metode penyetel yang sesuai setSelection(), tetapi sebenarnya tidak: itu setSelected(). Sekarang, setSelected()punya masalah besar. Argumennya adalah a ButtonModeldan boolean. Jika Anda menelepon setSelected()pada ButtonGroupdan lulus model tombol yang bukan bagian dari kelompok dan truesebagai argumen, maka tombol menjadi dipilih, dan semua tombol dalam kelompok menjadi tidak dipilih. Dengan kata lain,ButtonGroupmemiliki kekuatan untuk memilih atau membatalkan pilihan tombol apa pun yang diteruskan ke metodenya, meskipun tombol tersebut tidak ada hubungannya dengan grup. Perilaku ini terjadi karena setSelected()di ButtonGrouptidak memeriksa apakah ButtonModelreferensi yang diterima sebagai argumen mewakili tombol dalam grup. Dan karena metode ini memberlakukan pemilihan tunggal, metode ini sebenarnya membatalkan pilihan tombolnya sendiri untuk memilih yang tidak terkait dengan grup.

Ketentuan dalam ButtonGroupdokumentasi ini bahkan lebih menarik:

Tidak ada cara untuk mengubah tombol secara terprogram menjadi 'off' untuk menghapus grup tombol. Untuk memberikan tampilan 'tidak ada yang dipilih', tambahkan tombol radio tak terlihat ke grup dan kemudian pilih tombol itu secara terprogram untuk mematikan semua tombol radio yang ditampilkan. Misalnya, tombol normal dengan label 'tidak ada' dapat disambungkan untuk memilih tombol radio yang tidak terlihat.

Yah, tidak juga. Anda dapat menggunakan tombol apa saja, duduk di mana saja di aplikasi Anda, terlihat atau tidak, dan bahkan dinonaktifkan. Ya, Anda bahkan dapat menggunakan grup tombol untuk memilih tombol yang dinonaktifkan di luar grup, dan tetap akan membatalkan pilihan semua tombolnya. Untuk mendapatkan referensi ke semua tombol dalam grup, Anda harus menyebutnya menggelikan getElements(). Apa hubungannya "elemen" ButtonGroupadalah menebak siapa pun. Nama tersebut mungkin terinspirasi oleh metode Enumerationkelas ( hasMoreElements()dan nextElement()), tetapi getElements()jelas harus diberi nama getButtons(). Sebuah grup tombol mengelompokkan tombol, bukan elemen.

Solusi: JButtonGroup

Untuk semua alasan ini saya ingin menerapkan kelas baru yang akan memperbaiki kesalahan ButtonGroupdan menyediakan beberapa fungsionalitas dan kenyamanan bagi pengguna. Saya harus memutuskan apakah kelas tersebut harus menjadi kelas baru atau warisan dari ButtonGroup. Semua argumen sebelumnya menyarankan pembuatan kelas baru daripada ButtonGroupsubkelas. Namun, ButtonModelantarmuka membutuhkan metode setGroup()yang membutuhkan ButtonGroupargumen. Kecuali jika saya juga siap untuk menerapkan ulang model tombol, satu-satunya pilihan saya adalah membuat subkelas ButtonGroupdan mengganti sebagian besar metodenya. Berbicara tentang ButtonModelantarmuka, perhatikan tidak adanya metode yang dipanggil getGroup().

Satu masalah lain yang belum saya sebutkan adalah bahwa secara ButtonGroupinternal menyimpan referensi ke tombolnya di a Vector. Dengan demikian, ia tidak perlu mendapatkan Vectoroverhead sinkronisasi , ketika ia harus menggunakan ArrayList, karena kelas itu sendiri tidak aman untuk thread dan tetap saja Swing adalah single thread. Namun, variabel yang dilindungi buttonsdideklarasikan sebagai Vectortipe, dan tidak Listseperti yang Anda harapkan dari gaya pemrograman yang baik. Jadi, saya tidak bisa menerapkan ulang variabel sebagai ArrayList; dan karena saya ingin memanggil super.add()dan super.remove(), saya tidak bisa menyembunyikan variabel superclass. Jadi saya mengabaikan masalahnya.

Saya mengusulkan kelas JButtonGroup, sesuai dengan sebagian besar nama kelas Swing. Kelas mengganti sebagian besar metode ButtonGroupdan menyediakan metode kenyamanan tambahan. Itu menyimpan referensi ke tombol yang saat ini dipilih, yang dapat Anda ambil dengan panggilan sederhana getSelected(). Berkat ButtonGroupimplementasi yang buruk, saya dapat menamai metode saya getSelected(), karena getSelection()merupakan metode yang mengembalikan model tombol.

Berikut adalah JButtonGroupmetodenya.

Pertama, saya membuat dua modifikasi pada add()metode: Jika tombol yang akan ditambahkan sudah ada dalam grup, metode akan kembali. Jadi, Anda tidak dapat menambahkan tombol ke grup lebih dari sekali. Dengan ButtonGroup, Anda dapat membuat JRadioButtondan menambahkannya 10 kali ke grup. Memanggil getButtonCount()akan menghasilkan 10. Ini seharusnya tidak terjadi, jadi saya tidak mengizinkan referensi duplikat. Kemudian, jika tombol tambah sebelumnya dipilih, itu menjadi tombol yang dipilih (ini adalah perilaku default ButtonGroup, yang masuk akal, jadi saya tidak menimpanya). The selectedButtonvariabel adalah referensi ke tombol yang dipilih saat ini dalam kelompok:

public void add (tombol AbstractButton) buttons.contains (button)) return; super.add (tombol); jika (getSelection () == button.getModel ()) selectedButton = button;  

add()Metode kelebihan beban menambahkan seluruh larik tombol ke grup. Ini berguna ketika Anda menyimpan referensi tombol dalam larik untuk pemrosesan blok (yaitu, menyetel batas, menambahkan pendengar tindakan, dll.):

public void add (tombol AbstractButton []) {if (buttons == null) return; untuk (int i = 0; i
   
    

Dua metode berikut ini menghapus tombol atau larik tombol dari grup:

public void hapus (tombol AbstractButton) {if (button! = null) {if (selectedButton == button) selectedButton = null; super.remove (tombol); }} public void hapus (tombol AbstractButton []) {if (buttons == null) return; 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 .