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;
  }
}
\$\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\$
    – Tim Hunter
    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.

\$\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\$
    – Tim Hunter
    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

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.