javajoptionpanejcheckbox

Changing Button Text on JOptionPane during runtime


I want to change the text of the OK button (custom button text) during runtime with the ActionListener of a JCheckBox depending on the selection state. I dont want to create an own JDialog or JFrame.

I have a JPanel and add the JCheckBox to this JPanel. After this I add the JPanel to the JOptionpane.

As I have no direct access to the Button, I try to find the button. Below you can see what I already tried.

    JPanel myPanel = new JPanel();

    GridBagLayout gbl = new GridBagLayout();
    GridBagConstraints gbc = new GridBagConstraints();  
    myPanel.setLayout(gbl);

    // other components removed 

    JCheckBox chkboxCreateNew = new JCheckBox();
        gbc.fill = GridBagConstraints.HORIZONTAL;  
        gbc.ipady = 10;  
        gbc.gridx = 0;  
        gbc.gridy = 12;
        
        chkboxCreateNew.addActionListener(
                new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        
                        Window[] windows = Window.getWindows();
                        for (Window window : windows) {
                            if (window instanceof JDialog) {
                                for(Component c : window.getComponents()) {
                                    System.out.println("(window instanceof JDialog) Component: " + c.getClass());
                                    if(c instanceof JButton) {
                                        System.out.println("JButton found " + c.getName());
                                    }
                                }
                                
                                JDialog dialog = (JDialog) window;
                                
                                Component[] components = dialog.getContentPane().getComponents();
                                for (Component component : components) {
                                    System.out.println("(Component component) Component: " + component.getClass());
                                    if (component instanceof JButton) {
                                        JButton button = (JButton) component;
                                        if (button.getText().equals("Update")) {
                                            if (chkboxCreateNew.isSelected()) {
                                                button.setText("yyy");
                                            } else {
                                                button.setText("Test");
                                            }
                                            break;
                                        }
                                    }
                                }
                                
                                System.out.println("(JDialog) Component Count: " + dialog.getContentPane().getComponentCount());
                                if (dialog.getContentPane().getComponentCount() == 1
                                    && dialog.getContentPane().getComponent(0) instanceof JOptionPane){
                                    System.out.println("(JDialog) Component: " + dialog.getClass());
                                }
                            }
                        }

                        for(Component c : chkboxCreateNew.getParent().getParent().getParent().getComponents()) {
                            System.out.println("(Parent) Component: " + c.getClass() + " Name: " + c.getName());
                            if(c instanceof JButton) {
                                System.out.println("JButton " + c.getName());
                            }
                        }
                        
                        if(chkboxCreateNew.isSelected()) {
                            // nothing at the moment
                        } else {

                        }
                    }
                }
            );
        chkboxCreateNew.setText("Text");
        myPanel.add(chkboxCreateNew, gbc);
        
        // okButtonText
        Object[] options2 = {okButtonText, cancelButtonText};
        int result = JOptionPane.showOptionDialog(null,
                myPanel,
                "Name",
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.QUESTION_MESSAGE,
                null,         
                options2,     //the titles of buttons
                options2[0]); //default button title

Any idea ? Or is the only option to make my own JDialog ?


Solution

  • Most of the time, we use JOptionPane by invoking one of its static methods, just as you are doing when you call JOptionPane.showOptionDialog. However, JOptionPane is in fact a subclass of JComponent, with its own non-static methods and properties. When a static method is invoked, it creates an actual JOptionPane instance, calls that object’s createDialog method, and displays that JDialog.

    This means that when you call JOptionPane.showOptionDialog, myPanel has a JOptionPane ancestor.

    You can access that JOptionPane object and change its properties:

    chkboxCreateNew.addActionListener(e -> {
        JOptionPane optionPane = (JOptionPane)
            SwingUtilities.getAncestorOfClass(JOptionPane.class, myPanel);
    
        String okButtonText = chkboxCreateNew.isSelected() ? "yyy" : "Test";
        String cancelButtonText = "Cancel";
        Object[] newOptions = { okButtonText, cancelButtonText };
    
        optionPane.setOptions(newOptions);
        optionPane.setInitialValue(newOptions[0]);
    });
    

    …or at least, that’s how it should work. But JOptionPane stores the button selected by the user as its corresponding option value, not as an int; the showOptionDialog method tries to convert that option value to an int by looking for the index of that selected option value in the original array of option values. So the above code won’t actually work.

    To get around this, I created a very simple wrapper class for the option value. It allows the text to be changed, but as long as we use the same instance in the JOptionPane’s options array, the showOptionDialog method will see the chosen option value as equal to the original (since they are exactly the same object, with only the text property changed).

    class Option {
        private String text;
    
        Option(String text) {
            this.text = text;
        }
    
        void updateText(String text) {
            this.text = text;
        }
    
        @Override
        public String toString() {
            return text;
        }
    }
    
    chkboxCreateNew.addActionListener(e -> {
        JOptionPane optionPane = (JOptionPane)
            SwingUtilities.getAncestorOfClass(JOptionPane.class, myPanel);
    
        String newText =
            chkboxCreateNew.isSelected() ? "yyy" : "Test";
    
        Object[] options = optionPane.getOptions();
        Option okOption = (Option) options[0];
        okOption.updateText(newText);
        optionPane.updateUI();
    });
    
    Object[] options2 = { new Option("OK"), new Option("Cancel") };
    int response = JOptionPane.showOptionDialog(null,
        myPanel,
        "Name",
        JOptionPane.OK_CANCEL_OPTION,
        JOptionPane.QUESTION_MESSAGE,
        null,
        options2,
        options2[0]);
    

    It might seem unusual to declare a class inside a method, but this is perfectly legitimate in Java. It’s known as a local class. Since it is only used in this one method, it’s an appropriate use of a local class.

    updateUI() is needed because we are not changing the values of the JOptionPane’s options array, so the JOptionPane doesn’t know it needs to repaint its buttons. (We are changing an attribute of one of the options, but it’s still the same object; this is necessary for the showOptionDialog method to recognize it as an option in the original option array.)