Search code examples
javaswingjtextfield

Let JTextField resize automatically


I want to show an Two-Dimensional Array of JTextFields to be editet by the user. After initilisation(with text) they show up in good size. When Text is editet, they don't resize. I place them in a JPanel with GridbagLayout inside a JSrolePane. Tried to resize them "by hand", so calling .setPrefferedSize when reciving event, but its hard to get the TextLength accurate cause of different letterSize. And all JTextFields show up in one line, so I probably get something wrong about GridBagConstraints. (used JTable before but didn't liked it).

GridBagConstraints c = new GridBagConstraints();
    c.gridx = 0;
    c.gridy = 0;
    c.weighty = 0.1;
    c.weightx = 0.1;
    c.fill = GridBagConstraints.BOTH;
    for(int row=0; row<initText.length; row++) {
        for(int col=0; col<initText[row].length; col++) {
            jTextFields[row][col] = new JTextField(initText[row][col]);
            jTextFields[row][col].getDocument().addDocumentListener(new DocumentListener() {
                @Override
                public void removeUpdate(DocumentEvent arg0) {
                }
                @Override
                public void insertUpdate(DocumentEvent dE) {
                    int row = -1;
                    int col = -1;
                    for(int i=0; i<jTextFields.length; i++) {
                        for(int ii=0; ii<jTextFields[i].length; ii++) {
                            if(jTextFields[i][ii].getDocument()==dE.getDocument()) {
                                row=i;
                                col=ii;
                                break;
                            }
                        }
                        if(col!=-1) {
                            break;
                        }
                    }
                    //Tried it so to make resize "by Hand" but initalisation size is better
                    jTextFields[row][col].setPreferredSize(new Dimension(jTextFields[row][col].getText().length()*jTextFields[row][col].getPreferredSize().height, jTextFields[row][col].getPreferredSize().height));
                    System.out.println("insertUpdate in cell "+row+" "+col);
                    Main.gui.frame.repaint();
                }
                @Override
                public void changedUpdate(DocumentEvent arg0) {
                    // TODO Auto-generated method stub
                }
            });
            c.gridx = col;
            c.gridy = row;
            innerContentPane.add(jTextFields[row][col], c);
        }
    }
    contentPane = new JScrollPane(innerContentPane);
    frame.repaint();

Solution

  • You don't want to keep resetting the preferred size of the component. Let each component determine its own preferred size.

    Instead you want to invoke the layout manager so the size of each component can be recalculated and the layout of the panel can be adjusted. An easy way to do this is to revalidate() the panel containing the text fields.

    I'm not a big fan of multiple levels of looping code with multiple levels of looping code. You can simplify your code by creating a common DocumentListener to be shared by all the Documents.

    Also, your code only handles the case where text is added. What if text is removed, shouldn't the text field shrink in size?

    Applying all the above suggestions, the logic would be something like:

    //  Create shared DocumentListener
    
    DocumentListener dl = new DocumentListener()
    {
        @Override
        public void removeUpdate(DocumentEvent de)
        {
            resizeTextField(de);
        }
    
        @Override
        public void insertUpdate(DocumentEvent de)
        {
            resizeTextField(de);
        }
    
        @Override
        public void changedUpdate(DocumentEvent de) {}
    
        private void resizeTextField(DocumentEvent de)
        {
            innerContentPane.revalidate();
        }
    };
    
    GridBagConstraints c = new GridBagConstraints();
    c.gridx = 0;
    c.gridy = 0;
    //c.weighty = 0.1;
    //c.weightx = 0.1;
    //c.fill = GridBagConstraints.BOTH;
    
    //  Create text fields and add listener to the Document
    
    for(int row = 0; row < 3; row++)
    {
        for(int col = 0; col <3; col++)
        {
                JTextField textField = new JTextField("0");
                textFields[row][col] = textField;
                textField.getDocument().addDocumentListener(dl);
                c.gridx = col;
                c.gridy = row;
                innerContentPane.add(textField, c);
        }
    }
    

    Note the DocumentListener should also be created as an inner class to make the code even cleaner.

    And all JTextFields show up in one line, so I probably get something wrong about GridBagConstraints

    The default layout manager of a JPanel is the FlowLayout.You need to set the layout to GridBagLayout if that is what you want to use:

    innerContentPane.setLayout( new GridBagLayout() );
    GridBagConstraints c = new GridBagConstraints();
    

    otherwise the GridBagConstraints mean nothing to the FlowLayout.

    Read the section from the Swing tutorial on Layout Manager for more information.