3

I am making authentication GUI which should contain 2 text fields, username JTextField and password JPasswordField. I want to make the password field be below the username field. my current code is as follows:

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

However, this results in the password field being located against the username field, and both of them are centered, and not located at X position 10 which I specify:

--> Screenshot (click)

Is the problem in the layout I currently use (FlowLayout)? If so, which one should I use then? If not, what else might be wrong?

Tried using GridBagLayout as well. It results in this (fields side by side, centered):

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
German Vekhorev
  • 339
  • 6
  • 16
  • 2
    That's what FlowLayout does -- puts one component next to the other. Use different layouts. A GridBagLayout would work well here or nested layouts. – Hovercraft Full Of Eels Jul 30 '17 at 16:44
  • You can look how to use https://stackoverflow.com/questions/19415170/what-is-setbounds-and-how-do-i-use-it setbounds – Akash Jul 30 '17 at 16:46
  • GridBagLayout results in this: https://sc.reflex.rip/FwJVkM.png Not much changed. Fields have simply centered vertically. I know how to use setBounds, and I use it correctly. – German Vekhorev Jul 30 '17 at 17:06
  • 1
    1) Tip: Add @HovercraftFullOfEels (or whoever, the `@` is important) to *notify* the person of a new comment. 2) *"GridBagLayout results in this:"* The code is obviously wrong. Best to debug that. Post a [mcve] of that attempt. *"I know how to use setBounds, and I use it correctly."* The only 'correct' way to use `null` layouts is **not to** (use them). – Andrew Thompson Jul 30 '17 at 17:13
  • BTW - the idea is to get them located one above the other, with a little space, in a fixed size (not resizable) GUI? – Andrew Thompson Jul 30 '17 at 17:18
  • [For example](https://stackoverflow.com/a/9852059/522444) – Hovercraft Full Of Eels Jul 30 '17 at 18:22
  • 1
    Your GridBagLayout images suggests that you're adding components without passing in GridBagConstraints, suggesting that you're using a complex layout without first reviewing the [GridBagLayout tutorial](http://docs.oracle.com/javase/tutorial/uiswing/layout/gridbag.html). Do yourself a favor and do check out that tutorial, and then play with use of constraints. – Hovercraft Full Of Eels Jul 30 '17 at 18:33

1 Answers1

2

Your GridBagLayout attempt images suggest that either you're not using GridBagConstraints when adding components or that you're using the incorrectly.

If you want to center your text components in the GUI, one over the other, and say put them in their own box, then use a GridBagLayout for the container that holds them, and also pass in appropriate GridBagConstraints that will work well with your desire. This will mean giving the constraints an appropriate gridx and gridy value to match where in the grid you wish to place the component. Also you will want to anchor the component correctly, and you will usually want to set the constraints insets to give an empty space buffer around your components so that they don't crowd each other. Myself, when I do something like this, I often use a 4 x 4 grid with two rows and two columns including a column of JLabels on the left so the user knows what each text component in the right column represents. I often use a helper method to help create my constraints, something like this:

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

And then I'd use it like this:

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

A working example could look like so:

enter image description here

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());
    }
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • Are there ways to disable automated sizing and positioning at all? All the layouts don't seem to fit fine for what I want, and I'd prefer to calculate everything myself. – German Vekhorev Jul 31 '17 at 10:01
  • @HopeIsNope: Yes, you could set the layout to `null`, but I urge you not to do this. Much better to let the layouts do the work for you. Image that you've created a complex layout using null layout and setBounds, and it looks great on your computer. Then a friend tries your program, but on their computer the button names don't fully show -- a common problem due to different screen resolutions. Or later you want to add one more JRadioButton to a group of them, and now you find you have to re-calculate the position and sizes of all components to the right and below the area you want to change – Hovercraft Full Of Eels Jul 31 '17 at 10:55
  • @HopeIsNope: it's a nightmare to try to maintain these things. If you learn the layout managers, and learn them well, the GUI falls much more naturally into place. – Hovercraft Full Of Eels Jul 31 '17 at 10:56
  • could you please link some good layout tutorial/explanation then please? – German Vekhorev Jul 31 '17 at 11:19
  • My pleasure: [layout manager tutorial](https://docs.oracle.com/javase/tutorial/uiswing/layout/index.html). Also check out the posts from Swing experts on the subject which you can find [here](https://stackoverflow.com/questions/tagged/java+swing+layout-manager?sort=votes&pageSize=50). Look for Camickr's, MadProgrammer's, and AndrewThompson's posts in particular. – Hovercraft Full Of Eels Jul 31 '17 at 14:15