我目前正在编写一个用于手动将数据从一张纸复制到数据库的应用程序。此应用程序具有大量小部件,用户可以在其中输入数据。为了保持UI有点整洁,我决定使用选项卡式窗格,将输入字段分成逻辑单元。
该应用程序最重要的特性是单独使用键盘应该可以使用。所以你应该可以通过击键来切换标签。默认情况下,可以使用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
}
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);
}
}
答案 0 :(得分:2)
FTP是......真正痛苦的痛苦; - )
不完全确定究竟是什么导致了NPE,只是简化了你的代码(如果你有一个方法可以访问最后一个,更好地使用它)有点玩:
@Override
public Component getComponentAfter(Container aContainer, Component aComponent) {
System.out.println("after " + aComponent);
Component last = lastComponentInCurrentTab();
if (aComponent == last) {
// we reached the end. Go to the next tab!
currentTab = currentTab + 1;
Component fc = firstComponentInCurrentTab();
activateTab(currentTab);
return fc;
}
return super.getComponentAfter(aContainer, aComponent);
}
AIOOB消失了,第二个标签显示了......但没有集中注意力。发生这种情况的原因是(只猜测,因为我在其他设置中看到了类似的东西,而没有记住所有脏的细节;)在返回第二个选项卡的第一个补偿时,还不能接收焦点,所以它进一步转移到选项卡外的文本。
编辑:必须修改我的猜测 - 经过一些挖掘后,问题似乎是对getFirst / LastComponent的错误覆盖。他们必须返回所有选项卡的第一个/最后一个,即第一个始终是第一个选项卡的第一个,最后一个总是最后一个选项卡的最后一个选项卡。这里是第一个片段:
@Override
public Component getFirstComponent(Container aContainer) {
System.out.println("first");
return firstComponentInTab(0) ;//firstComponentInCurrentTab();
}
private Component firstComponentInCurrentTab() {
int tabIndex = currentTab;
return firstComponentInTab(tabIndex);
}
private Component firstComponentInTab(int tabIndex) {
Component comp = container.getComponentAt(tabIndex);
LOG.info("comp: " + comp.getName());
if (comp instanceof Container) {
Component[] components = ((Container) comp).getComponents();
if (components.length == 0) {
return null;
}
return components[0];
} else {
return comp;
}
}
然后前向标签看起来很好。类似的需要最后,清理留给OP :-)
顺便说一句:好主意!
Edit2:注意 - 清理不会是微不足道的。 FTP必须处理其子级的完整层次结构,因此仅检查直接子级将很快中断(对于像JComboBox这样的复合组件,f.i。)