如何在TreeCellEditor周围调整JTree行的大小

时间:2015-03-18 16:42:26

标签: java swing jtextarea jtree

我使用多行JTextArea来编辑我的JTree中的值。

通过一些哄骗,我可以让JTextArea调整大小以容纳其中的文本,但编辑器周围的JTree节点/行不会移开。 (SCCEE w /截图如下)

如何让JTree“重排”编辑器组件周围的所有节点?

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;

public final class TextAreaEditorForJTree {

    public static final String INITIAL_TEXT = "Line 1\nLine 2\nLine 3";

    public static void main(String args[]) {

        JTree tree = createSimpleTree();

        addTextAreaEditor(tree);

        JScrollPane scrollPane = new JScrollPane(tree);

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(scrollPane, BorderLayout.CENTER);
        frame.setSize(300, 300);
        frame.setVisible(true);
    }

    private static JTree createSimpleTree() {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode(INITIAL_TEXT);

        for (int i = 0; i < 10; i++) {
            MutableTreeNode child = new DefaultMutableTreeNode(INITIAL_TEXT);
            root.add(child);
        }

        JTree tree = new JTree(root);
        tree.setEditable(true);
        tree.setShowsRootHandles(true);

        return tree;
    }

    private static void addTextAreaEditor(JTree tree) {
        TreeCellEditor editor = new TextAreaTableCellEditor();
        tree.setCellEditor(editor);
    }

    private static final class TextAreaTableCellEditor extends AbstractCellEditor implements TreeCellEditor {

        private final JPanel panel;
        private final JLabel label;
        private final JTextArea textArea;
        private DefaultMutableTreeNode currentNode;

        public TextAreaTableCellEditor() {

            label = new JLabel("Editor:");

            textArea = new JTextArea();
            textArea.setColumns(10);

            panel = new JPanel();
            BoxLayout boxLayout = new BoxLayout(panel, BoxLayout.X_AXIS);
            panel.setLayout(boxLayout);
            panel.add(label);
            panel.add(textArea);

            textArea.addComponentListener(new ComponentListener() {

                @Override
                public void componentResized(ComponentEvent e) {
                    setSizeToPreferredSizeLater();
                }

                @Override
                public void componentShown(ComponentEvent e) {
                    setSizeToPreferredSizeLater();
                }

                @Override
                public void componentMoved(ComponentEvent e) {
                }

                @Override
                public void componentHidden(ComponentEvent e) {
                }
            });

            textArea.getDocument().addDocumentListener(new DocumentListener() {
                public void insertUpdate(DocumentEvent e) {
                    setSizeToPreferredSizeLater();
                }

                public void removeUpdate(DocumentEvent e) {
                    setSizeToPreferredSizeLater();
                }

                public void changedUpdate(DocumentEvent e) {
                    setSizeToPreferredSizeLater();
                }
            });
        }

        private void setSizeToPreferredSizeLater() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    panel.setSize(panel.getPreferredSize());
                }
            });

        }

        public Object getCellEditorValue() {
            return textArea.getText();
        }

        public boolean isCellEditable(EventObject anEvent) {
            return true;
        }

        public boolean shouldSelectCell(EventObject anEvent) {
            return true;
        }

        public boolean stopCellEditing() {
            currentNode.setUserObject(textArea.getText());
            return true;
        }

        public void cancelCellEditing() {
            currentNode.setUserObject(textArea.getText());
        }

        public Component getTreeCellEditorComponent(final JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {

            this.currentNode = ((DefaultMutableTreeNode) value);

            textArea.setText((String) currentNode.getUserObject());

            return panel;
        }
    }
}

SCCEE的屏幕截图 - 编辑器(灰色框和右边的文本)显示在所有其他树节点的顶部。

TreeCellEditor appears over neighboring tree nodes

1 个答案:

答案 0 :(得分:0)

我找到了解决问题的方法,它与the answer to a related question中的实现非常相似。如果您只是处理渲染器,那么通过查看other question and answer可能会更好,但在我的情况下,我正在处理一个在我输入时调整大小的编辑器。

两种情况下都可以调用AbstractLayoutCache.invalidateSizes()。这个缓存位于受BasicTreeUI的受保护成员中,您可以从JTree.getUI()获取(此假定您的树的L&amp; F扩展BasicTreeUI

您应该注意,虽然该方法被称为“invalidateSizes”,但它确实使所有节点 bounds 无效。节点 bounds 还包括组件的位置。我宁愿扩展UI对象或插入转发/代理对象,但我无法在分配的时间内找到解决方案。

在大小/边界无效后,对tree.repaint()的调用将更新UI以显示新编辑器的大小。

好的,所以我找到了两个叫这个方法的方法,我也不喜欢......但是他们确实有效:

private static class MyJTree extends JTree {
    ...
    public void invalidateNodeBoundsViaSideEffect() {
        if (ui instanceof BasicTreeUI) {
            BasicTreeUI basicTreeUI = (BasicTreeUI) ui;
            basicTreeUI.setLeftChildIndent(basicTreeUI.getLeftChildIndent());
        }}

    public void invalidateNodeBoundsViaRefection() {
        if (ui instanceof BasicTreeUI) {
            try {
                Field field = BasicTreeUI.class.getDeclaredField("treeState");
                field.setAccessible(true);

                AbstractLayoutCache treeState = (AbstractLayoutCache) field.get(ui);

                if (treeState != null) {
                    treeState.invalidateSizes();
                }
            } catch (Exception e) {
            }
        }}}

修订后的SCCEE包括此解决方案:

import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.Field;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.*;
import javax.swing.tree.*;

public final class TextAreaEditorForJTree2 {

    public static final String INITIAL_TEXT = "Line 1\nLine 2\nLine 3";

    public static void main(String args[]) {

        JTree tree = createSimpleTree();

        addTextAreaEditor(tree);

        JScrollPane scrollPane = new JScrollPane(tree);

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(scrollPane, BorderLayout.CENTER);
        frame.setSize(300, 300);
        frame.setVisible(true);
    }

    private static JTree createSimpleTree() {

        DefaultMutableTreeNode root = new DefaultMutableTreeNode(INITIAL_TEXT);

        for (int i = 0; i < 10; i++) {
            MutableTreeNode child = new DefaultMutableTreeNode(INITIAL_TEXT);
            root.add(child);
        }

        JTree tree = new MyJTree(root);
        tree.setRowHeight(0);  // CRITICAL - Setting to '0' means the row heights are variable and the renderer's **bounds** should be recomputed more often!
        tree.setEditable(true);
        tree.setShowsRootHandles(true);

        return tree;
    }

    private static void addTextAreaEditor(JTree tree) {
        TreeCellEditor editor = new TextAreaTableCellEditor(tree);
        tree.setCellEditor(editor);
    }

    private static final class TextAreaTableCellEditor extends AbstractCellEditor implements TreeCellEditor {

        private final JPanel editorPanel;
        private final JLabel editorLabel;
        private final JTextArea textArea;
        private DefaultMutableTreeNode currentNode;
        private final JTree tree;

        public TextAreaTableCellEditor(final JTree target) {
            this.tree = target;

            editorLabel = new JLabel("Editor:");

            textArea = new JTextArea();
            textArea.setColumns(10);
            textArea.setLineWrap(true);
            textArea.setWrapStyleWord(true);

            editorPanel = new JPanel();
            BoxLayout boxLayout = new BoxLayout(editorPanel, BoxLayout.X_AXIS);
            editorPanel.setLayout(boxLayout);
            editorPanel.add(editorLabel);
            editorPanel.add(textArea);

            editorPanel.setSize(editorPanel.getPreferredSize());

            textArea.addComponentListener(new ComponentListener() {
                public void componentResized(ComponentEvent e) {somethingChanged();}
                public void componentShown(ComponentEvent e) {somethingChanged();}
                public void componentMoved(ComponentEvent e) {}
                public void componentHidden(ComponentEvent e) {}
            });

            textArea.getDocument().addDocumentListener(new DocumentListener() {
                public void insertUpdate(DocumentEvent e) {somethingChanged();}
                public void removeUpdate(DocumentEvent e) {somethingChanged();}
                public void changedUpdate(DocumentEvent e) {somethingChanged();}
            });
        }

        private void somethingChanged() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    // TODO: skip if size is not changing
                    editorPanel.setSize(editorPanel.getPreferredSize());
                    ((MyJTree) tree).invalidateNodeBounds();
                    tree.repaint();
                }
            });
        }

        public Object getCellEditorValue() {
            return textArea.getText();
        }

        public boolean isCellEditable(EventObject anEvent) {
            return true;
        }

        public boolean shouldSelectCell(EventObject anEvent) {
            return true;
        }

        public boolean stopCellEditing() {
            currentNode.setUserObject(textArea.getText());
            return true;
        }

        public void cancelCellEditing() {
            currentNode.setUserObject(textArea.getText());
        }

        public Component getTreeCellEditorComponent(final JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
            this.currentNode = ((DefaultMutableTreeNode) value);
            textArea.setText((String) currentNode.getUserObject());
            return editorPanel;
        }
    }

    private static class MyJTree extends JTree {

        public MyJTree(TreeNode root) {
            super(root);
        }

        public void invalidateNodeBounds() {
            invalidateNodeBoundsViaSideEffect();
            //invalidateNodeBoundsViaRefection();
        }

        public void invalidateNodeBoundsViaSideEffect() {
            if (ui instanceof BasicTreeUI) {
                BasicTreeUI basicTreeUI = (BasicTreeUI) ui;
                basicTreeUI.setLeftChildIndent(basicTreeUI.getLeftChildIndent());
            }
        }

        public void invalidateNodeBoundsViaRefection() {

            if (ui instanceof BasicTreeUI) {

                try {
                    Field field = BasicTreeUI.class.getDeclaredField("treeState");
                    field.setAccessible(true);

                    AbstractLayoutCache treeState = (AbstractLayoutCache) field.get(ui);

                    if (treeState != null) {
                        treeState.invalidateSizes();
                    }
                } catch (Exception e) {
                }
            }
        }
    }
}