使用 FocusTraversalPolicy 时 JSpinner AutoSelect 不起作用
JSpinner AutoSelect not working when using FocusTraversalPolicy
在重构我的应用程序时,我开始将 FocusTraversalPolicies 添加到我的每个应用程序 JPanel。在那期间,我注意到了一些奇怪的事情。使用 @MadProgrammers 为 JSpinners 实现 FocusListener 来自动选择它的文本 (can be found here),我希望能够通过一系列 JSpinners 进行切换。
作为 MCVE(必须有那么长的时间才能显示问题并包含所需的一切)我编写了一个较小的程序来显示我面临的问题:
import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.FocusTraversalPolicy;
import java.awt.KeyboardFocusManager;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.*;
import javax.swing.text.JTextComponent;
public class testsforSO extends JFrame {
private static final long serialVersionUID = 1977580061768232581L;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
testsforSO frame = new testsforSO();
frame.pack();
frame.setSize(800, 300);
frame.setVisible(true);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public testsforSO(){
setContentPane(new JPanel());
MyJPanel myPanel = new MyJPanel();
getContentPane().add(myPanel);
}
}
class MyJPanel extends JPanel{
private static final SelectOnFocusgainedHandler FOCUSHANDLERINSTANCE = new SelectOnFocusgainedHandler();
ArrayList<JSpinner> spinners = new ArrayList<>();
JButton addTF = new JButton("Add TextField");
public MyJPanel(){
addTF.addActionListener((list) -> {
for (JSpinner jsp : spinners) remove(jsp);
FocusTraversalPolicy ftp = new FocusTraversalOnArray(spinners.toArray(new JSpinner[0]));
spinners.add(new JSpinner());
for (JSpinner jsp : spinners) {
add(jsp);
installFocusListener(jsp);
}
setFocusTraversalPolicy(ftp);
setFocusCycleRoot(true);
setFocusTraversalPolicyProvider(true);
revalidate();
repaint();
});
add(addTF);
}
private void installFocusListener(JSpinner spinner) {
List<JTextComponent> lstChildren = findAllChildren(spinner, JTextComponent.class);
if (lstChildren != null && lstChildren.size() > 0) {
JTextComponent editor = lstChildren.get(0);
editor.addFocusListener(FOCUSHANDLERINSTANCE);
}
}
private <T extends Component> List<T> findAllChildren(JComponent component, Class<T> clazz) {
List<T> lstChildren = new ArrayList<>(5);
for (Component comp : component.getComponents()) {
if (clazz.isInstance(comp)) {
lstChildren.add((T) comp);
} else if (comp instanceof JComponent) {
lstChildren.addAll(findAllChildren((JComponent) comp, clazz));
}
}
return Collections.unmodifiableList(lstChildren);
}
}
class SelectOnFocusgainedHandler extends FocusAdapter {
@Override
public void focusGained(FocusEvent e){
System.out.println("FocusGained");
Component comp = e.getComponent();
if (comp instanceof JTextComponent){
final JTextComponent textComponent = (JTextComponent) comp;
new Thread (new Runnable() {
@Override
public void run() {
try {
Thread.sleep(25);
} catch(InterruptedException e){
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
System.out.println((KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner().equals(textComponent)));
textComponent.selectAll();
}
});
}
}).start();;
}
}
}
class FocusTraversalOnArray extends FocusTraversalPolicy {
private final Component m_Components[];
public FocusTraversalOnArray(Component components[]) {
m_Components = components;
}
private int indexCycle(int index, int delta) {
int size = m_Components.length;
int next = (index + delta + size) % size;
return next;
}
private Component cycle(Component currentComponent, int delta) {
int index = -1;
loop : for (int i = 0; i < m_Components.length; i++) {
Component component = m_Components[i];
for (Component c = currentComponent; c != null; c = c.getParent()) {
if (component == c) {
index = i;
break loop;
}
}
}
int initialIndex = index;
while (true) {
int newIndex = indexCycle(index, delta);
if (newIndex == initialIndex) {
break;
}
index = newIndex;
//
Component component = m_Components[newIndex];
if (component.isEnabled() && component.isVisible() && component.isFocusable()) {
return component;
}
}
return currentComponent;
}
public Component getComponentAfter(Container container, Component component) {
return cycle(component, 1);
}
public Component getComponentBefore(Container container, Component component) {
return cycle(component, -1);
}
public Component getFirstComponent(Container container) {
return m_Components[0];
}
public Component getLastComponent(Container container) {
return m_Components[m_Components.length - 1];
}
public Component getDefaultComponent(Container container) {
return getFirstComponent(container);
}
}
事实是以下两行导致侦听器不再正常工作,这意味着,当至少存在其中一行时,根本不执行 focusGained 方法(通过简单的命令行输出测试) :
setFocusCycleRoot(true);
setFocusTraversalPolicyProvider(true);
但是根据Container的实现,只有当两个属性都设置为true时,我才能使用FocusTraversalPolicy:
Container 来源的片段:
public FocusTraversalPolicy getFocusTraversalPolicy() {
if (!isFocusTraversalPolicyProvider() && !isFocusCycleRoot()) {
return null;
}
//....
}
有没有办法同时使用它们?或者两者的任何替代方案都可以通过 Spinners 切换并同时自动选择它们?
注意:我知道上面的程序根本不需要 FocusTraversalPolicy,但如前所述,它只是为了演示目的!在我的应用程序中,给出的默认策略根本不是所需要的。
哦好吧....
这里的问题与我之前预期的完全不同...MadProgrammer 的 focusListener 工作得很好,请在 TraversalPolicy 中设置使用正确组件的情况。
显然(现在我知道了)JSpinner 的 TextField 是重点,而不是整个微调器。因此,在 FocusTraversalOnArray 中使用相应的 TextFields 而不是 JSpinners 会导致正确和所需的行为。
意味着我使用了一个额外的 ArrayList<JFormattedTextField>
而不是 ArrayList<JSpinner>
来创建 FocusTraversalPolicy,我在 foreach 循环中填充了该循环,该循环迭代 JSpinner
具有以下内容:
spinnersTextFields.add(((JSpinner.DefaultEditor) jsp.getEditor()).getTextField());
在那个循环之后:
FocusTraversalPolicy ftp = new FocusTraversalOnArray(spinnersTextFields.toArray(new Component[0]));
在重构我的应用程序时,我开始将 FocusTraversalPolicies 添加到我的每个应用程序 JPanel。在那期间,我注意到了一些奇怪的事情。使用 @MadProgrammers 为 JSpinners 实现 FocusListener 来自动选择它的文本 (can be found here),我希望能够通过一系列 JSpinners 进行切换。
作为 MCVE(必须有那么长的时间才能显示问题并包含所需的一切)我编写了一个较小的程序来显示我面临的问题:
import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.FocusTraversalPolicy;
import java.awt.KeyboardFocusManager;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.*;
import javax.swing.text.JTextComponent;
public class testsforSO extends JFrame {
private static final long serialVersionUID = 1977580061768232581L;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
testsforSO frame = new testsforSO();
frame.pack();
frame.setSize(800, 300);
frame.setVisible(true);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public testsforSO(){
setContentPane(new JPanel());
MyJPanel myPanel = new MyJPanel();
getContentPane().add(myPanel);
}
}
class MyJPanel extends JPanel{
private static final SelectOnFocusgainedHandler FOCUSHANDLERINSTANCE = new SelectOnFocusgainedHandler();
ArrayList<JSpinner> spinners = new ArrayList<>();
JButton addTF = new JButton("Add TextField");
public MyJPanel(){
addTF.addActionListener((list) -> {
for (JSpinner jsp : spinners) remove(jsp);
FocusTraversalPolicy ftp = new FocusTraversalOnArray(spinners.toArray(new JSpinner[0]));
spinners.add(new JSpinner());
for (JSpinner jsp : spinners) {
add(jsp);
installFocusListener(jsp);
}
setFocusTraversalPolicy(ftp);
setFocusCycleRoot(true);
setFocusTraversalPolicyProvider(true);
revalidate();
repaint();
});
add(addTF);
}
private void installFocusListener(JSpinner spinner) {
List<JTextComponent> lstChildren = findAllChildren(spinner, JTextComponent.class);
if (lstChildren != null && lstChildren.size() > 0) {
JTextComponent editor = lstChildren.get(0);
editor.addFocusListener(FOCUSHANDLERINSTANCE);
}
}
private <T extends Component> List<T> findAllChildren(JComponent component, Class<T> clazz) {
List<T> lstChildren = new ArrayList<>(5);
for (Component comp : component.getComponents()) {
if (clazz.isInstance(comp)) {
lstChildren.add((T) comp);
} else if (comp instanceof JComponent) {
lstChildren.addAll(findAllChildren((JComponent) comp, clazz));
}
}
return Collections.unmodifiableList(lstChildren);
}
}
class SelectOnFocusgainedHandler extends FocusAdapter {
@Override
public void focusGained(FocusEvent e){
System.out.println("FocusGained");
Component comp = e.getComponent();
if (comp instanceof JTextComponent){
final JTextComponent textComponent = (JTextComponent) comp;
new Thread (new Runnable() {
@Override
public void run() {
try {
Thread.sleep(25);
} catch(InterruptedException e){
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
System.out.println((KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner().equals(textComponent)));
textComponent.selectAll();
}
});
}
}).start();;
}
}
}
class FocusTraversalOnArray extends FocusTraversalPolicy {
private final Component m_Components[];
public FocusTraversalOnArray(Component components[]) {
m_Components = components;
}
private int indexCycle(int index, int delta) {
int size = m_Components.length;
int next = (index + delta + size) % size;
return next;
}
private Component cycle(Component currentComponent, int delta) {
int index = -1;
loop : for (int i = 0; i < m_Components.length; i++) {
Component component = m_Components[i];
for (Component c = currentComponent; c != null; c = c.getParent()) {
if (component == c) {
index = i;
break loop;
}
}
}
int initialIndex = index;
while (true) {
int newIndex = indexCycle(index, delta);
if (newIndex == initialIndex) {
break;
}
index = newIndex;
//
Component component = m_Components[newIndex];
if (component.isEnabled() && component.isVisible() && component.isFocusable()) {
return component;
}
}
return currentComponent;
}
public Component getComponentAfter(Container container, Component component) {
return cycle(component, 1);
}
public Component getComponentBefore(Container container, Component component) {
return cycle(component, -1);
}
public Component getFirstComponent(Container container) {
return m_Components[0];
}
public Component getLastComponent(Container container) {
return m_Components[m_Components.length - 1];
}
public Component getDefaultComponent(Container container) {
return getFirstComponent(container);
}
}
事实是以下两行导致侦听器不再正常工作,这意味着,当至少存在其中一行时,根本不执行 focusGained 方法(通过简单的命令行输出测试) :
setFocusCycleRoot(true);
setFocusTraversalPolicyProvider(true);
但是根据Container的实现,只有当两个属性都设置为true时,我才能使用FocusTraversalPolicy:
Container 来源的片段:
public FocusTraversalPolicy getFocusTraversalPolicy() {
if (!isFocusTraversalPolicyProvider() && !isFocusCycleRoot()) {
return null;
}
//....
}
有没有办法同时使用它们?或者两者的任何替代方案都可以通过 Spinners 切换并同时自动选择它们?
注意:我知道上面的程序根本不需要 FocusTraversalPolicy,但如前所述,它只是为了演示目的!在我的应用程序中,给出的默认策略根本不是所需要的。
哦好吧....
这里的问题与我之前预期的完全不同...MadProgrammer 的 focusListener 工作得很好,请在 TraversalPolicy 中设置使用正确组件的情况。
显然(现在我知道了)JSpinner 的 TextField 是重点,而不是整个微调器。因此,在 FocusTraversalOnArray 中使用相应的 TextFields 而不是 JSpinners 会导致正确和所需的行为。
意味着我使用了一个额外的 ArrayList<JFormattedTextField>
而不是 ArrayList<JSpinner>
来创建 FocusTraversalPolicy,我在 foreach 循环中填充了该循环,该循环迭代 JSpinner
具有以下内容:
spinnersTextFields.add(((JSpinner.DefaultEditor) jsp.getEditor()).getTextField());
在那个循环之后:
FocusTraversalPolicy ftp = new FocusTraversalOnArray(spinnersTextFields.toArray(new Component[0]));