I am trying to create custom layout which allows me to specify width of a component in percentage, and layout components based on that percentage widths. The following is the implementation I ended up with.
The problem I’ve is, one of the inner most panel’ calculated width is not sufficient to hold all of its components in one row, and the below implementation wraps them to next line, but the height of the parent [all container in the hierarchy] is fixed to some pixels [in my case I used 40px], and its not allowing the wrapped components to show.
Can you please suggest a way to fix it…
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.awt.Rectangle;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import javax.swing.BoxLayout;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
/**
* @author Rakesh.A
*
*/
public class Example extends JPanel {
public Example() {
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
for (int i = 0; i < 1; i++) {
JPanel row = new JPanel();
row.setLayout(new PercentageWidthLayout(5, 5));
JPanel column1 = new JPanel();
column1.setOpaque(true);
column1.setBackground(Color.white);
JPanel column2 = createColumn2();
row.add(column1, new MyConstraints(15, false)); // uses 15% of the available size
row.add(column2, new MyConstraints(50, false, true, true)); // uses 50% of the available size and wraps its contents
row.add(new JPanel(), new MyConstraints(25, false)); // uses 25% of the available size
add(row);
}
}
private JPanel createColumn2() {
JPanel column = new JPanel();
column.setOpaque(true);
column.setBackground(Color.green);
column.setLayout(new PercentageWidthLayout(3, 3, 35));
// total percentage is 100% for all the below components
column.add(new MyComponent(30, 28), new MyConstraints(20, true, false, true));
column.add(new MyComponent(30, 28), new MyConstraints(10, true, false, true));
column.add(new MyComponent(30, 28), new MyConstraints(20, true, false, true));
column.add(new MyComponent(30, 28), new MyConstraints(20, true, false, true));
column.add(new MyComponent(30, 28), new MyConstraints(10, true, false, true));
column.add(new MyComponent(30, 28), new MyConstraints(10, true, false, true));
column.add(new MyComponent(30, 28), new MyConstraints(10, true, false, true));
return column;
}
public static void main(final String[] args) {
JDialog dialog = new JDialog();
dialog.setSize(500, 150);
Example content = new Example();
JScrollPane scrl = new JScrollPane(content, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
dialog.getContentPane().add(scrl);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setVisible(true);
}
public static class MyComponent extends JPanel {
private Dimension minSize;
public MyComponent(final int minWidth, final int minHeight) {
minSize = new Dimension(minWidth, minHeight);
setOpaque(true);
setBackground(Color.yellow);
add(new JLabel("Block"));
}
@Override
public Dimension getPreferredSize() {
return minSize;
}
@Override
public Dimension getMaximumSize() {
return minSize;
}
@Override
public Dimension getMinimumSize() {
return minSize;
}
}
public static class PercentageWidthLayout implements LayoutManager2 {
private LinkedHashMap<Component, MyConstraints> components;
private final int leftMargin;
private final int topMargin;
private final int rowHeight;
// default size of the block
public static final Dimension minimumSize = new Dimension(10, 40);
public static final Dimension preferredSize = new Dimension(100, 40);
// default left margin between components
public static final int defaultLeftMargin = 5;
// default bottom margin between components
public static final int defaultTopMargin = 5;
// default row height
public static final int defaultRowHeight = 0;
public PercentageWidthLayout() {
this(defaultLeftMargin, defaultTopMargin);
}
public PercentageWidthLayout(final int leftMargin, final int topMargin) {
this(leftMargin, topMargin, defaultRowHeight);
}
public PercentageWidthLayout(final int leftMargin, final int topMargin, final int rowHeight) {
this.leftMargin = leftMargin;
this.topMargin = topMargin;
this.rowHeight = rowHeight;
components = new LinkedHashMap<Component, MyConstraints>();
}
@Override
public Dimension preferredLayoutSize(final Container parent) {
int maxX = 0;
int maxY = 0;
for (Entry<Component, MyConstraints> compEntry : components.entrySet()) {
Rectangle bounds = compEntry.getKey().getBounds();
maxX = Math.max(maxX, (int) bounds.getMaxX());
maxY = Math.max(maxY, (int) bounds.getMaxY());
}
if (maxX == 0 || maxY == 0) {
return preferredSize;
}
return new Dimension(maxX, maxY);
}
@Override
public Dimension minimumLayoutSize(final Container parent) {
return minimumSize;
}
@Override
public Dimension maximumLayoutSize(final Container target) {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
@Override
public void layoutContainer(final Container parent) {
synchronized (parent.getTreeLock()) {
// validate total percentage
validatePercentages();
// calculate available width & height for the components
Insets insets = parent.getInsets();
// available width after removing border space
int maxClientWidth = parent.getWidth() - insets.left - insets.right;
// calculated available width for the components
int clientWidth = maxClientWidth - (parent.getComponents().length * leftMargin);
// calculated available height for the components
int clientHeight = ((rowHeight > 0) ? rowHeight : preferredSize.height) - insets.top - insets.bottom - topMargin * 2;
// layout the components
int x = insets.left + leftMargin;
int y = insets.top + topMargin;
if (clientWidth > 0 && clientHeight > 0) {
for (Component component : parent.getComponents()) {
// get the constraints to be applied
MyConstraints constraints = components.get(component);
// calculate component width according to the given percentage
int componentWidth = clientWidth * constraints.percentage / 100;
// calculate the preferred size of the component
int prefW = component.getPreferredSize().width;
if (constraints.usePreferredSize && componentWidth < prefW) {
// configured to use preferred size if calculated size is less than the
// preferred size
componentWidth = prefW;
}
// calculate the minimum size of the component
int minW = component.getMinimumSize().width;
if (constraints.useMinWidth && componentWidth < minW) {
// configured to use minimum width if calculated size is less than the
// minimum size
componentWidth = minW;
}
// check and wrap component to next row if needed
if (constraints.wrapComponents && x + componentWidth > parent.getWidth()) {
x = insets.left + leftMargin;
y += clientHeight + insets.top;
// update height of the parent component if it doesn fit
// if (parent.getHeight() < y + clientHeight) {
// parent.setSize(parent.getWidth(), parent.getHeight() + rowHeight);
// }
}
component.setBounds(x, y, componentWidth, clientHeight);
// update x coordinate
x += componentWidth + leftMargin;
}
}
}
}
@Override
public void addLayoutComponent(final String name, final Component comp) {
}
@Override
public void removeLayoutComponent(final Component comp) {
components.remove(comp); // remove component from map
}
@Override
public void addLayoutComponent(final Component comp, final Object constraints) {
if (constraints == null || !(constraints instanceof MyConstraints)) {
throw new IllegalArgumentException("Invalid constraints object! - " + constraints);
}
MyConstraints myConstraints = (MyConstraints) constraints;
if (myConstraints.percentage > 100) {
throw new IllegalArgumentException("Invalid percentage value [" + myConstraints.percentage + "]!");
}
components.put(comp, myConstraints);
}
@Override
public float getLayoutAlignmentX(final Container target) {
return 0;
}
@Override
public float getLayoutAlignmentY(final Container target) {
return 0;
}
@Override
public void invalidateLayout(final Container target) {
}
public int getLeftMargin() {
return leftMargin;
}
public int getTopMargin() {
return topMargin;
}
public int getRowHeight() {
return rowHeight;
}
public static Integer calculatePercentage(final float value, final int total) {
return new Integer((int) (value / total * 100));
}
private void validatePercentages() {
int total = 0;
for (Entry<Component, MyConstraints> compEntry : components.entrySet()) {
total += compEntry.getValue().percentage;
}
if (total > 100) {
throw new IllegalArgumentException("Total percentage [" + total + "] of the components in the layout is more than 100!");
}
}
}
/**
* @author Rakesh.A
*
*/
public static class MyConstraints {
public final int percentage;
public final boolean usePreferredSize, useMinWidth, wrapComponents;
public MyConstraints(final int percentage, final boolean usePreferredSize) {
this(percentage, usePreferredSize, false);
}
public MyConstraints(final int percentage, final boolean usePreferredSize, final boolean useMinWidth) {
this(percentage, usePreferredSize, useMinWidth, false);
}
public MyConstraints(final int percentage, final boolean usePreferredSize, final boolean useMinWidth, final boolean wrapComponents) {
this.percentage = percentage;
this.usePreferredSize = usePreferredSize;
this.useMinWidth = useMinWidth;
this.wrapComponents = wrapComponents;
}
}
}
added to this, the root panel is added to a JScrollPane and it also needs to be updated.
Thank you all for your inputs. I opted a workaround for this problem, instead of wrapping the components, I added a horizontal scroll bar 🙂