Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting Mnemonics and Hot Keys for a JOptionPane Dialog

Is it possible to assign hotkeys and mnemonics to the buttons in a JOptionPane Dialog? I'd like to be able, in a JOptionPane generated message dialog with the options Yes, No and Cancel, press Y to hit the Yes button, N to hit the No button and escape to activate the escape button. Similarly in a dialog with Okay and Cancel buttons I'd like to be able to activate them with enter and escape.

I've attempted passing JButtons into the JOptionPane's button Object array with the Mnemonics set already. The mnemonics work and the buttons show up correctly in the dialogs, however, they do not act properly when they are activated. Most noticeably they do not dispose of the dialog.

What is the correct way to add hotkeys and Mnemonics to a JOptionPane Dialog's buttons?

like image 274
Daniel Bingham Avatar asked Oct 01 '09 18:10

Daniel Bingham


People also ask

What are the 4 JOptionPane dialog boxes?

The JOptionPane displays the dialog boxes with one of the four standard icons (question, information, warning, and error) or the custom icons specified by the user.

What is JOptionPane input dialog?

A dialog box is a small graphical window that displays a message to the user or requests input. Two of the dialog boxes are: – Message Dialog - a dialog box that displays a message. Input Dialog - a dialog box that prompts the user for input. The 'javax.swing.JOptionPane' class offers dialog box methods.

What package must be imported to use JOptionPane methods?

What package must be imported to use JOptionPane methods? The JOptionPane Class displays a dialog for user information and interaction it is part of the javax. swing package and must be imported before you can use it.


3 Answers

You can create your JOptionPane, and then loop through the components of the pane (children etc.) checking to see if any components are instanceof JButton, and if so check the text, and set the proper mnemonic.

JOptionPane p = new JOptionPane();
Component[] c = p.getComponents();
like image 185
broschb Avatar answered Sep 29 '22 12:09

broschb


Make use of UIManager as follows:

UIManager.put("OptionPane.okButtonMnemonic", "79");  // for Setting 'O' as mnemonic
UIManager.put("OptionPane.cancelButtonMnemonic", "67"); // for Setting 'C' as mnemonic
like image 34
Vikram Avatar answered Sep 29 '22 10:09

Vikram


You can look for a new window opening in the parent window by means of a Swing Worker. Then check if it is a JDialog and extract the button area. Then assign keys to the buttons. This works for the static methods of JOptionPane.

This is the Swing Worker class - just instantiate it and execute it right before the call to show the JOptionPane:

import java.awt.Component;
import java.awt.Window;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import javax.swing.FocusManager;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class HotKeyWorker extends SwingWorker<JComponent, Integer>
{
    private static final long TIMEOUT = 30000; //Don't wait forever for the JOptionPane
    private final String buttonAreaName;
    private final Map<String, ButtonData> buttonDataMap;
    private final Window owner;
    
    public HotKeyWorker(Component owner, Map<String, ButtonData> buttonDataMap)
    {
        this.buttonDataMap = buttonDataMap;
        if(owner instanceof Window)
            this.owner = (Window)owner;
        else if(owner != null)
            this.owner = SwingUtilities.windowForComponent(owner);
        else
            this.owner = null;
        buttonAreaName = getButtonAreaName();
    }

    @Override
    public JComponent doInBackground()
    {
        if(owner == null) return null;
        if(buttonAreaName == null) return null;
        long timeout = System.currentTimeMillis() + TIMEOUT;
        Window dialog = null;
        while(dialog == null && System.currentTimeMillis() < timeout)
        {
            dialog = FocusManager.getCurrentManager().getFocusedWindow();
            if(dialog != null)
                if(dialog.getOwner() != owner) 
                    dialog = null;
        }
        if(dialog instanceof JDialog)
            return getButtonArea(((JDialog)dialog).getRootPane());
        return null;
    }

    @Override
    public void done()
    {
        try
        {
            JComponent buttonArea = get();
            if(buttonArea != null)
                for(Component c : buttonArea.getComponents())
                    if(c instanceof JButton)
                        setHotKey((JButton)c);

        }
        catch(InterruptedException | ExecutionException ex) { /* Failed */ } 
    }

    private JComponent getButtonArea(JComponent component)
    {
        JComponent result = null;
        if(component.getName() != null)
            if(component.getName().equals(buttonAreaName) && component.getParent() instanceof JOptionPane)
                return component;
        for(Component c : component.getComponents())
            if(c instanceof JComponent)
                if((result = getButtonArea((JComponent)c)) != null)
                    return result;
        return result;
    }

    private void setHotKey(JButton button)
    {
        if(button.getText().isEmpty()) return;
        ButtonData data = buttonDataMap.get(button.getText());
        if(data == null) return;
        button.setText(data.updatedButtonText);
        button.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(data.hotKeyCode, 0), data.actionKey);
        button.getActionMap().put(data.actionKey, new ButtonPress(button));
    }
    
    private String getButtonAreaName()
    {
        JButton trace = new JButton();
        Object[] options = { trace };
        JOptionPane temp = new JOptionPane();
        temp.setOptions(options);
        Component buttonArea = trace.getParent();
        if(buttonArea != null)
            return buttonArea.getName();
        return null;
    }
}

These are the two helper classes to make things a little cleaner:

public class ButtonData
{
    public String updatedButtonText;
    public int hotKeyCode;
    public String actionKey;
    public ButtonData(String updatedButtonText, int hotKeyCode, String actionKey)
    {
        this.updatedButtonText = updatedButtonText;
        this.hotKeyCode = hotKeyCode;
        this.actionKey = actionKey;
    }
}

and

import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;

public class ButtonPress extends AbstractAction
{
    private final JButton button;
    public ButtonPress(JButton button) { this.button = button; }
    @Override
    public void actionPerformed(ActionEvent event) { button.doClick(); }
}

This is how you would use it:

ButtonData yesData = new ButtonData("<html><span style=\"color:Blue;\">Y</span>es</html>", KeyEvent.VK_Y, "yes");
ButtonData noData = new ButtonData("<html><span style=\"color:Blue;\">N</span>o</html>", KeyEvent.VK_N, "no");
ButtonData cancelData = new ButtonData("<html><span style=\"color:Blue;\">C</span>ancel</html>", KeyEvent.VK_C, "cancel");
Map<String, ButtonData> map = new HashMap<>();
map.put("Yes", yesData);
map.put("No", noData);
map.put("Cancel", cancelData);
HotKeyWorker worker = new HotKeyWorker(component, map);
worker.execute();
int result = JOptionPane.showConfirmDialog(component, "Just a text", "Confirm", JOptionPane.YES_NO_CANCEL_OPTION);

Here is a working example:

import javax.swing.JComponent;
import java.awt.Window;
import javax.swing.FocusManager;
import javax.swing.JDialog;
import java.awt.Component;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.JLabel;
import javax.swing.AbstractAction;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.KeyStroke;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.ExecutionException;
import javax.swing.SwingWorker;

public class Test 
{
    public static void main(String[] args) 
    {
        Frame frame = new Frame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
    
    private static class Frame extends JFrame
    {
        public Frame()
        {
            init();
        }

        private void init()
        {
            setSize(300,100);
            setTitle("Hot Key Test");
            add(new Panel());
        }

        private class Panel extends JPanel
        {
            private final JButton testButton;
            private final JLabel resultLabel;
            public Panel()
            {
                String testKey = "test";
                testButton = new JButton("<html><span style=\"color:Blue;\">T</span>est</html>");
                testButton.addActionListener((event) -> { test(this); });
                testButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0), testKey);
                testButton.getActionMap().put(testKey, new ButtonPress(testButton));
                resultLabel = new JLabel("No Result");
                init();
            }

            private void init()
            {
                add(testButton);
                add(resultLabel);
            }

            private void test(Component component)
            {
                String anotherTestKey = "Test";
                JButton anotherTestButton = new JButton("<html><span style=\"color:Blue;\">T</span>est</html>");
                anotherTestButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0), anotherTestKey);
                anotherTestButton.getActionMap().put(anotherTestKey, new ButtonPress(anotherTestButton));
                JLabel anotherTestLabel = new JLabel("No Result has been selected");
                JPanel testPanel = new JPanel();
                testPanel.add(anotherTestButton);
                testPanel.add(anotherTestLabel);
                anotherTestButton.addActionListener((event) -> { anotherTest(testPanel, anotherTestLabel); });


                ButtonData yesData = new ButtonData("<html><span style=\"color:Blue;\">Y</span>es</html>", KeyEvent.VK_Y, "yes");
                ButtonData noData = new ButtonData("<html><span style=\"color:Blue;\">N</span>o</html>", KeyEvent.VK_N, "no");
                ButtonData cancelData = new ButtonData("<html><span style=\"color:Blue;\">C</span>ancel</html>", KeyEvent.VK_C, "cancel");
                Map<String, ButtonData> map = new HashMap<>();
                map.put("Yes", yesData);
                map.put("No", noData);
                map.put("Cancel", cancelData);
                HotKeyWorker worker = new HotKeyWorker(component, map);
                worker.execute();
                int result = JOptionPane.showConfirmDialog(component, testPanel, "Confirm", JOptionPane.YES_NO_CANCEL_OPTION);
                switch(result)
                {
                    case 0 : resultLabel.setText("Yes Pressed"); break;
                    case 1 : resultLabel.setText("No Pressed"); break;
                    case 2 : resultLabel.setText("Cancel Pressed"); break;
                    default: resultLabel.setText("OptionPane Closed");
                }
            }

            public void anotherTest(Component component, JLabel label)
            {
                ButtonData fredData = new ButtonData("<html><span style=\"color:Blue;\">F</span>red</html>", KeyEvent.VK_F, "fred");
                ButtonData wilmaData = new ButtonData("<html><span style=\"color:Blue;\">W</span>ilma</html>", KeyEvent.VK_W, "wilma");
                ButtonData barneyData = new ButtonData("<html>B<span style=\"color:Blue;\">a</span>rney</html>", KeyEvent.VK_A, "barney");
                ButtonData bettyData = new ButtonData("<html>B<span style=\"color:Blue;\">e</span>tty</html>", KeyEvent.VK_E, "betty");
                Map<String, ButtonData> map = new HashMap<>();
                map.put("Fred", fredData);
                map.put("Wilma", wilmaData);
                map.put("Barney", barneyData);
                map.put("Betty", bettyData);
                HotKeyWorker worker = new HotKeyWorker(component, map);
                worker.execute();

                String[] options = {"Fred", "Wilma", "Barney", "Betty" };
                int result = JOptionPane.showOptionDialog(component, "Who do you like?", "Confirm", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, 0);
                switch(result)
                {
                    case 0 : label.setText("I like Fred"); break;
                    case 1 : label.setText("I like Wilma"); break;
                    case 2 : label.setText("I like Barney"); break;
                    case 3 : label.setText("I like Betty"); break;
                    default: label.setText("I decline to answer");
                }
            }
            
            private class HotKeyWorker extends SwingWorker<JComponent, Integer>
            {
                private static final long TIMEOUT = 30000; //Don't wait forever for the JOptionPane
                private final String buttonAreaName;
                private final Map<String, ButtonData> buttonDataMap;
                private final Window owner;

                public HotKeyWorker(Component owner, Map<String, ButtonData> buttonDataMap)
                {
                    this.buttonDataMap = buttonDataMap;
                    if(owner instanceof Window)
                        this.owner = (Window)owner;
                    else if(owner != null)
                        this.owner = SwingUtilities.windowForComponent(owner);
                    else
                        this.owner = null;
                    buttonAreaName = getButtonAreaName();
                }

                @Override
                public JComponent doInBackground()
                {
                    if(owner == null) return null;
                    if(buttonAreaName == null) return null;
                    long timeout = System.currentTimeMillis() + TIMEOUT;
                    Window dialog = null;
                    while(dialog == null && System.currentTimeMillis() < timeout)
                    {
                        dialog = FocusManager.getCurrentManager().getFocusedWindow();
                        if(dialog != null)
                            if(dialog.getOwner() != owner) 
                                dialog = null;
                    }
                    if(dialog instanceof JDialog)
                        return getButtonArea(((JDialog)dialog).getRootPane());
                    return null;
                }

                @Override
                public void done()
                {
                    try
                    {
                        JComponent buttonArea = get();
                        if(buttonArea != null)
                            for(Component c : buttonArea.getComponents())
                                if(c instanceof JButton)
                                    setHotKey((JButton)c);

                    }
                    catch(InterruptedException | ExecutionException ex) { /* Failed */ } 
                }

                private JComponent getButtonArea(JComponent component)
                {
                    JComponent result = null;
                    if(component.getName() != null)
                        if(component.getName().equals(buttonAreaName) && component.getParent() instanceof JOptionPane)
                            return component;
                    for(Component c : component.getComponents())
                        if(c instanceof JComponent)
                            if((result = getButtonArea((JComponent)c)) != null)
                                return result;
                    return result;
                }

                private void setHotKey(JButton button)
                {
                    if(button.getText().isEmpty()) return;
                    ButtonData data = buttonDataMap.get(button.getText());
                    if(data == null) return;
                    button.setText(data.updatedButtonText);
                    button.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(data.hotKeyCode, 0), data.actionKey);
                    button.getActionMap().put(data.actionKey, new ButtonPress(button));
                }

                private String getButtonAreaName()
                {
                    JButton trace = new JButton();
                    Object[] options = { trace };
                    JOptionPane temp = new JOptionPane();
                    temp.setOptions(options);
                    Component buttonArea = trace.getParent();
                    if(buttonArea != null)
                        return buttonArea.getName();
                    return null;
                }
            }
            
            private class ButtonData
            {
                public String updatedButtonText;
                public int hotKeyCode;
                public String actionKey;
                public ButtonData(String updatedButtonText, int hotKeyCode, String actionKey)
                {
                    this.updatedButtonText = updatedButtonText;
                    this.hotKeyCode = hotKeyCode;
                    this.actionKey = actionKey;
                }
            }
            
            private class ButtonPress extends AbstractAction
            {
                private final JButton button;
                public ButtonPress(JButton button) { this.button = button; }
                @Override
                public void actionPerformed(ActionEvent event) { button.doClick(); }
            }
        }
    }
}

This will work for all JOptionPane static methods. This will work fine as long as your not randomly popping up windows in the parent component's window. Note: the parent component of the JOptionPane cannot be null.


Of course, it is probably easier just to instantiate a JOptionPane and customize it. Here are the classes to do that:

import java.awt.Component;
import javax.swing.Icon;
import javax.swing.JOptionPane;
import javax.swing.JDialog;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.AbstractAction;
import javax.swing.Action;
import java.awt.event.ActionEvent;

public class HotKeyOptionPane
{
    public static int showOptionDialog(Component parentComponent,
                                    Object message, String title,
                                    int optionType, int messageType,
                                    Icon icon, HotKey[] options,
                                    Object initialValue)
    {
        JButton[] buttons = new JButton[options.length];
        for(int i = 0; i < options.length; i++)
            buttons[i] = new JButton(options[i].text);
        JOptionPane pane = new JOptionPane(message, messageType, optionType, icon, buttons, initialValue);
        for(int option = 0; option < buttons.length; option++)
            setButtonAction(buttons[option], options[option].keyCode, option, pane);
        JDialog dialog = pane.createDialog(parentComponent, title);
        dialog.setVisible(true);
 
        if (pane.getValue() instanceof Integer)
            return (Integer)pane.getValue();
        return -1;   
    }
    
    private static void setButtonAction(JButton button, int hotKey, Integer option, JOptionPane pane)
    {
        Action action = new AbstractAction()
        {
            @Override
            public void actionPerformed(ActionEvent event)
            {
                pane.setValue(option);
                pane.firePropertyChange(JOptionPane.VALUE_PROPERTY, JOptionPane.DEFAULT_OPTION, option);    
            }
        };
        button.addActionListener(action);
        button.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(hotKey, 0), button.getText());
        button.getActionMap().put(button.getText(), action);
    }      
}

and

public class HotKey 
{
    public String text;
    public int keyCode;
    public HotKey(String text, int keyCode)
    {
        this.text = text;
        this.keyCode = keyCode;
    }
}

And this is how you would use them:

import javax.swing.JComponent;
import java.awt.Component;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JOptionPane;
import javax.swing.JLabel;
import java.awt.event.KeyEvent;
import javax.swing.KeyStroke;

public class Test 
{
    public static void main(String[] args) 
    {
        Frame frame = new Frame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
    
    private static class Frame extends JFrame
    {
        public Frame()
        {
            init();
        }

        private void init()
        {
            setSize(300,100);
            setTitle("Hot Key Test");
            add(new Panel());
        }

        private class Panel extends JPanel
        {
            private final JButton testButton;
            private final JLabel resultLabel;
            public Panel()
            {
                String testKey = "test";
                testButton = new JButton("<html><span style=\"color:Blue;\">T</span>est</html>");
                testButton.addActionListener((event) -> { test(this); });
                testButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0), testKey);
                testButton.getActionMap().put(testKey, new ButtonPress(testButton));
                resultLabel = new JLabel("No Result");
                init();
            }

            private void init()
            {
                add(testButton);
                add(resultLabel);
            }
            
            private void test(Component component)
            {
                HotKey yesOption = new HotKey("<html><span style=\"color:Blue;\">Y</span>es</html>", KeyEvent.VK_Y);
                HotKey noOption = new HotKey("<html><span style=\"color:Blue;\">N</span>o</html>", KeyEvent.VK_N);
                HotKey cancelOption = new HotKey("<html><span style=\"color:Blue;\">C</span>ancel</html>", KeyEvent.VK_C);
                HotKey[] options = { yesOption, noOption, cancelOption };
                int result = HotKeyOptionPane.showOptionDialog(component, "Just a test", "Testing", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, 0);
                switch(result)
                {
                    case 0 : resultLabel.setText("Yes Pressed"); break;
                    case 1 : resultLabel.setText("No Pressed"); break;
                    case 2 : resultLabel.setText("Cancel Pressed"); break;
                    default: resultLabel.setText("OptionPane Closed");
                }
            }
        }
    }
}

"As far as the laws of mathematics refer to reality, they are not certain, and as far as they are certain, they do not refer to reality."

Albert Einstein

like image 39
user1819780 Avatar answered Sep 29 '22 11:09

user1819780