Dynamically resize JTextField in JTree Node
Asked Answered
M

2

6

I am using a JPanel with some JLabels and JTextFields as editor and renderer in a JTree.

The user can click on any JTextField and modify the text. All works fine.

All I am missing is how I can dynamically resize (grow) the JTextField as the user is typing.

Below a small sample program that illustrates the problem. Click on i.e. "colors" and enter some additional characters. The JTextField keeps its size, which is expected, but I would like it to grow and shrink to accommodate the text.

I tried different layout managers, calculate the preferredSize of the JTextField and the JPanel, call invalidate() and the JPanel and the Tree and so on, without success.

Any ideas?

import java.awt.Component;
import java.util.EventObject;

import javax.swing.AbstractCellEditor;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;

public class Tree {

    public static void main(final String[] args) {
        new Tree().grow();
    }

    private void grow() {
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        final JTree tree = new JTree();
        tree.setEditable(true);
        tree.setCellRenderer(new SomeTreeCellRenderer());
        tree.setCellEditor(new SomeTreeCellRenderer());

        frame.add(new JScrollPane(tree));

        frame.pack();
        frame.setSize(400, 300);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private class SomeTreeCellRenderer extends AbstractCellEditor implements 
            TreeCellRenderer, TreeCellEditor {

        @Override
        public Object getCellEditorValue() {
            return null;
        }

        @Override
        public boolean isCellEditable(final EventObject event) {
            return true;
        }

        @Override
        public Component getTreeCellRendererComponent(
                final JTree tree, final Object value, final boolean selected, final boolean expanded, 
                final boolean leaf, final int row, final boolean hasFocus) {

            final JPanel panel = new JPanel();
            final BoxLayout layout = new BoxLayout(panel, BoxLayout.X_AXIS);
            panel.setLayout(layout);

            panel.add(new JLabel("Some text: "));
            panel.add(new JTextArea(String.valueOf(value)));

            return panel;
        }

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

            return getTreeCellRendererComponent(tree, value, isSelected, expanded, leaf, row, false);
        }

    }

}
Mission answered 2/8, 2012 at 15:10 Comment(7)
unrelated to your problem: a) the implementation of celleditor is invalid (doesn't service its contract to notify listeners when edits are terminated b) don't create components in the getXX, instead create them once and showAnkledeep
related to your problem: adjusting a textField's size to always fit its content is unexpectedly non-straigthforward even in normal environments b) living in a tree certainly doesn't soften that :-) See #11717285Ankledeep
a) The implementation is incomplete because it is supposed to be a minimal sample app b) In the actual application a varying amount of components is added to different nodes a2) The JTextFields are part of a "sentence" so they should just accommodate the "word" instead of having a fixed width.Tericaterina
a) fair enough b) even then, I would go for caching panels with different numbers of textfields (a renderer is called really often) a2) that's exactly what the answers in the linked thread are doingAnkledeep
b) Caching is a good idea, thanks!Tericaterina
I shall note that I put a JTextArea instead of a JTextField in my sample code. To get the accepted answer to work with a JTextField, the findings findings by kleopatra need to be applied as well.Tericaterina
Another note: Caching the panels by nr. of fields returned by the renderer/editor yielded a very bad performance, the application becomes nearly unusable. Without any caching, it actually renders and scrolls very smooth.Tericaterina
S
3

What's happening is that the container owning your panel doesn't have a layout manager assigned to it - so the panel is not resizing when the text area is resizing. A simple fix to this would be to set the size of the panel whenever your text area resizes. So instead of directly doing this:

        panel.add(new JTextArea(String.valueOf(value)));

You can do this:

        JTextArea ta = new JTextArea(String.valueOf(value));
        ta.addComponentListener(new ComponentListener() {

            @Override
            public void componentResized(ComponentEvent e) {
                panel.setSize(panel.getPreferredSize());
            }

            @Override
            public void componentShown(ComponentEvent e) {}

            @Override
            public void componentMoved(ComponentEvent e) {}

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

        panel.add(ta);
Shaff answered 2/8, 2012 at 16:30 Comment(0)
M
3

Just for convenience, here a solution using a JTextField instead of a JTextArea similar to the accepted solution, using info from: change the size of JTextField on KeyPress

final JPanel panel = new JPanel();
final BoxLayout layout = new BoxLayout(panel, BoxLayout.X_AXIS);
panel.setLayout(layout);

panel.add(new JLabel("Some text: "));

final JTextField field = new JTextField(String.valueOf(value));
final DocumentListener documentListener = new DocumentListener() {

    private void updatePanel() {
        panel.revalidate();
        panel.setSize(panel.getPreferredSize());
    }

    @Override
    public void removeUpdate(DocumentEvent e) {
        updatePanel();
    }

    @Override
    public void insertUpdate(DocumentEvent e) {
        updatePanel();
    }

    @Override
    public void changedUpdate(DocumentEvent e) {
    }
};

field.getDocument().addDocumentListener(documentListener);

panel.add(field);
Mission answered 2/8, 2012 at 17:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.