1
\$\begingroup\$

Put together a RadioMenu class that can use a Enum to generate a Single-Selection Radio Button Menu. My main question is about whether there's a way to remove the need to pass in the Class of the Enum to the generateButtonsFromEnum() method. Otherwise, would appreciate any general pointers on ways to improve this system since I'm still pretty new when it comes to AWT/Swing features.

RadioMenu

package tools;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import javax.swing.JMenu;
import javax.swing.JRadioButtonMenuItem;
public class RadioMenu<E extends Enum<E>> extends JMenu {
 private static final long serialVersionUID = 1L;
 
 private E currentState;
 private JRadioButtonMenuItem selectedRadioButton;
 
 private HashMap<E, JRadioButtonMenuItem> stateMap;
 
 public RadioMenu() {
 stateMap = new HashMap<E, JRadioButtonMenuItem>();
 }
 
 public RadioMenu(String name) {
 super(name);
 stateMap = new HashMap<E, JRadioButtonMenuItem>();
 }
 
 public void addRadioButton(E enumValue, JRadioButtonMenuItem radioButton) {
 //Set default to first added button
 if(stateMap.isEmpty()) {
 currentState = enumValue;
 radioButton.setSelected(true);
 selectedRadioButton = radioButton;
 }
 
 add(radioButton);
 stateMap.put(enumValue, radioButton);
 radioButton.addActionListener(new ActionListener() {
 @Override
 public void actionPerformed(ActionEvent e) {
 setState(enumValue);
 }
 });
 }
 
 public void generateButtonsFromEnum(Class<E> enumType) {
 for(E enumValue : enumType.getEnumConstants()) {
 addRadioButton(enumValue, new JRadioButtonMenuItem(enumValue.toString()));
 }
 }
 
 public E getState() {
 return currentState;
 }
 
 public void setState(E newState) {
 currentState = newState;
 selectedRadioButton.setSelected(false);
 
 selectedRadioButton = stateMap.get(newState);
 selectedRadioButton.setSelected(true);
 }
}

RadioMenuTest

package main;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.SwingUtilities;
import tools.RadioMenu;
public class RadioMenuTest implements Runnable {
 public enum RadioOptions {
 Forward, Backward, Left, Right
 }
 
 public static void main(String[] args) {
 SwingUtilities.invokeLater(new RadioMenuTest());
 }
 
 @Override
 public void run() {
 JFrame frame = new JFrame("RadioMenu Test");
 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
 frame.setJMenuBar(createMenuBar());
 
 frame.pack();
 frame.setLocationByPlatform(true);
 frame.setVisible(true);
 }
 private JMenuBar createMenuBar() {
 JMenuBar menuBar = new JMenuBar();
 JMenu fileMenu = new JMenu("File");
 menuBar.add(fileMenu);
 
 RadioMenu<RadioOptions> optionsMenu = new RadioMenu<RadioOptions>("Options");
 optionsMenu.generateButtonsFromEnum(RadioOptions.class);
 fileMenu.add(optionsMenu);
 
 return menuBar;
 }
}
asked Jun 24, 2021 at 19:48
\$\endgroup\$
5
  • 1
    \$\begingroup\$ may i ask if you are allowed to use RadioOptions[] values = RadioOptions.values(); ? doing so you could directly add all possible values (i don't know your assets). \$\endgroup\$ Commented Jun 25, 2021 at 9:01
  • 1
    \$\begingroup\$ Doing so would result in the following lines int the main method: optionsMenu.generateButtonsFromEnum(RadioOptions.values()); \$\endgroup\$ Commented Jun 25, 2021 at 9:09
  • 1
    \$\begingroup\$ it is not possible to know the class T of the type T at compile time. hence you have to provide either the class T (as you did) or you provide one (or more) instance(s) of T as suggested by enum.values() \$\endgroup\$ Commented Jun 29, 2021 at 8:44
  • \$\begingroup\$ @MartinFrank Ah, figured that was the case but nice to hear confirmation. Yeah, if the method is going to require a parameter no matter what it would make more sense to just pass the Enum values directly huh? Was just hoping there might be a way to avoid a parameter since the generic would be referencing the Enum anyways and it's members should be static if I recall correctly. \$\endgroup\$ Commented Jun 29, 2021 at 13:25
  • \$\begingroup\$ very nice code at all \$\endgroup\$ Commented Jun 30, 2021 at 7:46

1 Answer 1

2
\$\begingroup\$

dependency injection

with the feedback from your comments i would provide those <T extends Enum<T>> that you want to use already in your constructor. That would remove the not-so-handy method generateButtonsFromEnum.

public enum RadioOptions {Forward, Backward, Left, Right}
RadioMenu<RadioOptions> optionsMenu = new RadioMenu<>(RadioOptions.values());
optionsMenu.setText("Options")

doing so it would also let you create a reduced menu if you don't want to use all enum values:

public enum RadioOptions {Forward, Backward, Left, Right}
RadioOptions[] leftRight = {RadioOptions.Left, RadioOptions.Right} 
RadioMenu<RadioOptions> optionsMenu = new RadioMenu<>(leftRight);
optionsMenu.setText("Options")

Such an constructor you would provide an instance of T (as suggested in the comments).

Note

have a look at EnumMap a specialized map for storing/accessings Enums.

answered Jun 30, 2021 at 4:09
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Hit a snag with EnumMap where it requires the Class in the constructor (but I still like it, thanks for the tip). As a result, ended up with a compromise based on this advise where I pass the Class to the constructor and keep a generateButtons() method with a generateButtons(E[] enumValues) overload variant for generating using a array to keep the flexibility of generating using a subset. Opted to keep a generateButtons() method because I prefer that being explicitly executed instead of assumed behavior. \$\endgroup\$ Commented Jun 30, 2021 at 13:25
  • 1
    \$\begingroup\$ you made some good points =) a Review is very valuable to those who want to improve their skills and very rarely there is a plain right/wrong answer - thanks for sharing your thoughts! \$\endgroup\$ Commented Jun 30, 2021 at 14:05

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.