1

Recently I'm writing a mail system client using Java (I chose swing to write the GUI and use IDEA to hardcode my GUI). In the Compose module, I want to show or hide the textfield for CC and Bcc when I click the corresponding buttons.

So I googled and browsed the following questions and doc on the web:

Finally, I chose the JScrollPane to implement it.

My simplified sample code is as follows (the original code is tedious):

import java.awt.CardLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class Demo extends JFrame implements ActionListener {
    private static final long serialVersionUID = 1L;

    private JLabel lbl1;
    private JTextField txf1;

    private JLabel lbl2;
    private JTextField txf2;
    // container for lbl2 and txf2, which should be able to be shown or hidden
    private JPanel pnlContainer2;

    private JLabel lbl3;
    private JTextField txf3;
    // container for lbl3 and txf3
    private JPanel pnlContainer3; 

    private JButton btnShow;

    // the container I want to move when I click btnShow
    private JPanel pnlBody; 

    // the panel to hold my "cards"
    // In this example, I include it just to show what controls are on my interface.
    private JPanel pnlContent;

    private JPanel pnlContainer;

    // here, I want to use JScrollPane to make my pnlContainer scrollable
    // to adapt to my interface
    private JScrollPane scrollPane;

    public Demo() {
        init();
    }

    private void init() {
        pnlContainer = new JPanel(new CardLayout(), true);
        pnlContainer.setBounds(0, 0, 200, 180);

        pnlContent = new JPanel(null, true);
        pnlContent.setBounds(0, 0, 200, 180 + 50);

        scrollPane = new JScrollPane(pnlContent, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setBounds(0, 0, 200, 180);
        pnlContainer.add(scrollPane);

        pnlBody = new JPanel(null, true);

        lbl1 = new JLabel("lbl1");
        lbl1.setBounds(10, 20, 40, 30);

        txf1 = new JTextField();
        txf1.setBounds(60, 20, 120, 30);

        pnlContent.add(lbl1);
        pnlContent.add(txf1);

        pnlContainer2 = new JPanel(null, true);
        pnlContainer2.setBounds(0, 70, 180, 30);

        lbl2 = new JLabel("lbl2");
        lbl2.setBounds(10, 0, 40, 30);

        txf2 = new JTextField();
        txf2.setBounds(60, 0, 120, 30);

        pnlContainer2.add(lbl2);
        pnlContainer2.add(txf2);
        pnlContainer2.setVisible(false);
        pnlContent.add(pnlContainer2);

        pnlBody = new JPanel(null, true);
        pnlBody.setBounds(0, 70, 180, 90);

        pnlContainer3 = new JPanel(null, true);
        pnlContainer3.setBounds(0, 0, 180, 30);
        pnlBody.add(pnlContainer3);

        lbl3 = new JLabel("lbl3");
        lbl3.setBounds(10, 0, 40, 30);

        txf3 = new JTextField();
        txf3.setBounds(60, 0, 120, 30);

        pnlContainer3.add(lbl3);
        pnlContainer3.add(txf3);

        btnShow = new JButton("show");
        btnShow.setBounds(60, 50, 80, 30);
        btnShow.addActionListener(this);
        pnlBody.add(btnShow);

        pnlContent.add(pnlBody);

        this.add(pnlContainer);
        this.setLayout(null);
        this.setTitle("Demo");
        this.setSize(200, 200);
        this.setLocationRelativeTo(null);
        this.setVisible(true);
        this.setResizable(false);
//      ImageIcon icon = new ImageIcon("E:\\Javarepo\\Hmail\\src\\main\\resources\\assets\\hmail.png");
//      this.setIconImage(icon.getImage());
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // TODO Auto-generated method stub
        Object src = e.getSource();
        if (src instanceof JButton) {
            JButton btn = (JButton) src;
            boolean showSelected = false;
            String altText;
            if (btn == btnShow) {
                showSelected = btnShow.getText() == "show";
                altText = showSelected ? "hide" : "show";
                btnShow.setText(altText);
            }
            relayout(showSelected);
        }
    }

    private void relayout(boolean showSelected) {
        int x = pnlBody.getX();
        int y = pnlBody.getY();
        if (showSelected) {
            pnlContainer2.setVisible(true);
            pnlBody.setBounds(x, y + 50, 180, 90);
        } else {
            pnlContainer2.setVisible(false);
            pnlBody.setBounds(x, y - 50, 180, 90);
        }

    }

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

}

However, no matter which JPanel I apply JScrollPane to, I cannot make my interface adaptive to the hide and show of my JContainer2.
How can I modify it, or what control to use to replace JScrollPane? Any suggestions will be welcome.

And here is my platform information:

java -version
java version "1.8.0_212" Java(TM) SE Runtime Environment (build 1.8.0_212-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.212-b10, mixed mode)

OS: win10 1909
arch: amd64

Hwa
  • 89
  • 5
  • Refer to [Doing Without a Layout Manager (Absolute Positioning)](https://docs.oracle.com/javase/tutorial/uiswing/layout/none.html): _Although it is possible to do without a layout manager, you should use a layout manager if at all possible. ... If a container holds components whose size is not affected by the container's size or by font, look-and-feel, or language changes, then absolute positioning might make sense._ – Abra Jul 18 '20 at 04:12
  • 1
    1) *"I want to show or hide the textfield for CC and Bcc when I click the corresponding buttons."* A better strategy is often to enable or disable components.. The components will generally look different enough (subject to the PLAF) to alert the user. 2) `this.setLayout(null);` Java GUIs have to work on different OS', screen size, screen resolution etc. using different PLAFs in different locales. As such, they are not conducive to pixel perfect layout. Instead use layout managers, or [combinations of them](http://stackoverflow.com/a/5630271/418556) .. – Andrew Thompson Jul 18 '20 at 04:22
  • .. along with layout padding and borders for [white space](http://stackoverflow.com/a/17874718/418556). – Andrew Thompson Jul 18 '20 at 04:25
  • Hmm... When the first time I wrote the user interface, I did try to find a suitable layout manager every time I added a panel to my GUI, but the result always don't meet my expectations. So I `setLayout(null)`. – Hwa Jul 18 '20 at 08:54
  • Tip: Add @Abra (or whoever, the `@` is important) to *notify* the person of a new comment. *"I did try to find a suitable layout manager"* That comment itself reveals a potential source of the problem, in that it is uncommon to make a single view of a GUI using only **one** layout manager. We more commonly use [**combinations** of layout managers](http://stackoverflow.com/a/5630271/418556) for each view. – Andrew Thompson Jul 20 '20 at 23:14

1 Answers1

3

I rewrote your code. Explanations appear after it.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.WindowConstants;

public class Demo2 implements ActionListener, Runnable {
    private static final String HIDE = "HIDE";
    private static final String SHOW = "SHOW";
    
    private JButton button;
    private JLabel lbl2;
    private JFrame frame;
    private JTextField txf2;

    @Override
    public void run() {
        showGui();
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        boolean visible;
        String text;
        String actionCommand = event.getActionCommand();
        switch (actionCommand) {
            case HIDE:
                text = SHOW;
                visible = false;
                break;
            case SHOW:
                text = HIDE;
                visible = true;
                break;
            default:
                text = "???";
                visible = false;
        }
        button.setText(text);
        lbl2.setVisible(visible);
        txf2.setVisible(visible);
    }

    private JPanel createButtonsPanel() {
        JPanel buttonsPanel = new JPanel();
        button = new JButton(SHOW);
        button.addActionListener(this);
        buttonsPanel.add(button);
        return buttonsPanel;
    }

    private JScrollPane createForm() {
        JPanel form = new JPanel(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;

        JLabel lbl1 = new JLabel("lbl1");
        form.add(lbl1, gbc);

        gbc.gridx = 1;
        JTextField txf1 = new JTextField(6);
        form.add(txf1, gbc);

        gbc.gridx = 0;
        gbc.gridy = 1;

        lbl2 = new JLabel("lbl2");
        lbl2.setVisible(false);
        form.add(lbl2, gbc);

        gbc.gridx = 1;
        txf2 = new JTextField(6);
        txf2.setVisible(false);
        form.add(txf2, gbc);

        gbc.gridx = 0;
        gbc.gridy = 2;

        JLabel lbl3 = new JLabel("lbl3");
        form.add(lbl3, gbc);

        gbc.gridx = 1;
        JTextField txf3 = new JTextField(6);
        form.add(txf3, gbc);

        return new JScrollPane(form,
                               ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
                               ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    }

    private void showGui() {
        frame = new JFrame("Demo");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.add(createForm(), BorderLayout.CENTER);
        frame.add(createButtonsPanel(), BorderLayout.PAGE_END);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    /**
     * Start here.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Demo2());
    }
}

You should always try to use a layout manager. The code above uses GridBagLayout but there are several other layout managers that are good at handling forms, including GroupLayout and SpringLayout as well as third party layout managers like MiG Layout and FormLayout.

In order to "show" and "hide" the middle row in your form, simply set the visible property to true or false. If the text of the button is SHOW, then when the user clicks on it, I change the button text to HIDE and make lbl2 and txf2 both visible. If the button text is HIDE, then when the user clicks the button I change the text to SHOW and make lbl2 and txf2 not visible.

Because I use a layout manager, it handles resizing the JPanel whenever the contents of the JPanel are changed. When you don't use a layout manager, then you have to write code that handles the resizing and of-course your code does not, hence your problem.

Abra
  • 19,142
  • 7
  • 29
  • 41