Shaun
Shaun

Reputation: 75

Making a help menu

I'm adding a Help Menu to my program and I'm trying to figure out if there's a way to intercept keyboard input without having to use the setFocusable method on the JFrame. It works no problem if I set the focus to the frame and press F1. The problem comes in when they press on any other component. The Frame is no longer in focus, so the F1 key no longer displays the menu. Here is how I did it

package helpmenu;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JFrame;

public class HelpMenu extends JFrame
{
    public HelpMenu() 
    {
        setSize(300, 300);
        setTitle("Help Menu");
    }


    /**
     * Sets a KeyListener to
     * the passed in JFrame.
     * @param mainFrame
     */
    public void callMenu( JFrame mainFrame )
    {
        mainFrame.addKeyListener( new functionListener() ); //Set keylistener to the main frame.
        mainFrame.setFocusable(true); //Put the main frame into focus.
    }

    /**
     * Inner KeyListener
     * class for the mainFrame
     * @author Shaun
     */
    class functionListener implements KeyListener
    {
        @Override
        public void keyPressed(KeyEvent e)
        {
            //Check if the F1 key was pressed
            if ( e.getKeyCode() == KeyEvent.VK_F1 ) { 
                setVisible(true);
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {}

        @Override
        public void keyTyped(KeyEvent e) {}
    }
}

Upvotes: 0

Views: 908

Answers (1)

Radiodef
Radiodef

Reputation: 37855

Don't use KeyListener for this type of thing. As you've noticed, one reason is it requires the component to be in focus. Instead, use a Key Binding with the WHEN_ANCESTOR_OF_FOCUSED_COMPONENT input map.

Also, somewhat related, see "The Use of Multiple JFrames, Good/Bad Practice?" Your Help Menu should probably be a JDialog.

Here's an example of using a Key Binding:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class HelpDialogEx {
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new HelpDialogEx();
            }
        });
    }

    final JFrame frame = new JFrame("Press F1 for help");
    final JPanel cpane = new JPanel(new GridLayout(4, 5, 10, 10));

    final HelpDialog help = new HelpDialog(frame);

    final AbstractAction helpToggle = new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent ae) {
            help.setVisible(!help.isVisible());
        }
    };

    HelpDialogEx() {
        for(int i = 0; i < 20; i++) {
            cpane.add(new JButton("Nil"));
        }

        cpane.setPreferredSize(new Dimension(640, 480));
        frame.setContentPane(cpane);

        addHelpToggle(frame.getRootPane());
        addHelpToggle(help.getRootPane());

        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    void addHelpToggle(JComponent comp) {
        KeyStroke f1Key = KeyStroke.getKeyStroke(
            KeyEvent.VK_F1, 0, true
        );
        String cmd = "helpToggle";

        for(InputMap im : new InputMap[] {
            comp.getInputMap(JComponent.WHEN_FOCUSED),
            comp.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
        }) {
            im.put(f1Key, cmd);
        }

        comp.getActionMap().put(cmd, helpToggle);
    }

    static class HelpDialog
    extends JDialog {
        HelpDialog(Window parent) {
            super(parent, "\"Help\"", Dialog.ModalityType.MODELESS);

            JTextArea doc = new JTextArea(getUnhelpfulText());
            doc.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            doc.setEditable(false);
            doc.setLineWrap(true);

            JScrollPane scroll = new JScrollPane(doc);
            scroll.getViewport().setPreferredSize(new Dimension(480, 360));

            setContentPane(scroll);

            pack();
            setLocationRelativeTo(null);
        }

        static String getUnhelpfulText() {
            StringBuilder sb = new StringBuilder();

            addRandomParagraph(sb);
            for(int i = 0; i < 3; i++) {
                sb.append("\n\n");
                addRandomParagraph(sb);
            }

            return sb.toString();
        }

        static void addRandomParagraph(StringBuilder sb) {
            sb.append("     ");
            addRandomSentence(sb);
            for(int i = 0; i < 10; i++) {
                sb.append(' ');
                addRandomSentence(sb);
            }
        }

        static void addRandomSentence(StringBuilder sb) {
            sb.append((char)(Math.random() * 26 + 'A'));

            for(int i = (int)(Math.random() * 10 + 1); i > 0; i--) {
                for(int len = (int)(Math.random() * 10 + 1); len > 0; len--) {
                    sb.append((char)(Math.random() * 26 + 'a'));
                }

                sb.append(' ');
            }

            sb.setCharAt(sb.length() - 1, '.');
        }
    }
}

I've found that although the tutorial says that WHEN_ANCESTOR_OF_FOCUSED_COMPONENT should cover when the component itself is focused, it doesn't.

Upvotes: 3

Related Questions