JPanels are not fully stretched to take up available space

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 the sub-panel, where I place all the mini-panels (see code below). It really works and behaves correctly, increasing the size and that's it. However, as the number of mini-panels increases, a strange behavior occurs: an empty space appears at the end of the parent panel.

enter image description here

I think I discovered that this is a problem with shooting in layout managers, because in order to stretch the panels, the layout manager tries to add one pixel to each mini-panel. However, when the number of mini-panels is large, adding one pixel to each of them will add many pixels and go beyond the size of the parent. Thus, the layout manager ends up not adding any pixels to any mini-panel, which leads to empty space.

Here is my SSCCE: (try starting and stretching 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 to avoid this behavior? I want my panels to occupy the entire container.

EDIT: Mini-panels are designed to have (after it is allowed) a listener mouse. Thus, paint solutions, unfortunately, can be avoided.

+6
source share
3 answers

Layout problems are resolved with ... LayoutManagers :-) If you donโ€™t do what you want, do the required behavior.

So, if the BoxLayout core just ignores the pixels due to rounding errors, the subclass allows you to distribute these pixels as needed. Very rough 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); } } } } 
+3
source

Two options that I can think of:

  • Have a custom component that has a paintComponent(...) method that paints all regions, and when you resize the window, it will redraw the areas when scaling in accordance with the new width information.

  • Color the colored areas once on the image, and then draw it, and when the window is resized, zoom in to fit it.

+2
source

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

+1
source

Source: https://habr.com/ru/post/908047/


All Articles