0

I have recently started working with Java+Swing building an UI and I currently have an issue with JTextField placed on JPanel with FlowLayout.

In my example I have a window, containing panel with button. Clicking the button adds a component derived from JPanel and containing JTextField.

The problem is that when I type in JTextField it does not get updated (does not get resized). However when I resize the window or do anything else which forces window/panel redraw, the text field being resized (just what I expect to happen automatically).

When I change base class from JPanel to JTextField it works in the way I try to achieve, but I need to have JPanel as the base class so that I can take advantages of putting child components to it.

I have checked different questions here as well as I have Googled trying to find the solution, however it did not work for me. I have tried validate/invalidate/revalidate/repaint in different combinations and for different components, as well as trying to enforce revalidation for each typed character, which does not sound as the right way for me. So far I understoon that it is something to do with Layout Managers.

Could anyone please help me with understanding how that works and what should I read about how Swing UI, layout management and redrawing is working?

Also, I would be glad if someone could help me with my particular issue with my code.

Thanks in advance!

Here is my code below:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;


class TagVisual extends JPanel /*JTextField*/ {

    private JTextField editField;

    public TagVisual() {

        FlowLayout layout = new FlowLayout();
        layout.setHgap(0);
        layout.setVgap(0);
        setLayout(layout);

        editField = new JTextField();
        editField.setBackground(Color.RED);

        editField.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                editField.setSize(editField.getSize());
                editField.revalidate();
                remove(editField);
                add(editField);
                revalidate();
                repaint();
            }
        });

        add(editField, FlowLayout.LEFT);
    }

    public void place(JPanel panel) {
        panel.add(this);

        editField.grabFocus();
    }
}

public class MainWindow {

    private JPanel mainPanel;
    private JButton btnPlace;
    private JFrame frame;

    public MainWindow(JFrame frame) {

        mainPanel = new JPanel(new FlowLayout());
        btnPlace = new JButton();
        btnPlace.setText("Place");
        mainPanel.add(btnPlace);

        this.frame = frame;
        btnPlace.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                TagVisual v = new TagVisual();
                v.place(mainPanel);
                mainPanel.revalidate();
                mainPanel.repaint();
                mainPanel.updateUI();
                frame.revalidate();
                frame.repaint();
            }
        });
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("TextFieldUpdateIssue");

        frame.setContentPane(new MainWindow(frame).mainPanel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }
}
  • So you want your textfield to increase its size based on the typed text ? This is not the normal behaviour, as a user i would find it quite nasty ... – Ansharja Jul 29 '17 at 10:39
  • Yes, that is what I want. I am creating tag editor component which is expected to increase its size when typing. – Ramiz Zeynalov Jul 29 '17 at 10:56
  • Welcome to SO. Please post [mcve]. This code will not run (`btnPlace` and `mainPanel` are never initialized nor added to the frame) – c0der Jul 29 '17 at 11:17
  • Thanks for pointing that, updated the code. – Ramiz Zeynalov Jul 29 '17 at 11:23

2 Answers2

1

If i were you, i would not try to resize the textfields when the user enters some text.

I suggest you to give them a fixed size using JTextField (int columns) constructor, this will allow you to create some textfields which are "wide enough".

If you still want to make them wider when some text is entered, you can't use an ActionListener, since it will fire an event when the user presses ENTER key, not based on the text entered.

For this purpose you can register a Document Listener on your textfield's document.

You also could override getPreferredSize () method to calculate and return an appropriate size. In the example below i use a JLabel for convenience to calculate the preferred width, but you could use FontMetrics.

If you are adding multiple tags to your panel, you should also consider using a JScrollPane in order to make scrollbars appear when your panel needs more space.

See this example (i changed a bit your code because it would not compile and the general design was bad, now i think it is better, but not still good) :

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class MainWindow
{
    public static void main (String [] a) {
        SwingUtilities.invokeLater (new Runnable () {
            @Override public void run () {
                try {
                    UIManager.setLookAndFeel (UIManager.getSystemLookAndFeelClassName ());
                    createAndShowGUI ();
                }
                catch (Exception e) {
                    JOptionPane.showMessageDialog (null, "An unexpected error occurred: " + e.getClass ().getSimpleName (), "Error", JOptionPane.ERROR_MESSAGE);
                }
            }
        });
    }
    private static void createAndShowGUI () {
        JFrame frame = new JFrame ("TextFieldUpdateIssue");
        frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        frame.setContentPane (new MainPanel ());
        frame.setExtendedState (JFrame.MAXIMIZED_BOTH);
        frame.setLocationRelativeTo (null);
        frame.setVisible (true);
    }
}
class MainPanel extends JPanel
{
    private JPanel tagsPanel;

    public MainPanel () {
        super (new BorderLayout (0, 10));
        add (new JButton (new AbstractAction ("Add tag") {
             @Override public void actionPerformed(ActionEvent e) {
                addNewTag ();
            }
        }), BorderLayout.NORTH);
        tagsPanel = new JPanel ();
        tagsPanel.setLayout (new FlowLayout (FlowLayout.CENTER, 10, 0));
        add (tagsPanel, BorderLayout.CENTER);
    }
    private void addNewTag () {
        TagVisual v = new TagVisual ();
        tagsPanel.add (v);
        v.grabFocusOnField ();
        revalidate ();
    }
}
class TagVisual extends JPanel
{
    private JTextField editField;

    public TagVisual() {
        super (new FlowLayout (FlowLayout.CENTER, 0, 0));
        add (editField = createNewTextField (null), FlowLayout.LEFT);
    }
    private JTextField createNewTextField (String text) {
        JTextField textField = new JTextField (text) {
            @Override public Dimension getPreferredSize () {
                Dimension d = super.getPreferredSize ();
                return new Dimension (new JLabel (getText ()).getPreferredSize ().width + 10, d.height);
            }
        };
        textField.setBackground (Color.RED);
        textField.getDocument ().addDocumentListener (new DocumentListener () {
            @Override public void changedUpdate (DocumentEvent e) {
                revalidate ();
            }
            @Override public void insertUpdate (DocumentEvent e) {
                revalidate ();
            }
            @Override public void removeUpdate (DocumentEvent e) {
                revalidate ();
            }
        });
        return textField;
    }
    public void grabFocusOnField () {
        editField.grabFocus ();
        editField.setCaretPosition (editField.getText ().length ());
    }
}

Screenshot (short text):

enter image description here

Screenshot (Longer text):

enter image description here

Ansharja
  • 1,237
  • 1
  • 14
  • 37
0

Please review the code and note comments:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class MainWindow {

    private JPanel mainPanel;
    private JButton btnPlace;

    public MainWindow(){

        JFrame frame = new JFrame("TextFieldUpdateIssue");
        //you can't use components before initializing them
        btnPlace = new JButton("Button");
        frame.add(btnPlace, BorderLayout.NORTH);
        mainPanel = new JPanel();
        frame.add(mainPanel, BorderLayout.CENTER);
        btnPlace.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                TagVisual v = new TagVisual();
                mainPanel.add(v); //add it to main panel
                //v.place(mainPanel);
                //mainPanel.revalidate();
                //mainPanel.repaint();
                //mainPanel.updateUI();
                //frame.revalidate();
                //frame.repaint();
                frame.pack();
            }
        });

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {

        new MainWindow();
    }
}

class TagVisual extends JPanel /*JTextField*/ {

    private JTextField editField;

    public TagVisual() {

        FlowLayout layout = new FlowLayout();
        layout.setHgap(0);
        layout.setVgap(0);
        setLayout(layout);

        editField = new JTextField();
        //give it a preferred size to be used by layout manager
        editField.setPreferredSize(new Dimension(150,25));
        editField.setBackground(Color.RED);

        editField.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //not sure what you want to do here 
                //not relevant to the question 
            }
        });

        add(editField, FlowLayout.LEFT);
    }
}
c0der
  • 18,467
  • 6
  • 33
  • 65
  • 2
    `"//give it a preferred size to be used by layout manager`" -- never good thing to do with text components as it's use of a blunt instrument that runs roughshod over the innate behaviors of the component. Much better to "set size" by setting Font and column properties. For example, if the component ever goes within a JScrollPane, your suggestion will guarantee that it will not behave correctly. – Hovercraft Full Of Eels Jul 29 '17 at 12:02
  • Thanks, that worked for me after I have applied autoresize for JTextField described here: https://stackoverflow.com/questions/22128813/set-a-jtextfield-width-in-maner-to-wrap-a-given-text – Ramiz Zeynalov Jul 29 '17 at 12:08