当我在 JPanel 中有一个 JTextField 并且它具有焦点时,按“tab”不会执行任何操作...但按“shift-tab”会导致焦点丢失(FocusEvent.getOppositeComponent() 为 null)。
如果 JPanel 上(或者更确切地说,在“焦点循环根”下)有其他可聚焦组件,则不会发生这种情况:相反,它们将焦点放在 Shift-Tab 上。
在下面的 SSCCE 中,我演示了这一点...每次在搜索框中按 Return 键时,都会向 JTable 添加一行,这会使其变得可聚焦。您还可以取消注释使 JRadioButtons 无法聚焦的行。
我还查看了InputMaps,看看shift-tab是否以某种方式参与其中......根本没有。
我还尝试用 FocusTraversalPolicy 进行实验,看看我是否能理解这个问题。没有喜悦。
我的目标:当焦点循环根的范围内有单个可聚焦组件时,阻止“shift-tab”导致焦点丢失(焦点消失)。
later
解决方法是添加行
if( oppComp == null ){ impl.searchBox.requestFocus(); }
在搜索框的 FocusListener 的 focusLost 方法的末尾...但对我来说这只是一种解决方法... 1)它并没有通过理解焦点遍历机制来解决问题; 2)当你需要从搜索框中失去焦点时,可能会出现这种情况......
import java.awt.*;
import java.awt.event.*;
import java.lang.*;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
class BackTabProb {
JFrame mainFrame;
JTextField searchBox;
JTable resultsTable;
public static void print(String msg) {
System.out.println(msg);
}
public static void main(String[] a_args) throws InvocationTargetException, InterruptedException {
final BackTabProb impl = new BackTabProb();
class Show implements Runnable {
public void run() {
impl.mainFrame = new JFrame("Back Tab problem");
impl.mainFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent windowEvent) {
impl.mainFrame.dispose();
}
});
impl.resultsTable = new JTable();
impl.resultsTable.setFocusable(false);
Vector<Object> dataVector = new Vector<Object>();
Vector<Object> colIdentifiers = new Vector<Object>(Arrays.asList(new String[] { "ONE", "TWO" }));
((DefaultTableModel) impl.resultsTable.getModel()).setDataVector(dataVector, colIdentifiers);
JScrollPane jsp = new JScrollPane(impl.resultsTable);
JPanel northPanel = new JPanel();
northPanel.setLayout(new BoxLayout(northPanel, BoxLayout.X_AXIS));
impl.searchBox = new JTextField("Enter search text", 10);
JLabel label = new JLabel("Search:");
label.setLabelFor(impl.searchBox);
northPanel.add(label);
northPanel.add(impl.searchBox);
ButtonGroup buttonGroup = new ButtonGroup();
ArrayList<JRadioButton> indexButtons = new ArrayList<JRadioButton>();
for (int i = 0; i < 2; i++) {
JRadioButton indexButton = new JRadioButton(i == 0 ? "Stemmer" : "Simple");
// commenting this out means back-tabbing from search box does not result
// in focus going "nowhere"
indexButton.setFocusable(false);
buttonGroup.add(indexButton);
northPanel.add(indexButton);
}
impl.mainFrame.getContentPane().setLayout(new BorderLayout());
impl.mainFrame.getContentPane().add(northPanel, BorderLayout.NORTH);
impl.mainFrame.getContentPane().add(jsp, BorderLayout.CENTER);
impl.mainFrame.pack();
impl.mainFrame.setVisible(true);
print("=== visible");
}
}
EventQueue.invokeAndWait(new Show());
class AddMore implements Runnable {
public void run() {
impl.mainFrame.setFocusTraversalPolicyProvider(true);
class SearchBoxFocusListener implements FocusListener {
public void focusGained(FocusEvent focusEvent) {
print("=== search box got focus");
impl.searchBox.selectAll();
}
public void focusLost(FocusEvent focusEvent) {
Component oppComp = focusEvent.getOppositeComponent();
print(String.format("=== search box lost focus to %s",
oppComp == null ? "nowhere" : oppComp.getClass()));
}
}
impl.searchBox.addFocusListener(new SearchBoxFocusListener());
class SearchBoxActionListener implements ActionListener {
public void actionPerformed( ActionEvent actionEvent ){
if( actionEvent.getSource() != null ){
((DefaultTableModel)impl.resultsTable.getModel()).insertRow( 0, new Object[]{ "blip", "blap" });
// as soon as the table has at least one row it is set to "focusable"
// commenting this out means back-tabbing from search box results
// in focus going "nowhere"
impl.resultsTable.setFocusable( true );
}
}
}
impl.searchBox.addActionListener( new SearchBoxActionListener() );
ActionMap am = impl.searchBox.getActionMap();
print("=== ActionMap");
for (Object key : am.allKeys()) {
print(String.format(" === action key %s", key));
}
for (int i = 0; i < 3; i++) {
print(String.format("=== InputMap type %d", i));
InputMap im = impl.searchBox.getInputMap(i);
KeyStroke[] allKeys = im.allKeys();
if (allKeys != null) {
for (KeyStroke ks : allKeys) {
print(String.format(" === keystroke %s object %s", ks, im.get(ks)));
}
}
}
// various experiments with FocusTraversalPolicy... NB LayoutTraversalPolicy
// is what the framework uses here by default
class MainFrameFocusTraversalPolicy extends LayoutTraversalPolicy {
public Component getComponentAfter(Container arg0, Component arg1) {
Component comp = super.getComponentAfter(arg0, arg1);
print(String.format("=== comp after %s", comp == null ? "Null" : comp.getClass()));
return comp;
}
public Component getComponentBefore(Container arg0, Component arg1) {
Component comp = super.getComponentBefore(arg0, arg1);
print(String.format("=== comp before %s", comp == null ? "Null" : comp.getClass()));
return comp;
}
public Component getDefaultComponent(Container arg0) {
Component comp = super.getDefaultComponent(arg0);
print(String.format("=== default comp %s", comp == null ? "Null" : comp.getClass()));
return comp;
}
public Component getFirstComponent(Container arg0) {
Component comp = super.getFirstComponent(arg0);
print(String.format("=== first comp %s", comp == null ? "Null" : comp.getClass()));
return comp;
// return impl.searchBox;
}
public Component getLastComponent(Container arg0) {
Component comp = super.getLastComponent(arg0);
print(String.format("=== last comp %s", comp == null ? "Null" : comp.getClass()));
return comp;
}
protected boolean accept(Component comp) {
boolean accept = super.accept(comp);
print(String.format("=== accept %s? %s", comp == null ? "Null" : comp.getClass(), accept));
return accept;
}
}
impl.mainFrame.setFocusTraversalPolicy(new MainFrameFocusTraversalPolicy());
}
}
EventQueue.invokeAndWait(new AddMore());
}
}