Invisible components still take up space JPanel

12,374

Solution 1

because there is still an empty gap where the components were.

Yes, GridLayout is not that smart. It just uses the total number of components to determine the number of row/columns.

Is there a quick and easy way to do this?

I would create a custom layout manager. Just copy the GridLayout code and make a couple of changes. The basic changes would be:

  1. Override the ncomponents variable. Instead of just using the number of components on the panel you would need to loop thorugh all the components and count the visible ones.

  2. In the layout code you would need to add an if (visible) check.

Edit:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;

public class InvisibleGridLayout implements LayoutManager, java.io.Serializable
{
    int hgap;
    int vgap;
    int rows;
    int cols;

    public InvisibleGridLayout() {
    this(1, 0, 0, 0);
    }

    public InvisibleGridLayout(int rows, int cols) {
    this(rows, cols, 0, 0);
    }

    public InvisibleGridLayout(int rows, int cols, int hgap, int vgap) {
    if ((rows == 0) && (cols == 0)) {
        throw new IllegalArgumentException("rows and cols cannot both be zero");
    }
    this.rows = rows;
    this.cols = cols;
    this.hgap = hgap;
    this.vgap = vgap;
    }

    public int getRows() {
    return rows;
    }

    public void setRows(int rows) {
    if ((rows == 0) && (this.cols == 0)) {
        throw new IllegalArgumentException("rows and cols cannot both be zero");
    }
    this.rows = rows;
    }

    public int getColumns() {
    return cols;
    }

    public void setColumns(int cols) {
    if ((cols == 0) && (this.rows == 0)) {
        throw new IllegalArgumentException("rows and cols cannot both be zero");
    }
    this.cols = cols;
    }

    public int getHgap() {
    return hgap;
    }

    public void setHgap(int hgap) {
    this.hgap = hgap;
    }

    public int getVgap() {
    return vgap;
    }

    public void setVgap(int vgap) {
    this.vgap = vgap;
    }

    public void addLayoutComponent(String name, Component comp) {
    }

    public void removeLayoutComponent(Component comp) {
    }

    public Dimension preferredLayoutSize(Container parent) {
      synchronized (parent.getTreeLock()) {
    Insets insets = parent.getInsets();
//  int ncomponents = parent.getComponentCount();
    int ncomponents = getVisibleComponents(parent);
    int nrows = rows;
    int ncols = cols;

    if (nrows > 0) {
        ncols = (ncomponents + nrows - 1) / nrows;
    } else {
        nrows = (ncomponents + ncols - 1) / ncols;
    }
    int w = 0;
    int h = 0;
//  for (int i = 0 ; i < ncomponents ; i++) {
    for (int i = 0 ; i < parent.getComponentCount(); i++) {
        Component comp = parent.getComponent(i);

        if (!comp.isVisible()) continue; // added

        Dimension d = comp.getPreferredSize();
        if (w < d.width) {
        w = d.width;
        }
        if (h < d.height) {
        h = d.height;
        }
    }

    Dimension d = new Dimension(insets.left + insets.right + ncols*w + (ncols-1)*hgap,
                 insets.top + insets.bottom + nrows*h + (nrows-1)*vgap);

    return d;
      }
    }

    public Dimension minimumLayoutSize(Container parent) {
      synchronized (parent.getTreeLock()) {
        Insets insets = parent.getInsets();
//  int ncomponents = parent.getComponentCount();
    int ncomponents = getVisibleComponents(parent);
    int nrows = rows;
    int ncols = cols;

    if (nrows > 0) {
        ncols = (ncomponents + nrows - 1) / nrows;
    } else {
        nrows = (ncomponents + ncols - 1) / ncols;
    }
    int w = 0;
    int h = 0;
//  for (int i = 0 ; i < ncomponents ; i++) {
    for (int i = 0 ; i < parent.getComponentCount(); i++) {
        Component comp = parent.getComponent(i);

        if (!comp.isVisible()) continue; // added

        Dimension d = comp.getMinimumSize();
        if (w < d.width) {
        w = d.width;
        }
        if (h < d.height) {
        h = d.height;
        }
    }

    Dimension d = new Dimension(insets.left + insets.right + ncols*w + (ncols-1)*hgap,
                 insets.top + insets.bottom + nrows*h + (nrows-1)*vgap);

    return d;
      }
    }

    public void layoutContainer(Container parent) {
      synchronized (parent.getTreeLock()) {
    Insets insets = parent.getInsets();
//  int ncomponents = parent.getComponentCount();
    int ncomponents = getVisibleComponents(parent);
    int nrows = rows;
    int ncols = cols;
    boolean ltr = parent.getComponentOrientation().isLeftToRight();

    if (ncomponents == 0) {
        return;
    }
    if (nrows > 0) {
        ncols = (ncomponents + nrows - 1) / nrows;
    } else {
        nrows = (ncomponents + ncols - 1) / ncols;
    }

//  int w = parent.width - (insets.left + insets.right);
//  int h = parent.height - (insets.top + insets.bottom);
    int w = parent.getSize().width - (insets.left + insets.right);
    int h = parent.getSize().height - (insets.top + insets.bottom);
    w = (w - (ncols - 1) * hgap) / ncols;
    h = (h - (nrows - 1) * vgap) / nrows;
/*
    if (ltr) {
        for (int c = 0, x = insets.left ; c < ncols ; c++, x += w + hgap) {
        for (int r = 0, y = insets.top ; r < nrows ; r++, y += h + vgap) {
            int i = r * ncols + c;
            if (i < ncomponents) {
            parent.getComponent(i).setBounds(x, y, w, h);
            }
        }
        }
    } else {
//      for (int c = 0, x = parent.width - insets.right - w; c < ncols ; c++, x -= w + hgap) {
        for (int c = 0, x = parent.getSize().width - insets.right - w; c < ncols ; c++, x -= w + hgap) {
        for (int r = 0, y = insets.top ; r < nrows ; r++, y += h + vgap) {
            int i = r * ncols + c;
            if (i < ncomponents) {
            parent.getComponent(i).setBounds(x, y, w, h);
            }
        }
        }
    }
      }
*/

        int i = 0;

        if (ltr)
        {
            for (int r = 0, y = insets.top ; r < nrows ; r++, y += h + vgap)
            {
                int c = 0;
                int x = insets.left;

                while (c < ncols)
                {
                    if (i >= parent.getComponentCount()) break;

                    Component component = parent.getComponent(i);

                    if (component.isVisible())
                    {
                        parent.getComponent(i).setBounds(x, y, w, h);
                        c++;
                        x += w + hgap;
                    }

                    i++;
                }
            }
        }

    }}

    private int getVisibleComponents(Container parent)
    {
        int visible = 0;

        for (Component c: parent.getComponents())
        {
            if (c.isVisible())
                visible++;
        }

        return visible;
    }

    public String toString() {
    return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap +
                           ",rows=" + rows + ",cols=" + cols + "]";
    }



    public static void main(String[] args)
    {
        final JPanel innerPane = new JPanel();
        JScrollPane scr  = new JScrollPane(innerPane);

        innerPane.setLayout(new InvisibleGridLayout(0, 3));


        for (int i = 0; i < 30; i++)
        {
            JPanel ret = new JPanel();
            JLabel lbl = new JLabel("This is  pane " + i);

            ret.add(lbl);
            ret.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
            ret.setBackground(Color.gray);

            innerPane.add(ret);
        }

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(scr);
        frame.setBounds(400, 0, 400, 700);
        frame.setVisible(true);

        javax.swing.Timer timer = new javax.swing.Timer(2000, new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                for (int i = 0; i < 30; i++)
                {
                    if (i%2==0)
                        innerPane.getComponent(i).setVisible(false);
                }

            }
        });
        timer.setRepeats(false);
        timer.start();

    }
}

Solution 2

Here are 3 ways off the top of my head.

Hide Components

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class HideComponents {

    public static void main(String args[]) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JPanel gui = new JPanel(new BorderLayout());
                JToolBar tb = new JToolBar();
                gui.add(tb, BorderLayout.NORTH);
                final JButton openTool = new JButton("Open");
                final JButton saveTool = new JButton("Save");
                tb.add( openTool );
                tb.add( saveTool );

                JPanel buttonFlow = new JPanel(new FlowLayout(3));
                gui.add(buttonFlow, BorderLayout.CENTER);
                final JButton openFlow = new JButton("Open");
                final JButton saveFlow = new JButton("Save");
                buttonFlow.add( openFlow );
                buttonFlow.add( saveFlow );

                JPanel buttonBox = new JPanel();
                gui.add(buttonBox, BorderLayout.EAST);
                BoxLayout bl = new BoxLayout(buttonBox, BoxLayout.Y_AXIS);
                buttonBox.setLayout(bl);
                final JButton openBox = new JButton("Open");
                final JButton saveBox = new JButton("Save");
                buttonBox.add( openBox );
                buttonBox.add( saveBox );

                final JCheckBox openChoice = new JCheckBox("Show open", true);
                openChoice.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent ae) {
                        openTool.setVisible(openChoice.isSelected());
                        openFlow.setVisible(openChoice.isSelected());
                        openBox.setVisible(openChoice.isSelected());
                    }
                });
                gui.add(openChoice, BorderLayout.SOUTH);

                JOptionPane.showMessageDialog(null, gui);
            }
        });
    }
}

On reflection

Please consider swapping:

button.setVisible(false);

For:

button.setEnabled(false);

This will be more intuitive to most users who view the GUI, and has the same ultimate effect.

Vis:

Disable Components

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class DisableComponents {

    public static void main(String args[]) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JPanel gui = new JPanel(new BorderLayout());
                JToolBar tb = new JToolBar();
                gui.add(tb, BorderLayout.NORTH);
                final JButton openTool = new JButton("Open");
                final JButton saveTool = new JButton("Save");
                tb.add( openTool );
                tb.add( saveTool );

                JPanel buttonFlow = new JPanel(new FlowLayout(3));
                gui.add(buttonFlow, BorderLayout.CENTER);
                final JButton openFlow = new JButton("Open");
                final JButton saveFlow = new JButton("Save");
                buttonFlow.add( openFlow );
                buttonFlow.add( saveFlow );

                JPanel buttonBox = new JPanel();
                gui.add(buttonBox, BorderLayout.EAST);
                BoxLayout bl = new BoxLayout(buttonBox, BoxLayout.Y_AXIS);
                buttonBox.setLayout(bl);
                final JButton openBox = new JButton("Open");
                final JButton saveBox = new JButton("Save");
                buttonBox.add( openBox );
                buttonBox.add( saveBox );

                final JCheckBox openChoice = new JCheckBox("Enable open", true);
                openChoice.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent ae) {
                        openTool.setEnabled(openChoice.isSelected());
                        openFlow.setEnabled(openChoice.isSelected());
                        openBox.setEnabled(openChoice.isSelected());
                    }
                });
                gui.add(openChoice, BorderLayout.SOUTH);

                JOptionPane.showMessageDialog(null, gui);
            }
        });
    }
}

Solution 3

don't call Thread.sleep(int); during EDT, because block EDT, use javax.swing.Timer

for example

import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.border.*;

public class SSCCE extends JFrame {

    private static final long serialVersionUID = 1L;
    private JPanel innerPane = new JPanel();
    private JScrollPane scr = new JScrollPane(innerPane);
    private Timer timer;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                SSCCE sSCCE = new SSCCE();
            }
        });
    }

    private JPanel getPane() {
        JPanel ret = new JPanel();
        JLabel lbl = new JLabel("This is a pane.");
        ret.add(lbl);
        ret.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
        ret.setBackground(Color.gray);
        return ret;
    }

    public SSCCE() {
        innerPane.setLayout(new GridLayout(0, 1));
        add(scr);
        for (int i = 0; i < 30; i++) {
            innerPane.add(getPane());
        }
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pack();
        setVisible(true);
        start();
    }

    private void start() {
        timer = new javax.swing.Timer(2000, updateCol());
        timer.start();
        timer.setRepeats(false);
    }

    private Action updateCol() {
        return new AbstractAction("Hide Row Action") {

            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                for (int i = 0; i < 30; i++) {
                    if (i % 2 == 0) {
                        innerPane.getComponent(i).setVisible(false);
                    }
                }
            }
        };
    }
}

Solution 4

I really don't like the GridLayout. Instead of writing your own layout manager, I would suggest you take a look at the TableLayout. I use it all the time.

The initial learning curve is a bit steeper than the GridLayout, but it is easy to get it to behave the way you want.

http://java.sun.com/products/jfc/tsc/articles/tablelayout/

Share:
12,374
David
Author by

David

Hey! I'm David, co-founder at a design &amp; dev startup called All Front. I love to build single page apps with the amazing design &amp; dev team! I'm enthusiastic about Angular, React and progressive web apps.

Updated on July 29, 2022

Comments

  • David
    David almost 2 years

    I have a series of components underneath each other in a JPanel set as a GridLayout. I need to temporarily hide the components but setVisible(false) doesn't cut it, because there is still an empty gap where the components were.

    Is there a quick and easy way to do this? Or do I have to stay saving the state of the JPanel, removing the components, then restoring it?

    SSCCE:

    [GridLayout2.java]

    import java.awt.Component;
    import java.awt.Container;
    import java.awt.Dimension;
    import java.awt.GridLayout;
    import java.awt.Insets;
    
    public class GridLayout2 extends GridLayout 
    {
      public GridLayout2() {
        this(1, 0, 0, 0);
      }
    
      public GridLayout2(int rows, int cols) {
        this(rows, cols, 0, 0);
      }
    
      public GridLayout2(int rows, int cols, int hgap, int vgap) {
        super(rows, cols, hgap, vgap);
      }
    
      public Dimension preferredLayoutSize(Container parent) {
        //System.err.println("preferredLayoutSize");
        synchronized (parent.getTreeLock()) {
          Insets insets = parent.getInsets();
          int ncomponents = parent.getComponentCount();
          int nrows = getRows();
          int ncols = getColumns();
          if (nrows > 0) {
            ncols = (ncomponents + nrows - 1) / nrows;
          } 
          else {
            nrows = (ncomponents + ncols - 1) / ncols;
          }
          int[] w = new int[ncols];
          int[] h = new int[nrows];
          for (int i = 0; i < ncomponents; i ++) {
            int r = i / ncols;
            int c = i % ncols;
            Component comp = parent.getComponent(i);
            Dimension d = comp.getPreferredSize();
            if (w[c] < d.width) {
              w[c] = d.width;
            }
            if (h[r] < d.height) {
              h[r] = d.height;
            }
          }
          int nw = 0;
          for (int j = 0; j < ncols; j ++) {
            nw += w[j];
          }
          int nh = 0;
          for (int i = 0; i < nrows; i ++) {
            nh += h[i];
          }
          return new Dimension(insets.left + insets.right + nw + (ncols-1)*getHgap(), 
              insets.top + insets.bottom + nh + (nrows-1)*getVgap());
        }
      }
    
      public Dimension minimumLayoutSize(Container parent) {
        System.err.println("minimumLayoutSize");
        synchronized (parent.getTreeLock()) {
          Insets insets = parent.getInsets();
          int ncomponents = parent.getComponentCount();
          int nrows = getRows();
          int ncols = getColumns();
          if (nrows > 0) {
            ncols = (ncomponents + nrows - 1) / nrows;
          } 
          else {
            nrows = (ncomponents + ncols - 1) / ncols;
          }
          int[] w = new int[ncols];
          int[] h = new int[nrows];
          for (int i = 0; i < ncomponents; i ++) {
            int r = i / ncols;
            int c = i % ncols;
            Component comp = parent.getComponent(i);
            Dimension d = comp.getMinimumSize();
            if (w[c] < d.width) {
              w[c] = d.width;
            }
            if (h[r] < d.height) {
              h[r] = d.height;
            }
          }
          int nw = 0;
          for (int j = 0; j < ncols; j ++) {
            nw += w[j];
          }
          int nh = 0;
          for (int i = 0; i < nrows; i ++) {
            nh += h[i];
          }
          return new Dimension(insets.left + insets.right + nw + (ncols-1)*getHgap(), 
              insets.top + insets.bottom + nh + (nrows-1)*getVgap());
        }
      }
    
      public void layoutContainer(Container parent) {
        //System.err.println("layoutContainer");
        synchronized (parent.getTreeLock()) {
          Insets insets = parent.getInsets();
          int ncomponents = parent.getComponentCount();
          int nrows = getRows();
          int ncols = getColumns();
          if (ncomponents == 0) {
            return;
          }
          if (nrows > 0) {
            ncols = (ncomponents + nrows - 1) / nrows;
          } 
          else {
            nrows = (ncomponents + ncols - 1) / ncols;
          }
          int hgap = getHgap();
          int vgap = getVgap();
          // scaling factors      
          Dimension pd = preferredLayoutSize(parent);
          double sw = (1.0 * parent.getWidth()) / pd.width;
          double sh = (1.0 * parent.getHeight()) / pd.height;
          // scale
          int[] w = new int[ncols];
          int[] h = new int[nrows];
          for (int i = 0; i < ncomponents; i ++) {
            int r = i / ncols;
            int c = i % ncols;
            Component comp = parent.getComponent(i);
            Dimension d = comp.getPreferredSize();
            d.width = (int) (sw * d.width);
            d.height = (int) (sh * d.height);
            if (w[c] < d.width) {
              w[c] = d.width;
            }
            if (h[r] < d.height) {
              h[r] = d.height;
            }
          }
          for (int c = 0, x = insets.left; c < ncols; c ++) {
            for (int r = 0, y = insets.top; r < nrows; r ++) {
              int i = r * ncols + c;
              if (i < ncomponents) {
                parent.getComponent(i).setBounds(x, y, w[c], h[r]);
              }
              y += h[r] + vgap;
            }
            x += w[c] + hgap;
          }
        }
      }  
    }
    

    [SSCCE.java]

    import java.awt.Color;
    import javax.swing.*;
    import javax.swing.border.*;
    
    public class SSCCE extends JFrame{
    
        JPanel innerPane = new JPanel();
        JScrollPane scr  = new JScrollPane(innerPane);
    
        public static void main(String[] args) {
            new SSCCE();
        }
    
    
        public SSCCE() {
    
            setSize(400, 800);
            innerPane.setLayout(new GridLayout2(0, 1));
    
            add(scr);
    
            for (int i = 0; i < 30; i++)
            {
                innerPane.add(getPane());
            }
    
            setVisible(true);
    
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {}
    
            for (int i = 0; i < 30; i++)
            {
                if (i%2==0)
                    innerPane.getComponent(i).setVisible(false);
            }
    
        }
    
    
        private JPanel getPane()
        {
            JPanel ret = new JPanel();
            JLabel lbl = new JLabel("This is a pane.");
    
            ret.add(lbl);
            ret.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
            ret.setBackground(Color.gray);
    
            return ret;
    
        }
    
    }
    

    screenshot

  • David
    David over 12 years
    I edited the question. I had already (incidentally) needed to modify the GridLayout because I needed rows of different sizes. I added the class code. I have tried doing as you said but it's not trivial (for me). I keep getting arrayIndexOutOfBoundsException in the for loop. What do you do when you detect an invisible component?
  • David
    David over 12 years
    You missed the whole point. I'm using a gridLayout, with rows. It's too much work to change it now that the program is almost ready.
  • David
    David over 12 years
    @RiaanCornelius It's a modified version of a GridLayout, not Gridbaglayout :) Nevertheless, thanks.
  • Riaan Cornelius
    Riaan Cornelius over 12 years
    Sorry, I just mistyped... I meant GridLayout. My description of my dislike of Gridbag would be more graphic :)
  • David
    David over 12 years
    Unfortunately I need to hide the component without removing it :( I need to clean up the clutter without making the user interface ugly. Simply setting it invisible does the latter. Thanks for the suggestion though.
  • David
    David over 12 years
    I was trying to keep the short as code as possible to demonstrate the problem. Your suggestion, although indicates a measure of good practice, does not solve the defined problem.
  • mKorbel
    mKorbel over 12 years
    your code demonstated only mess, btw your question was answered by another posters, then no reason why I repeating that again, thanks for down-voting
  • David
    David over 12 years
    Sorry for the downvote. It is not answered, or else I would have selected an answer. I still have a problem doing what Camickr suggested. I will update the code with what I tried.
  • camickr
    camickr over 12 years
    I don't know what you mean by "rows of different sizes". In your SSCCE, they all look the same size to me. When you detect an invisible component you just ignore it. Basically you would just invoke the continue statement.
  • David
    David over 12 years
    The SSCCE does not show the complete problem. Originally, I had multiple rows which could differ in size. I gave up trying. I just stored everything in an Arraylist and then re-added all the removed rows at the end. I tried doing exactly as you instructed but I'm not skilled enough to achieve it. 1. was no problem, but with 2. I always get an ArrayIndexOutOfBounds exception because components cannot be ignored so easily in the for loop. Even though I override "nComponents", it still uses comp.getComponent(i), which is a problem. It needs to increment the loop or something similar to work.
  • camickr
    camickr over 12 years
    @David, the layout code was a little more complicated than I thought it would be at first glance. Here is the code for a left-to-right layout. I leave it up to you to implement the right-to-left layout.
  • Andrew Thompson
    Andrew Thompson over 12 years
    "a little more complicated than I thought it would be at first glance" Makes me think of this thread, which I opened with.. "I was offering advice on capturing an image of tabular data .. Turns out to be harder than I thought!" +1 for the example.