Swing:JPanel 中的 JTextField,重绘问题

Swing: JTextField inside JPanel, redraw issue

我最近开始使用 Java+Swing 构建一个 UI,目前我在使用 FlowLayout 将 JTextField 放置在 JPanel 上时遇到问题。

在我的示例中,我有一个 window,包含带按钮的面板。单击该按钮会添加一个派生自 JPanel 并包含 JTextField 的组件。

问题是,当我在 JTextField 中键入时,它不会更新(不会调整大小)。但是,当我调整 window 的大小时或执行任何其他强制 window/panel 重绘的操作时,文本字段会被调整大小(这正是我希望自动发生的)。

当我将基础 class 从 JPanel 更改为 JTextField 时,它以我尝试实现的方式工作,但我需要将 JPanel 作为基础 class 以便我可以利用放置它的子组件。

我在这里检查了不同的问题,并用谷歌搜索试图找到解决方案,但它对我不起作用。我已经尝试 validate/invalidate/revalidate/repaint 以不同的组合和不同的组件,以及尝试对每个键入的字符强制重新验证,这对我来说听起来不是正确的方法。到目前为止,我明白这与布局管理器有关。

任何人都可以帮助我了解它是如何工作的以及我应该阅读有关 Swing UI、布局管理和重绘如何工作的哪些内容?

此外,如果有人可以帮助我解决我的代码的特定问题,我将很高兴。

提前致谢!

下面是我的代码:

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);
    }
}

请查看代码并注意注释:

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);
    }
}

如果我是你,我不会在用户输入一些文本时尝试调整文本字段的大小。

我建议你使用 JTextField (int columns) 构造函数给它们一个固定的大小,这将允许你创建一些 "wide enough".

的文本字段

如果在输入某些文本时仍想让它们变宽,则不能使用 ActionListener,因为它会在用户按下 ENTER 键时触发一个事件,而不是基于输入的文本。

为此,您可以在文本字段的文档中注册 Document Listener

您也可以覆盖 getPreferredSize () 方法来计算和 return 合适的大小。在下面的示例中,为了方便计算首选宽度,我使用了 JLabel,但您可以使用 FontMetrics。

如果您要向面板添加多个标签,您还应该考虑使用 JScrollPane 以便在面板需要更多标签时显示滚动条 space。

看到这个例子(我稍微修改了你的代码,因为它无法编译,而且总体设计很糟糕,现在我认为它更好,但仍然不是很好):

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 ());
    }
}

屏幕截图(短文本):

屏幕截图(较长的文本):