bullen
bullen

Reputation: 53

JPanel with children that matches the width of the parent and wraps the height of the content

I am trying to make the contents of a JPanel (the "parent panel") be added from the top towards the bottom, and have the width of each component match the width of the parent panel while having the height of each component wrap the content. The content will be dynamically generated.

This is what I'd like it to look like:

enter image description here

This would've been fairly easy to do in Android. I have looked into the Swing LayoutManagers, but none of them seemed to be an obvious choice. I have tried using Boxlayout (vertical), which works except for the panels not filling the width of the parent panel (and this makes it look really bad).

Thankful for any advice!

Edit:

Thanks to camickr's answer, I figured it out. In case it would help anyone in the future, I have supplied my code and a screenshot.

The code that solved the question:

(Note the use of GridBagLayout, BorderLayout and the parameters for the GridBagConstraints)

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Random;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class Test extends JFrame{

    private final Color darkGreen = Color.decode("#388E3C");
    private final Color green = Color.decode("#4CAF50");
    private final Color lightGreen = Color.decode("#C8E6C9");

    public static void main(String[] args){
        Test test = new Test();
        test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    int noOfTitles = 4;
    int noOfSubTitles = 3;
    Random random = new Random();

    public Test(){


        JPanel parentPanel = new JPanel();
        this.setSize(new Dimension(300,800));
        this.setContentPane(parentPanel);
        parentPanel.setBackground(Color.white);

        //Set layout of parent panel to Gridlayout
        parentPanel.setLayout(new GridBagLayout());

        //Panel for our titles
        JPanel titlesPanel = new JPanel();
        titlesPanel.setLayout(new BoxLayout(titlesPanel, BoxLayout.Y_AXIS));

        for(int i = 0;i<noOfTitles;i++){
            //Panel for the subtitles of each title
            JPanel subTitlesPanel = new JPanel();
            subTitlesPanel.setLayout(new BoxLayout(subTitlesPanel, BoxLayout.Y_AXIS));

            for(int j = 0;j<noOfSubTitles;j++){
                //Get a panel with a title header, a collapse/uncollapse button and some sample content
                subTitlesPanel.add(getCollapsiblePanel(getSampleContent(),"Subtitle"));
                subTitlesPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
            }
            //Get a panel with a title header, a collapse/uncollapse button and the subtitles as content
            titlesPanel.add(getCollapsiblePanel(subTitlesPanel,"Title"));
        }

        //The constraints for the 
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.anchor = GridBagConstraints.PAGE_START;
        constraints.fill = GridBagConstraints.HORIZONTAL; //Fill the panels horizontally. A weightx is needed for this to work. 
        constraints.gridx = 1;
        constraints.gridy = 0;
        constraints.weightx = 1;    //Actually makes it fill
        parentPanel.add(titlesPanel,constraints);

        //To actually bump the rest of the other stuff to the top, 
        //we need something below with a weighty > 0
        constraints.gridy = 1;
        constraints.weighty = 1;
        constraints.anchor = GridBagConstraints.PAGE_END;
        parentPanel.add(new JLabel(""),constraints);

        this.setVisible(true);
    }

    /*
     * Gets a title for the supplied content-panel. 
     * The title includes the titleText and a button for collapsing/uncollapsing the content. 
     */
    private JPanel getCollapsiblePanel(JPanel content,String titleText){
        JPanel titlePanel = new JPanel();   //Top container for the title
        JPanel title = new JPanel();        //collapse/uncollapse button and title text

        title.setLayout(new BoxLayout(title,BoxLayout.X_AXIS));
        title.add(getToggleVisibilityIcon(content));
        title.add(new JLabel(" "+titleText));

        //A border layout is needed here for the fill of the parent panel to work 
        // (I tried with the box layout but it didn't work)
        titlePanel.setLayout(new BorderLayout());
        titlePanel.add(title,BorderLayout.PAGE_START);
        titlePanel.add(content,BorderLayout.CENTER);

        //Vanity
        title.setBackground(green);
        title.setBorder(BorderFactory.createEmptyBorder(1,1,2,1));

        return titlePanel;
    }

    /*
     * Not important, just generates a panel with some "sample" strings. 
     */
    private JPanel getSampleContent(){
        JPanel content = new JPanel();
        content.setLayout(new BoxLayout(content,BoxLayout.Y_AXIS));
        for(int i = 0; i<1+random.nextInt(3); i++){
            String sampleContent = "";
            for(int j = 0;j<1 + random.nextInt(5);j++){
                sampleContent += "sample ";
            }
            JLabel sample = new JLabel(sampleContent);
            sample.setForeground(Color.decode("#111111"));
            content.add(sample);
        }
        content.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
        content.setBackground(lightGreen);
        return content;
    }

    /*
     * Method that returns a JLabel that will toggle the visibility of the 
     * supplied content panel upon clicking. 
     */
    private JLabel getToggleVisibilityIcon(JPanel content){
        JLabel icon = new JLabel(" V ");
        icon.setBackground(green);
        icon.setBorder(BorderFactory.createLineBorder(darkGreen,2));
        icon.setOpaque(true);
        icon.addMouseListener(new MouseListener(){
            private JPanel content;
            private JLabel icon;
            MouseListener init(JPanel content,JLabel icon){
                this.content = content;
                this.icon = icon;
                return this;
            }
            @Override
            public void mouseClicked(MouseEvent e) {
                if(content.isVisible()){
                    content.setVisible(false);
                    icon.setText(" > ");
                }else{
                    content.setVisible(true);
                    icon.setText(" V ");
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {}
            @Override
            public void mouseReleased(MouseEvent e) {}
            @Override
            public void mouseEntered(MouseEvent e) {}
            @Override
            public void mouseExited(MouseEvent e) {}
        }.init(content,icon));
        return icon;
    }
}

A screenshot of the running application:

enter image description here

Upvotes: 3

Views: 3000

Answers (1)

camickr
camickr

Reputation: 324118

Not really understand your question. You generally don't have text, buttons and combo boxes randomly wrap. Usually the panel has a layout so the components are logically organized. That is you don't want a label with text like "First Name" followed by a text field that randomly wraps to the next line.

It looks to me like you have a "detail panel" that you hide or show?

Well you could implement this using a BorderLayout. So the hide/show button would be placed in the BorderLayout.PAGE_START. Then the detail panel would be in the BorderLayout.CENTER. Then you set the visibility of the detail panel as required.

Then the top level container could be a GridBagLayout. You can use the constraints to have each row fill the width of the cell. Each child panel could use a Border to make it slightly indents on the left side.

Read the Swing tutorial. There are sections on:

  1. How to Use GridBagLayout and
  2. How to Use Borders

Upvotes: 1

Related Questions