使用 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]));