问题描述
我正在编写一个应用程序,用于将纸张上的数据手动复制到数据库中。该应用程序有很多小部件,用户可以在其中输入数据。为了使界面看起来整洁一些,我决定使用选项卡窗格,将输入字段分成逻辑单元。
该应用程序最重要的特性是它应该可以仅通过键盘使用。因此,您应该能够使用 CTRL+PgUp/PgDown
快捷键切换选项卡。但是,为了额外的方便,当用户将焦点从当前选项卡的最后一个小部件转移出去时,我希望立即激活下一个选项卡。
因此,如果用户将焦点放在最后一个文本字段上,并按下 Tab 键,我希望激活下一个选项卡,并将焦点放在其中的第一个小部件上。
为了解决这个问题,我将 jTabbedPane 标记为 focusCycleRootProvider
并添加了自定义的 FocusTraversalPolicy
。我的当前问题是:当我以编程方式激活下一个选项卡(使用 setSelectedIndex
)时,在 getComponentAfter
方法中会执行第二次 getComponentAfter
方法。这破坏了我的当前逻辑。我似乎找不到防止这种情况发生的方法。有什么想法吗?
在下面的示例中,您将看到一个 ArrayIndexOutOfBoundsException
。这是因为 getComponentAfter
在第一个选项卡和第二个选项卡上分别调用了两次。但是两次使用相同的小部件作为参数。这意味着第二次时,for 循环将无法找到匹配的组件,因此计数器 i
将与第二个选项卡中的组件数量 +1 一样大。这会导致异常。
测试可执行文件
/*
* TestFrame.java
*
* Created on Apr 18, 2011, 4:37:52 PM
*/
package testrun;
import java.awt.Component;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
/**
*
* @author malbert
*/
public class TestFrame extends javax.swing.JFrame {
/** Creates new form TestFrame */
public TestFrame() {
initComponents();
jTabbedPane1.setFocusTraversalPolicyProvider(true);
jTabbedPane1.setFocusTraversalPolicy(new EasyTabberFocusTraversalPolicy(jTabbedPane1));
jTabbedPane1.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
super.focusGained(e);
jTabbedPane1.setSelectedIndex(0);
Component ca = jTabbedPane1.getFocusTraversalPolicy().getFirstComponent(jTabbedPane1);
if (ca != null) {
ca.requestFocusInWindow();
}
}
});
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jTabbedPane1 = new javax.swing.JTabbedPane();
jPanel1 = new javax.swing.JPanel();
jTextField2 = new javax.swing.JTextField();
jTextField3 = new javax.swing.JTextField();
jPanel2 = new javax.swing.JPanel();
jButton1 = new javax.swing.JButton();
jButton2 = new javax.swing.JButton();
jTextField4 = new javax.swing.JTextField();
jTextField1 = new javax.swing.JTextField();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jTextField2.setText("jTextField2");
jTextField3.setText("jTextField3");
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addContainerGap()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jTextField3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap(289, Short.MAX_VALUE))
);
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addContainerGap()
.addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jTextField3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(168, Short.MAX_VALUE))
);
jTabbedPane1.addTab("tab1", jPanel1);
jButton1.setText("jButton1");
jButton2.setText("jButton2");
jTextField4.setText("jTextField4");
javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
jPanel2.setLayout(jPanel2Layout);
jPanel2Layout.setHorizontalGroup(
jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel2Layout.createSequentialGroup()
.addContainerGap()
.addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel2Layout.createSequentialGroup()
.addComponent(jButton1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jButton2))
.addComponent(jTextField4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap(165, Short.MAX_VALUE))
);
jPanel2Layout.setVerticalGroup(
jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel2Layout.createSequentialGroup()
.addContainerGap()
.addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jButton1)
.addComponent(jButton2))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jTextField4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(162, Short.MAX_VALUE))
);
jTabbedPane1.addTab("tab2", jPanel2);
jTextField1.setText("jTextField1");
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jTabbedPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 376, Short.MAX_VALUE)
.addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jTabbedPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 251, Short.MAX_VALUE)
.addContainerGap())
);
pack();
}// </editor-fold>//GEN-END:initComponents
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new TestFrame().setVisible(true);
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton jButton1;
private javax.swing.JButton jButton2;
private javax.swing.JPanel jPanel1;
private javax.swing.JPanel jPanel2;
private javax.swing.JTabbedPane jTabbedPane1;
private javax.swing.JTextField jTextField1;
private javax.swing.JTextField jTextField2;
private javax.swing.JTextField jTextField3;
private javax.swing.JTextField jTextField4;
// End of variables declaration//GEN-END:variables
}
Traversal Policy
package testrun;
import java.awt.Component;
import java.awt.Container;
import javax.swing.JTabbedPane;
import javax.swing.LayoutFocusTraversalPolicy;
/**
*
* @author malbert
*/
public class EasyTabberFocusTraversalPolicy extends LayoutFocusTraversalPolicy {
private final JTabbedPane container;
private int currentTab = 0;
public EasyTabberFocusTraversalPolicy(JTabbedPane container) {
this.container = container;
}
@Override
public Component getComponentAfter(Container aContainer, Component aComponent) {
System.out.println("after " + aComponent);
Component comp = container.getComponentAt(currentTab);
if (Container.class.isInstance(comp)) {
Component[] components = ((Container) comp).getComponents();
int i = 0;
for (i = 0; i < components.length; i++) {
if (!components[i].isEnabled() || !components[i].isFocusable()) {
continue;
}
if (components[i].equals(aComponent)) {
break;
}
}
if (i == components.length - 1) {
// we reached the end. Go to the next tab!
currentTab = currentTab + 1;
Component fc = firstComponentInCurrentTab();
activateTab(currentTab);
return fc;
} else {
return components[i + 1];
}
} else {
return comp;
}
}
@Override
public Component getComponentBefore(Container aContainer, Component aComponent) {
System.out.println("before");
return super.getComponentBefore(aContainer, aComponent);
}
@Override
public Component getFirstComponent(Container aContainer) {
System.out.println("first");
return firstComponentInCurrentTab();
}
@Override
public Component getLastComponent(Container aContainer) {
System.out.println("last");
return lastComponentInCurrentTab();
}
private Component firstComponentInCurrentTab() {
Component comp = container.getComponentAt(currentTab);
if (comp instanceof Container) {
Component[] components = ((Container) comp).getComponents();
if (components.length == 0) {
return null;
}
return components[0];
} else {
return comp;
}
}
private Component lastComponentInCurrentTab() {
Component comp = container.getComponentAt(currentTab);
if (comp instanceof Container) {
Component[] components = ((Container) comp).getComponents();
if (components.length == 0) {
return null;
}
return components[components.length - 1];
} else {
return comp;
}
}
private void activateTab(int index) {
// wrap around
if (index < 0) {
index = container.getTabCount() - 1;
} else if (index > container.getTabCount() - 1) {
index = 0;
}
currentTab = index;
container.setSelectedIndex(index);
}
}
EasyTabberFocusTraversalPolicy
降级为默认访问权限,并将其放在TestFrame
源代码的末尾,以使 SSCCE 更容易编译和运行(这是我的建议)。 - Andrew Thompson