如何将一个JTextField放置在另一个JTextField下方?

3

我正在制作一个身份验证GUI,它应该包含2个文本字段,用户名JTextField和密码JPasswordField。我想让密码字段位于用户名字段下方。我的当前代码如下:

public class GuiAuthentication extends JFrame {

private static final int WIDTH  = 1000;
private static final int HEIGHT =  650;

private JTextField     tfUsername;
private JPasswordField tfPassword;

public GuiAuthentication() {
    try {
        setTitle("Metaspace Launcher");

        getContentPane().setLayout(new FlowLayout());
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        setSize(WIDTH, HEIGHT);

        tfUsername = new JTextField("Username");
        tfPassword = new JPasswordField("********");

        tfUsername.setBounds(10, 10, 50, 20);
        tfPassword.setBounds(10, 50, 50, 20);

        getContentPane().add(tfUsername);
        getContentPane().add(tfPassword);

        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setMinimumSize  (new Dimension(WIDTH, HEIGHT));
        setMaximumSize  (new Dimension(WIDTH, HEIGHT));

        setResizable(false);
        requestFocus();

        setLocationRelativeTo(null);
        setVisible(true);
    } catch (final Exception ex) {
        ex.printStackTrace();
        System.exit(ex.toString().hashCode());
    }
}

@Override
public void paint(final Graphics g) {
    super.paint(g);

    Gui.drawBackground(this, g, WIDTH, HEIGHT);
    Gui.drawRect(g, WIDTH/3, HEIGHT/3 + 20, 325, 200, 0xAA000000);
}

然而,这会导致密码字段位于用户名字段旁边,两者都是居中对齐的,而不是我指定的X位置10截图(点击) 问题出在我目前使用的布局(FlowLayout)吗?如果是,那么我应该使用哪一个呢?如果不是,还有什么其他问题吗?
还尝试过使用GridBagLayout。结果是这样的:这里 (字段并排、居中)

2
这就是 FlowLayout 所做的 -- 将一个组件放在另一个组件旁边。使用不同的布局方式。GridBagLayout 在这里很适合或者使用嵌套布局。 - Hovercraft Full Of Eels
你可以查看如何使用 https://dev59.com/KWIk5IYBdhLWcg3wTcYr 的 setbounds。 - Akash
1
  1. 提示:在新评论中添加@HovercraftFullOfEels(或其他人,@很重要)以通知该人。 2)“GridBagLayout的结果是这样的:”代码显然有误。最好调试一下。发布那个尝试的[mcve]。“我知道如何使用setBounds,并且我使用它正确。”使用null布局的唯一“正确”方法是不要使用它们。
- Andrew Thompson
顺便提一下 - 想法是让它们位于彼此上方,留有一些间隔,在固定大小(不可调整大小)的GUI中? - Andrew Thompson
1
你的GridBagLayout图片表明你正在添加组件而没有传递GridBagConstraints,这表明你在使用复杂布局之前没有先查看GridBagLayout教程。为自己着想,请务必查看该教程,然后尝试使用约束条件。 - Hovercraft Full Of Eels
显示剩余2条评论
1个回答

2
你的GridBagLayout尝试图片表明你要么没有在添加组件时使用GridBagConstraints,要么使用了不正确的方式。
如果您想将文本组件居中放置在GUI上,并将它们放置在自己的框中,则应该为包含它们的容器使用GridBagLayout,并传递适当的GridBagConstraints,以便与您的需求良好配合。这意味着为约束条件提供适当的gridx和gridy值以匹配您希望放置组件的网格位置。此外,您还需要正确地锚定组件,并通常要设置约束条件插入以在组件周围留出空白空间缓冲区,以避免它们相互挤在一起。我个人在做这样的事情时,经常使用一个4 x 4网格,其中包括两行两列,左侧有JLabel的一列,以便用户知道右侧每个文本组件表示什么。我经常使用辅助方法来帮助创建我的约束条件,类似于以下内容:
private GridBagConstraints createGbc(int x, int y) {
    GridBagConstraints gbc = new GridBagConstraints();
    gbc.gridx = x;
    gbc.gridy = y;
    gbc.fill = GridBagConstraints.HORIZONTAL; // stretch components horizontally
    gbc.weightx = 1.0;
    gbc.weighty = 0.0; // increase if you want component location to stretch vert.

    // I_GAP is a constant and is the size of the gap around
    // each component
    gbc.insets = new Insets(I_GAP, I_GAP, I_GAP, I_GAP);

    // if the x value is odd, anchor to the left, otherwise if even to the right
    gbc.anchor = x % 2 == 0 ? GridBagConstraints.WEST : GridBagConstraints.EAST;
    return gbc;
}

然后我会像这样使用它:

JPanel innerPanel = new JPanel(new GridBagLayout());

JLabel userNameLabel = new JLabel("User Name:");
userNameLabel.setForeground(Color.LIGHT_GRAY);
innerPanel.add(userNameLabel, createGbc(0, 0)); // add w/ GBC
innerPanel.add(tfUsername, createGbc(1, 0)); // etc...
JLabel passwordLabel = new JLabel("Password:");
passwordLabel.setForeground(Color.LIGHT_GRAY);
innerPanel.add(passwordLabel, createGbc(0, 1));
innerPanel.add(tfPassword, createGbc(1, 1));

一个工作示例可能如下所示:

在此输入图像描述


请注意,该示例仅供参考。
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.Dialog.ModalityType;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;    
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.Border;

@SuppressWarnings("serial")
public class MetaSpaceLauncherPanel extends JPanel {
    // path to a public starry image
    public static final String IMG_PATH = "https://upload.wikimedia.org/wikipedia/"
            + "commons/thumb/b/be/Milky_Way_at_Concordia_Camp%2C_Karakoram_Range%2"
            + "C_Pakistan.jpg/1280px-Milky_Way_at_Concordia_Camp%2C_Karakoram_Range"
            + "%2C_Pakistan.jpg";
    private static final int I_GAP = 10;

    private static final int COLS = 15;
    private JTextField tfUsername = new JTextField(COLS);
    private JPasswordField tfPassword = new JPasswordField(COLS);
    private BufferedImage background = null;

    public MetaSpaceLauncherPanel(BufferedImage background) {
        this.background = background;

        // close window if enter pressed and data within fields
        ActionListener listener = e -> {
            String userName = tfUsername.getText().trim();
            char[] password = tfPassword.getPassword();

            Window window = SwingUtilities.getWindowAncestor(MetaSpaceLauncherPanel.this);
            if (userName.isEmpty() || password.length == 0) {
                // both fields need to be filled!
                String message = "Both user name and password fields must contain data";
                String title = "Invalid Data Entry";
                JOptionPane.showMessageDialog(window, message, title, JOptionPane.ERROR_MESSAGE);
            } else {
                // simply close the dialog
                window.dispose();
            }
        };

        tfUsername.addActionListener(listener);
        tfPassword.addActionListener(listener);

        JPanel innerPanel = new JPanel(new GridBagLayout());
        innerPanel.setOpaque(false);
        Border outerBorder = BorderFactory.createEtchedBorder();
        Border innerBorder = BorderFactory.createEmptyBorder(I_GAP, I_GAP, I_GAP, I_GAP);
        Border border = BorderFactory.createCompoundBorder(outerBorder, innerBorder);
        innerPanel.setBorder(border);

        JLabel userNameLabel = new JLabel("User Name:");
        userNameLabel.setForeground(Color.LIGHT_GRAY);
        innerPanel.add(userNameLabel, createGbc(0, 0));
        innerPanel.add(tfUsername, createGbc(1, 0));
        JLabel passwordLabel = new JLabel("Password:");
        passwordLabel.setForeground(Color.LIGHT_GRAY);
        innerPanel.add(passwordLabel, createGbc(0, 1));
        innerPanel.add(tfPassword, createGbc(1, 1));

        setLayout(new GridBagLayout());
        add(innerPanel); // add without constraints to center it
    }

    public String getUserName() {
        return tfUsername.getText();
    }

    public char[] getPassword() {
        return tfPassword.getPassword();
    }

    private GridBagConstraints createGbc(int x, int y) {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = x;
        gbc.gridy = y;
        gbc.fill = GridBagConstraints.HORIZONTAL; // stretch components horizontally
        gbc.weightx = 1.0;
        gbc.weighty = 0.0; // increase if you want component location to stretch vert.

        // I_GAP is a constant and is the size of the gap around
        // each component
        gbc.insets = new Insets(I_GAP, I_GAP, I_GAP, I_GAP);

        // if the x value is odd, anchor to the left, otherwise if even to the right
        gbc.anchor = x % 2 == 0 ? GridBagConstraints.WEST : GridBagConstraints.EAST;
        return gbc;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (background != null) {
            g.drawImage(background, 0, 0, this);
        }
    }

    @Override
    public Dimension getPreferredSize() {
        if (isPreferredSizeSet() || background == null) {
            return super.getPreferredSize();
        }
        int w = background.getWidth();
        int h = background.getHeight();
        return new Dimension(w, h);
    }

    private static void createAndShowGui() {
        BufferedImage img = null;
        try {
            // just using this as an example image, one available to all
            // you would probably use your own image
            URL imgUrl = new URL(IMG_PATH); // online path to starry image
            img = ImageIO.read(imgUrl);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1); // no image available -- exit!
        }
        MetaSpaceLauncherPanel launcherPanel = new MetaSpaceLauncherPanel(img);

        JDialog dialog = new JDialog(null, "MetaSpace Launcher", ModalityType.APPLICATION_MODAL);
        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        dialog.getContentPane().add(launcherPanel);
        dialog.pack();
        dialog.setLocationRelativeTo(null);
        dialog.setVisible(true);

        // test to see if we can get the data
        String userName = launcherPanel.getUserName();
        char[] password = launcherPanel.getPassword();

        // don't convert password into String as I'm doing below as it is now
        // not secure
        String message = String.format("<html>User Name: %s<br/>Password: %s</html>", userName,
                new String(password));
        JOptionPane.showMessageDialog(null, message);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}

有没有办法完全禁用自动大小调整和定位?所有的布局似乎都不适合我想要的,我更喜欢自己计算一切。 - German Vekhorev
@HopeIsNope: 是的,你可以将布局设置为null,但我敦促你不要这样做。让布局为你做工作会更好。想象一下,你使用空布局和setBounds创建了一个复杂的布局,在你的计算机上它看起来很棒。然后一个朋友尝试运行你的程序,但在他们的计算机上,按钮名称没有完全显示——这是由于不同的屏幕分辨率造成的常见问题。或者稍后,你想向其中一组添加一个JRadioButton,现在你发现你必须重新计算所有组件的位置和大小,以及要更改的区域右侧和下方的所有组件。 - Hovercraft Full Of Eels
@HopeIsNope:尝试维护这些东西真是一场噩梦。如果你学习布局管理器并且学得好,GUI 就会更自然地落在正确的位置上。 - Hovercraft Full Of Eels
请问您能否提供一些好的布局教程或解释的链接吗? - German Vekhorev
非常荣幸为您提供布局管理器教程。此外,您还可以查看Swing专家在该主题上的帖子,您可以在这里找到。特别是要注意Camickr、MadProgrammer和AndrewThompson的帖子。 - Hovercraft Full Of Eels

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接