Search code examples
javaswingfocusjtextfieldjtree

JTree valueChanged event gets called before focusLost of another component on clicking a node


I have a JTree where each node of the tree represents some user data. The data is editable and is stored in a file/db. There are a bunch of JTextField that lets you edit/update the user data for the node. Saving of the data event is fired when JTextField loses the focus. This works fine except when you change the selection of the node in JTree. Because valueChanged of JTree event occurs before focusLost of JTextField.

Here is a SSCCE: http://pastebin.com/wH1Veqdc

public class JTreeFocusTest extends JFrame{
        private static final long serialVersionUID = 1L;
        public JTreeFocusTest() {
                super("JTree Focus Test");
                JPanel panel = new JPanel(true);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                MyTree tree = new MyTree();
                panel.add(tree);
                panel.add(Box.createVerticalStrut(20));
                panel.add(new MyTextField(tree));
                panel.add(Box.createVerticalStrut(20));
                panel.add(new JTextField(30));
                getContentPane().add(panel);
                setVisible(true);
                setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                pack();
        }

        public static void main(String[] args){
                new JTreeFocusTest();
        }
}

class MyTree extends JTree{
        private static final long serialVersionUID = 1L;
        public MyTree() {
                DefaultMutableTreeNode root = new DefaultMutableTreeNode(new MyNode("Root", "Root"));
                DefaultMutableTreeNode child1 = new DefaultMutableTreeNode(new MyNode("Child 1", "DOC122122"));
                root.add(child1);
                DefaultMutableTreeNode child2 = new DefaultMutableTreeNode(new MyNode("Child 2", "DOC134342"));
                root.add(child2);
                DefaultTreeModel model = new DefaultTreeModel(root, true);
                setModel(model);
                getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
                setSelectionPath(getPathForRow(0));
        }
}

class MyTextField extends JTextField implements FocusListener, TreeSelectionListener{
        private static final long serialVersionUID = 1L;
        private MyTree tree;
        public MyTextField(MyTree tree) {
                this.tree = tree;
                setText("Some");
                addFocusListener(this);
                this.tree.addTreeSelectionListener(this);
                Dimension dim = new Dimension(200, 20);
                setPreferredSize(dim);
                setMaximumSize(dim);
                setMinimumSize(dim);
        }
        @Override
        public void focusGained(FocusEvent e) {System.out.println("TextField gained focus");}
        @Override
        public void focusLost(FocusEvent e) {
                getSelectedNodeUserObj().setValue(getText());
        }
        @Override
        public void valueChanged(TreeSelectionEvent e) {
                setText(getSelectedNodeUserObj().getValue());
        }
        private MyNode getSelectedNodeUserObj(){
                return ((MyNode)((DefaultMutableTreeNode)this.tree.getSelectionPath().getLastPathComponent()).getUserObject());
        }
}

class MyNode{
        private String label = "";
        private String value = "";

        public MyNode(String label, String value) {
                this.label = label;
                this.value = value;
        }
        public void setValue(String value){
                this.value = value;
        }
        public String getValue(){
                return value;
        }
        public String toString(){
                return label;
        }      
}

In this example I just have one field but in the actual application I have several fields that represents user data. Also JTextField is just an example, the input field could be JComboBox or JSpinner.

How can I save this user data? I'd appreciate any help/pointer.

Thanks!


Solution

  • Because valueChanged of JTree event occurs before focusLost of JTextField.

    • this is logical ordering, don't to change that, one JComponents focusGained, then after focus owner focusLost in the Focus in Window

    • Focus is quite asyncronous, for JTextComponent can be delayed events firing from added Listeners/InputMask/Formatter, can be solved by delaying in invokeLater, forgot about that, not your issue

    • (I'm ...) remove TreeSelectionListener from JTextField, move to (logical) JTree,

    • add System.out.println(getText());, as 1st code line to public void valueChanged(TreeSelectionEvent e) {, see whats happend :-), there is place to store (use Runnable#Thread, SwingWorker to redirect whatever to Workers Thread)

    • then there stays question whats happend if value is/are changed in JTextField and user click to the same node, then value stays unchanged or JComponent are refreshed with original value, but this is about bussines logics, not about how to code, my speculation