Setting Mnemonics and Hot Keys for a JOptionPane Dialog
Asked Answered
W

4

6

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?

Weddle answered 1/10, 2009 at 18:6 Comment(1)
See also: #4793766Doha
N
2

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();
Northington answered 1/10, 2009 at 20:0 Comment(3)
These are the dialogs created with the JOptionPane.showDialog() functions. They return an integer value, and as far as I know there's no way to access an instance of them. Legacy code.Weddle
Sounds like you will need to override the JOptionPane class then to add the mnemonic. Either that or create the JOptionPane component, and put it in a dialog yourself, and then you can wrap through the compoents, or better yet, just create your own dialog or option dialog to meet your needs, sounds like that may be your best option, giving you full control of the dialog. Once you create the dialog once, you can then use it everywhere you need it.Northington
Yeah, the issue was trying to force the mnemonics into the JOptionPane using legacy code with out much refactoring. The more research I'm doing the more it looks impossible. The only way to do it is seems is option A) make my own JDialog or option B) extend JOptionPane. Neither works for this situation.Weddle
I
2

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
Irizarry answered 15/8, 2012 at 22:46 Comment(0)
H
1

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

Hardenberg answered 14/7, 2020 at 9:28 Comment(0)
P
0

Send the buttons in as parameters instead of Strings

    JButton button1 = new JButton( "<html>" + nextQuestion1 + "</html>");
    button1.setMnemonic('a');
    JButton button2 = new JButton(nextQuestion2 + "VUHU");
    JButton button3 = new JButton(abort);
    Object[] possibleValues = new Object[]{button1,button2,button3};
    int selectedValue = showOptionDialog(owner, question, possibleValues);
Patrickpatrilateral answered 8/12, 2016 at 11:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.