Question

I have a panel where I place several mini-panels, side-by-side, with different sizes and colors, and they should occupy the entire parent panel (horizontally).

For this I use BorderLayout (for the parent panel), and BoxLayout for a sub-panel where I place all the mini-panels (see code below). It does work and behave correctly uppon resizing and everything. However, as the number of mini-panels becomes larger, a strange behaviour occurs: empty space appears at the end of the parent panel.

enter image description here

I think I found that this is a streching bug in the layout managers, because in order to strech the panels, the layout manager tries to add a single pixel to each mini-panel. However, when the number of mini-panels is large, adding a single pixel to every one will result in adding many pixels and going beyond the size of the parent. Thus, the layout manager ends up not adding any pixels to any mini-panel, resulting in the empty space.

Here is my SSCCE: (try running, and streching the window, to understand the problem)

package com.myPackage;

import java.awt.*;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class ColoredPanels extends JPanel
{
    /* Content information. */
    private Vector<Integer> partitions;
    private Vector<Color> colors;

    /* Panel where the content panels will go. */
    private JPanel contentHolder;

    private final int defaultHeight = 20;

    public ColoredPanels(Vector<Integer> partitions, Vector<Color> colors)
    {
        assert partitions != null;
        assert !partitions.isEmpty();
        assert colors != null;
        assert !colors.isEmpty();
        assert colors.size() == partitions.size();

        this.partitions = partitions;
        this.colors = colors;

        /* Set layout manager. */
        setLayout(new BorderLayout());

        /* Create the content holder. */
        contentHolder = new JPanel();
        contentHolder.setLayout(new BoxLayout(contentHolder, BoxLayout.X_AXIS));
        this.add(contentHolder, BorderLayout.NORTH);

        /* Fill content holder with colored panels. */
        createPanels();
    }

    private void createPanels()
    {
        assert partitions != null;
        assert !partitions.isEmpty();
        assert colors != null;
        assert !colors.isEmpty();
        assert colors.size() == partitions.size();

        for (int i = 0; i < partitions.size(); i++)
        {
            JPanel newPanel = new JPanel();
            newPanel.setBackground(colors.get(i));
            newPanel.setPreferredSize(new Dimension(partitions.get(i), defaultHeight));
            newPanel.setMinimumSize(new Dimension(1, defaultHeight));
            contentHolder.add(newPanel);
        }
    }

    public static void main(String[] in)
    {
        Vector<Integer> sizes = new Vector<Integer>();
        Vector<Color> cols = new Vector<Color>();

        /* Make 100 random sizes, and use two colors. */
        for (int i = 0; i < 100; i++)
        {
            int size = (int)Math.round(1 + Math.random() * 10);
            sizes.add(size);
            cols.add((i%2 == 0)? Color.red : Color.green);
        }

        ColoredPanels panels = new ColoredPanels(sizes, cols);
        panels.setBorder(BorderFactory.createLineBorder(Color.yellow, 1));

        JFrame newFrame = new JFrame();
        newFrame.getContentPane().add(panels);
        newFrame.pack();
        newFrame.setVisible(true);
    }
}

How do I avoid this behaviour? I want my panels to occupy the whole container.

EDIT: The mini-panels are intended to have (once this is resolved) mouse listeners. Thus, painting solutions are unfortunatelly avoidable.

Was it helpful?

Solution

Layout problems are solved by ... LayoutManagers :-) If one doesn't what you want it to do, implement the behaviour you want.

So if the core BoxLayout simply ignores pixels due to rounding errors, subclass and make it distribute those pixels as needed. A very raw quick example:

public static class XBoxLayout extends BoxLayout {

    enum Strategy {
        NONE,
        STRETCH_LAST,
        DISTRUBUTE
    }

    private Strategy strategy;

    public XBoxLayout(Container target, int axis, Strategy strategy) {
        super(target, axis);
        this.strategy = strategy;
    }


    @Override
    public void layoutContainer(Container target) {
        super.layoutContainer(target);
        if (Strategy.NONE == strategy) return;
        Insets targetInsets = target.getInsets();
        int targetSize = target.getWidth() - targetInsets.left - targetInsets.right;
        int childSum = 0;
        for (Component child : target.getComponents()) {
            childSum += child.getWidth();
        }
        if (targetSize > childSum) {
            int excess = targetSize - childSum;
            distribute(target, excess);
        }
    }


    private void distribute(Container target, int excess) {
        System.out.println("childCount/rounding excess " + target.getComponentCount() + "/" + excess);
        if (Strategy.STRETCH_LAST == strategy) {
            Component lastChild = target.getComponent(target
                    .getComponentCount() - 1);
            lastChild.setSize(lastChild.getWidth() + excess,
                    lastChild.getHeight());
        } else {
            int firstToDistribute = target.getComponentCount() - excess;
            int summedOffset = 0;
            for(int index = firstToDistribute; index < target.getComponentCount(); index++) {
                Component child = target.getComponent(index);
                Rectangle bounds = child.getBounds();
                bounds.x += summedOffset++;
                bounds.width += 1;
                child.setBounds(bounds);
            }
        }
    }

}

OTHER TIPS

Two options I can think of:

  1. Have a custom component that has a paintComponent(...) method that paints all the regions, and when the window is resized it would repaint the regions whilst scaling them according to the new width information.

  2. Paint the coloured regions once to an image, and then paint that, and when the window is resized rescale the image to fit.

I once ran into this problem when using GridLayout for a chessboard. My solution was to extend GridLayout and calculate the layout with floating point precision.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top