Search code examples
javauser-interfacejoptionpane

Adding an additional custom button to JOptionPane


I'm trying to show a frame that presents a user with a list of items, which the user can choose from (from a combobox menu) and the user is now able to select ok to choose the selected item, or press cancel, which returns null. These are the default options given to me.

Here's my code snippet:

Set<ICarOption> optionSet = spec.getCarOptions(category);
// If the category is not mandatory, create a makeshift option object to skip.
if(!category.getMandatory())    optionSet.add(new CarOption("skip"));
ICarOption[] optionArray = optionSet.toArray(new ICarOption[optionSet.size()]);

ICarOption selectedOption = (ICarOption)JOptionPane.showInputDialog(
                frame,
                "Choose one of the following options for Category " + category + ".\n" + 
                "If skip is available, you may choose it to skip this category.",
                "Select a feature",
                JOptionPane.QUESTION_MESSAGE, 
                null, 
                optionArray, 
                optionArray[0]);

This code happens in a for loop, iterating over categories, where the category is not always mandatory. This means that if I want to allow a user to have the option to skip a category, I implement another option to the combobox called skip, and if that is selected, I will skip it accordingly. But this feels like a dirty way of doing things (skip is not an option at all in the sense of my defined ICarOption object) and I'd rather have a button called skip that's greyed out (not clickable) if the category is mandatory and available when the category is not mandatory.

I've seen some examples here: http://docs.oracle.com/javase/7/docs/api/javax/swing/JOptionPane.html

It seems to show that I should replace my combobox with a self defined list of buttons, which is not what I want. I need to have 3 buttons (ok, skip, cancel), as well as the list of items.

UPDATE: To illustrate what my GUI would look like:

  1. The frame gets made.
  2. On the dialog window you see a combobox (dropdown list) of ICarOption objects
  3. There are also 3 buttons on the window: OK, SKIP and CANCEL
  4. If the category was mandatory, SKIP will be greyed out.
  5. If OK is selected, the currently selected item in the combobox will be given to the selectedOption variable
  6. If CANCEL is selected, selectedOption == null
  7. If SKIP is selected, this category will be skipped (continue;)

This means that on the input window I need to see a combobox with items and 3 buttons.

-Removed the subquestion-

UPDATE2: I just realized I can't use JButton either because I need to perform quite a few actions within the actionListener, and it requires the variables to be final, some of which cannot be final.

Currently my code looks like this:

JPanel panel = new JPanel();

JComboBox<ICarOption> optionsBox = new JComboBox<ICarOption>();
panel.add(optionsBox);
for(ICarOption option : spec.getCarOptions(category)){
    optionsBox.addItem(option);
}

Object[] options = { "Select option", "Skip", "Cancel" };

int selected = JOptionPane.showOptionDialog(
                panel,
                "Choose one of the following options for Category " + category + ".\n" + 
                "If skip is available, you may choose it to skip this category.",
                "Select option",
                JOptionPane.YES_NO_CANCEL_OPTION, 
                JOptionPane.INFORMATION_MESSAGE, null, 
                options, 
                options[0]);

if(selected == JOptionPane.NO_OPTION)   continue;
if(selected == JOptionPane.CANCEL_OPTION)   throw new UnavailableException();
if(selected == JOptionPane.YES_OPTION){
    ...
}

Inspiration gotten from: Java: Custom Buttons in showInputDialog

The problem with this is that I now don't have a way of controlling the skip button, as it gets created the moment the window gets created.

UPDATE3: It works now, but I'm not proud of how I did it..

        JPanel panel = new JPanel();

        JComboBox<ICarOption> optionsBox = new JComboBox<ICarOption>();
        panel.add(optionsBox);
        for(ICarOption option : spec.getCarOptions(category)){
            optionsBox.addItem(option);
        }

        int selected;
        if(!category.getMandatory()){
            Object[] options = { "Select option", "Cancel", "Skip" };

            selected = JOptionPane.showOptionDialog(
                    panel,
                    "Choose one of the following options for Category " + category + ".\n" + 
                    "If skip is available, you may choose it to skip this category.",
                    "Select option",
                    JOptionPane.YES_NO_CANCEL_OPTION, 
                    JOptionPane.INFORMATION_MESSAGE, null, 
                    options, 
                    options[0]);
        }
        else{
            Object[] options = { "Select option", "Cancel" };

            selected = JOptionPane.showOptionDialog(
                    panel,
                    "Choose one of the following options for Category " + category + ".\n" + 
                    "If skip is available, you may choose it to skip this category.",
                    "Select option",
                    JOptionPane.YES_NO_OPTION, 
                    JOptionPane.INFORMATION_MESSAGE, null, 
                    options, 
                    options[0]);
        }

        // careful! CANCEL_OPTION means skip has been pressed and NO_OPTION means cancel
        if(selected == JOptionPane.CANCEL_OPTION)   continue;
        if(selected == JOptionPane.NO_OPTION)   throw new UnavailableException();
        if(selected == JOptionPane.YES_OPTION){
            ...
        }

There's definitely a chunk of duplicate code there, but this is a way that works and feels better than passing skip as an object.

UPDATE4: Changed the duplicate part to the following:

        ArrayList<Object> tempList = new ArrayList<Object>();
        int optionType;

        tempList.add("Select option");
        tempList.add("Cancel");
        if(!category.getMandatory()){
            tempList.add("Skip");
            optionType = JOptionPane.YES_NO_CANCEL_OPTION;
        }
        else    optionType = JOptionPane.YES_NO_OPTION;

        Object[] options = tempList.toArray(new Object[tempList.size()]);

        int selected = JOptionPane.showOptionDialog(
                panel,
                "Choose one of the following options for Category " + category + ".\n" + 
                        "If skip is available, you may choose it to skip this category.",
                        "Select option",
                        optionType, 
                        JOptionPane.INFORMATION_MESSAGE, null, 
                        options, 
                        options[0]);

So I store the initialization in an ArrayList and convert it to an array afterwards. This is starting to look pretty good to me. :p I have to say I severely underestimated this problem. I simply wanted to change from adding an object to my list of items in order to skip, to having a skip button. That somehow took me several hours to do 'properly'.

small edit (note to user3469755): My apologies, I just looked over some of the previous edits and something just hit me. Your original answer provided me with what I wanted all along... The problem I had with using the listener is that I put a lot of the functionality in the OK-button, but all it really needed to do was to assign the selected item from the dropdown list to the parameter 'selectedOption'. The rest of the functionality I simply had to add after the showOptionDialog and the part where skip and cancel gets handled, as I'm sure that at that point, an item has been selected and the parameter will hold an object. I can be incredibly dense sometimes..


Solution

  • The documentation you linked is pretty straight forward:

    options: A more detailed description of the set of option buttons that will appear at the bottom of the dialog box. The usual value for the options parameter is an array of Strings. But the parameter type is an array of Objects

    You therefore just need to define some JButtons of your choice, wrap them in an array (like your String array) and then pass them onto the JOptionPane.showOptionDialog-method. For interactivity of the JButtons you can use some Mouseclick listeners for example and to grey-out and make it non-clickable (different things!) you can change the JButton-properties with setEnabled();

    Here is the full example I wrote:

        JButton jbt_ok = new JButton("OK");
        JButton jbt_skip = new JButton("Skip");
        JButton jbt_cancel = new JButton("Cancel");
    
        boolean greyOutSkipButton = true;
    
        jbt_ok.addMouseListener(new MouseAdapter() {
    
            @Override
            public void mouseClicked(MouseEvent e) {
                System.out.println("OK was clicked");
    
            }
        });
    
        jbt_cancel.addMouseListener(new MouseAdapter() {
    
            @Override
            public void mouseClicked(MouseEvent e) {
                System.out.println("Cancel was clicked");
    
            }
        });
    
        if(greyOutSkipButton)
            jbt_skip.setEnabled(false);
        else
    
            jbt_skip.addMouseListener(new MouseAdapter() {
    
                @Override
                public void mouseClicked(MouseEvent e) {
                    System.out.println("Skip was clicked");
    
                }
            });
    
        Object[] options = {jbt_ok, jbt_skip, jbt_cancel};
    
        JOptionPane.showOptionDialog(null, "Click OK to continue", "Warning",
                JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
                null, options, options[0]);