5

I am working with a Swing program and having a little trouble. The program has two windows (both are JFrames). The main window is just fine and should not be relevant to this issue.

The window I am having issues with contains a JScrollPane with a JPanel in it, and has a JMenuBar. The JPanel has a bunch of JTextComponents (some JTextFields, some JTextAreas) on it.

What I want to do is have an ActionListener attached to a JMenuItem find the JTextComponent that has focus.

I have seen the previous posts at focused component reference and How to find out which object currently has focus. My issue is that calling the particular window's getFocusOwner() method merely returns the JFrame's JRootPane, which is utterly unhelpful. Both the JScrollPane and the JPanel in question are focusable according to their isFocusable() methods. This happens even if I actually enter text into one of the JTextComponents before clicking the menu item. The cursor still blinks in the text field while I open the menu, and everything. For what it's worth, getMostRecentFocusOwner() also simply returns the JRootPane.

Community
  • 1
  • 1
DYS
  • 87
  • 4
  • no idea something wrong in your code, both methods getFocusOwner() and getMostRecentFocusOwner() should be returns the same Component, thats right, for JMenuItem to use events from ButtonModel, events form ActiionListener correctly return Focus to the Container, for better help sooner post an [SSCCE](http://sscce.org/) demonstrated your issue with JMenu(Xxx) and ActionListener, JMenu and JMenuItems have got own Listeners too – mKorbel Jul 02 '12 at 21:24
  • Are you sure that you need this information? Often there's more than one way to solve an over-all problem. – Hovercraft Full Of Eels Jul 02 '12 at 21:49
  • I think I have got what you want working, please see my revised post – David Kroukamp Jul 02 '12 at 23:14
  • *"The program has two windows (both are JFrames)."* See [The Use of Multiple JFrames, Good/Bad Practice?](http://stackoverflow.com/a/9554657/418556) – Andrew Thompson Jul 03 '12 at 07:45
  • 1
    That's an interesting perspective, and perhaps my purposes would be equally well-served with JDialog. However, the particular way this application is designed, basically to be as abstract and easily-extensible, by people who are experienced programmers but not necessarily with Java or Swing, I believe justifies this choice. (For example, the ability to launch one of the sub-windows from an otherwise command-line-only session is a circumstance I can foresee.) – DYS Jul 06 '12 at 22:35

4 Answers4

2

If you use TextActions, the action knows which JTextComponent has the focus. I've modified some code that I found here to show that even if the TextActions come from one JTextArea, they still will automatically work on any and all text components that have focus:

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

public class TextActionExample {
  public static void main(String[] args) {

    // Create a text area.
    JTextArea ta = new JTextArea(15, 30);
    ta.setLineWrap(true);

    // Add all actions to the menu (split into two menus to make it more
    // usable).
    Action[] actions = ta.getActions();
    JMenuBar menubar = new JMenuBar();
    JMenu actionmenu = new JMenu("Actions");
    menubar.add(actionmenu);

    JMenu firstHalf = new JMenu("1st Half");
    JMenu secondHalf = new JMenu("2nd Half");
    actionmenu.add(firstHalf);
    actionmenu.add(secondHalf);

    int mid = actions.length / 2;
    for (int i = 0; i < mid; i++) {
      firstHalf.add(actions[i]);
    }
    for (int i = mid; i < actions.length; i++) {
      secondHalf.add(actions[i]);
    }

    JTextField textField = new JTextField(20);
    JPanel textFieldPanel = new JPanel();
    textFieldPanel.add(textField);

    JPanel mainPanel = new JPanel(new GridLayout(1, 0, 5, 5));
    mainPanel.add(new JScrollPane(ta));
    mainPanel.add(new JScrollPane(new JTextArea(15, 30)));
    mainPanel.add(textFieldPanel);


    // Show it . . .
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.getContentPane().add(mainPanel);
    f.setJMenuBar(menubar);
    f.pack();
    f.setVisible(true);
  }
}

This is very interesting stuff that I have to learn more about.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • What David said (I would +1 if I could). Hopefully at some point I'll upgrade his solution to something in this vein - it seems infinitely cleaner in every way. – DYS Jul 02 '12 at 23:57
1

I think I have solved this, because of the fact that you lose focus when you click on a menu item, we simply have to wait for focus to return to the component before we check who has focus, I have done this using a swing timer that waits 100ms and then executes its method to check which component has focus:

import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.TimerTask;
import javax.swing.*;

public class JavaApplication180 extends JFrame {

    private JTextField[] JTextFields;
    private JMenuBar menuBar;
    private JMenu menu;
    private JMenuItem item;

    public JavaApplication180() {
        initComponents();

        createAndShowUI();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new JavaApplication180();
            }
        });
    }

    private void createAndShowUI() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().setLayout(new GridLayout(2, 2, 10, 10));
        setJMenuBar(menuBar);
        addComponentsToPane();

        pack();
        setVisible(true);
    }

    private void initComponents() {
        JTextFields = new JTextField[4];

        menuBar = new JMenuBar();
        item = new JMenuItem("Who has focus?");
        item.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent ae) {
                TimerTask tt = new TimerTask() {

                    @Override
                    public void run() {
                        JOptionPane.showMessageDialog(null, getMostRecentFocusOwner().getName());
                    }
                };
                new java.util.Timer().schedule(tt, 100);
            }
        });

        menu = new JMenu("File");
        menu.add(item);
        menuBar.add(menu);
    }

    private void addComponentsToPane() {
        for (int i = 0; i < JTextFields.length; i++) {
            JTextFields[i] = new JTextField();
            JTextFields[i].setText(String.valueOf(i));
            JTextFields[i].setName(String.valueOf(i));
            getContentPane().add(JTextFields[i]);
        }
    }
}
David Kroukamp
  • 36,155
  • 13
  • 81
  • 138
1

I'm probably too late, but I just did the following in my JFrame constructor.

this.rootPane.setFocusable(false);

Now

getFocusOwner()

will return the current JTextComponent instead of the rootPane. I then used this code in an ActionListener attached to my JMenuItem to select the text within it.

if (getFocusOwner() instanceof JTextField)
{
    ((JTextField) getMostRecentFocusOwner()).selectAll();
}

It should be noted that If you have a JScrollPane etc, you may have to use setFocusable(false) on all the components between the rootPane and the textfields.

Hope this helps anyone else with the same issue!

Source: Personal Experience

rohan
  • 31
  • 3
0

When I've needed this, I wrote a focus listener. I had a JPanel with two columns of JTextFields and the focus listener kept track of which column was last used by the user. I enablesd the user to enter some text into that last focused column with a button click. You would use just one instance of your FocusListener for all text fields and have a field referencing the most recently focused component. Your menu action can then query that field to determine which text field to use.

See http://docs.oracle.com/javase/tutorial/uiswing/events/focuslistener.html

Thorn
  • 4,015
  • 4
  • 23
  • 42