Reputation: 857
I want to create an expandable panel (to accomodate several UI elements for specific tasks). The panel can be expanded by user action, and should stay expanded as long as any subcomponent in the panel has focus. When the user puts focus somewhere outside the panel, then it should close.
Is there an easy way
Or would I need to register/check for focus(-events) on every subcomponent? (Since this should be a generically usable panel, I need a generic approach which is independent from the specific hierarchy of subcomponents.)
Upvotes: 1
Views: 124
Reputation: 3321
A recommended way of grouping focus listeners on multiple Component
s, according to the official tutorials, is to install a single PropertyChangeListener
on the current KeyboardFocusManager
. For example something like:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class Main {
private static void createAndShowGUI() {
final int rows = 5, cols = 5;
final JPanel focusables = new JPanel(new GridLayout(0, cols, 10, 10));
for (int row = 0; row < rows; ++row)
for (int col = 0; col < cols; ++col)
focusables.add(new JTextField("Focusable", 10));
final JPanel nonFocusable = new JPanel(); //FlowLayout.
nonFocusable.add(new JTextField("Click here to change focus"));
final JPanel contents = new JPanel(new BorderLayout());
contents.add(focusables, BorderLayout.CENTER);
contents.add(nonFocusable, BorderLayout.LINE_END);
KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", new PropertyChangeListener() {
@Override
public void propertyChange(final PropertyChangeEvent evt) {
final Object newValue = evt.getNewValue();
if (newValue instanceof Component
&& SwingUtilities.isDescendingFrom((Component) newValue, focusables)) //If 'focusables' is a parent of newValue...
focusables.setBackground(Color.GREEN); //Then focus is back on our components...
else
focusables.setBackground(Color.RED); //Else the focus is not on our components (it may even be another application).
}
});
final JFrame frame = new JFrame("Multi focus lost");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(contents);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(Main::createAndShowGUI);
}
}
According to the tutorial:
The property change listener is notified of every change involving the focus...
How to use the above example:
JTextField
to give it focus.JTextField
has for its parent a Component
that we specify (which is the parent JPanel
of the desired JTextField
s in this case, named focusables
) then the background color of that parent changes to green, otherwise red. But you can obviously implement your logic instead of changing the background color.How the above example works:
PropertyChangeListener
on the current KeyboardFocusManager
for the property focusOwner
, which will let us get notified only when any Component
in the application changes its focus-owner property (ie changing the focused Component
triggers such an event). We can install a PropertyChangeListener
such that we get notified for all sorts of events, but the focusOwner
is what we are only intersted in, so we let the system know. For a list of all properties you can track by focus events, scroll to the bottom of the tutorial.PropertyChangeListener
implementation we only need to check that the new focus owner has the focusables
panel for its parent. We can check this by traversing the Component
hierarchy upwards from the focus owner, or just call SwingUtilities#isDescendingFrom
which is a convenience method for this purpose. If we were listening to all the focus events, then we would also have to filter manually the property focusOwner
(by checking that PropertyChangeEvent#getPropertyName
returns it).Note that the focus from our desired Component
s is lost when we are leaving the application (but gets back when we return).
Also note that the hierarchy of Component
s in the above example is only one level deep for simplicity (ie the desired focusable Component
s are direct children of the focusables
panel), but we can change this to any level deep we want, as long as the desired Component
s all have the same parent along their path (which has to be different from the parent of the non-desired ones).
The only exception that I can think of is if you generally have mixed parent Container
s, ie JPanel
s for example that have as direct children both a desired Component
and a non-desired one. In this case, you will have to check for each Component
individually in the PorpertyChangeListener
or implement a FocusListener
for each desired Component
. The official tutorial on how to implement a FocusListener
can be found here.
Upvotes: 1