Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
175 views
in Technique[技术] by (71.8m points)

java - JSpinner AutoSelect not working when using FocusTraversalPolicy

While restructuring my application, I began to add FocusTraversalPolicies to each of my Applications JPanels. During that I noticed something odd. Using @MadProgrammers implementation of a FocusListener for JSpinners to autoselect the text of it (can be found here), I wanted to be able to tab through a series of JSpinners.

As an MCVE (had to be that long to show the problem and include everything needed) I made up a smaller program showing the problem I am facing:

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);
    }
}

Thing is that the following two lines cause the Listener not to work properly anymore meaning, that the focusGained method is not executed at all (tested by simple command-line-output) when at least one of the lines is present:

setFocusCycleRoot(true);
setFocusTraversalPolicyProvider(true);

However according to the implementation of the Container, I can only use the FocusTraversalPolicy when both attributes are set to true:

Snippet from the source of Container:

 public FocusTraversalPolicy getFocusTraversalPolicy() {
       if (!isFocusTraversalPolicyProvider() && !isFocusCycleRoot()) {
           return null;
       }
    //....
 }

Is there a way to use both of them at the same time? Or any alternative to each of both to have the possibility to tab through the Spinners and get them autoselected at the same time?

Note: I am aware that above program does not need a FocusTraversalPolicy at all, but as said, it is for demonstrational purpose! In my application the default Policy given, is not at all, what is needed.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Oh well....

The problem here was a completely different one than previously expected by me... The focusListener by MadProgrammer is working perfectly fine, set the case that one uses the correct components in the TraversalPolicy.

Obviously (now that I know it) the TextField of the JSpinner is focused and not the spinner in total. So using the respective TextFields in the FocusTraversalOnArray instead of the JSpinners lead to the correct and desired behaviour.

Meaning that instead of the ArrayList<JSpinner> to create the FocusTraversalPolicy I used an additional ArrayList<JFormattedTextField> which I filled in the foreach loop that iterates over the JSpinner with the following:

spinnersTextFields.add(((JSpinner.DefaultEditor) jsp.getEditor()).getTextField());

and after that loop:

FocusTraversalPolicy ftp = new FocusTraversalOnArray(spinnersTextFields.toArray(new Component[0]));

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...