I use a GridLayout
with one column inside a Section
. There are three "rows" in the grid, having a white background. I would like the Section
to grab horizontal space but only as much vertical space as needed:
Each row uses a custom row layout and has two children. Furthermore, that row layout has two states:
a) show on a single line (if total width is large enough)
b) show second child (red) on an extra line (if total width of window is too small for both children)
For both states I would expect the total height of the Section content (blue) to equal the sum of the heights of the rows (neglecting spacing).
However, the total height of the Section is state a) is the height that I expect for case b). The section somehow seems to reserve vertical space so that it does not need to change its height when the width is adapted.
If the GridLayout
is not placed inside a section, the height is fine. If I use a standard RowLayout
instead of my custom row layout, the total height is also fine. The excess vertical space increases with the number of rows.
=> I guess that there is something wrong with my custom row layout?.
=> Or is there an option that I have to set for the Section
to not reserve space?
Should my custom layout "somehow communicate" a second type of height/hint for the parent layout? Since the height of the white region is fine and all rows are located at the top, the method computeSize seems to be fine.
I don't want the Section
to reserve space. I want it to adapt its height lazily / as required.
Related question:
SWT RowLayout with last element grabbing excess horizontal space?
My custom layout:
package org.treez.core.adaptable.composite;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Layout;
/**
* A custom row layout for exactly two children. If there is not enough space for the second child it is wrapped to a
* second line. The second child grabs excess horizontal space (which would not be possible with a normal RowLayout).
*/
public final class ExtendingRowLayout extends Layout {
//#region ATTRIBUTES
private int minWidthForFirstChild;
private int minWidthForSecondChild;
private int spacing = 5;
//#end region
//#region CONSTRUCTORS
public ExtendingRowLayout() {
this(80, 200);
}
public ExtendingRowLayout(int minWidthForFirstChild, int minWidthForSecondChild) {
this.minWidthForFirstChild = minWidthForFirstChild;
this.minWidthForSecondChild = minWidthForSecondChild;
}
//#end region
//#region METHODS
@Override
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
Point extent = layoutHorizontal(composite, false, wHint, flushCache);
return extent;
}
@Override
protected void layout(Composite composite, boolean flushCache) {
Rectangle clientArea = composite.getClientArea();
layoutHorizontal(composite, true, clientArea.width, flushCache);
}
@Override
protected boolean flushCache(Control control) {
return true;
}
@Override
public String toString() {
return getClass().getSimpleName();
}
private Point layoutHorizontal(
Composite composite,
boolean doPositionChildren,
int clientWidth,
boolean flushCache) {
Control[] children = composite.getChildren();
if (children.length != 2) {
String message = "There must be exactly two children and not " + children.length + ".";
throw new IllegalStateException(message);
}
int clientX = 0;
int clientY = 0;
if (doPositionChildren) {
Rectangle rect = composite.getClientArea();
clientX = rect.x;
clientY = rect.y;
}
Control firstChild = children[0];
Control secondChild = children[1];
Point firstSize = firstChild.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
Point secondSize = secondChild.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
int firstChildWidth = Math.max(firstSize.x, minWidthForFirstChild);
int correctedSpacing = spacing;
if (firstChildWidth == 0) {
correctedSpacing = 0;
}
int minForSecondChildWidth = Math.max(secondSize.x, minWidthForSecondChild);
int minForTotalWidthOfSingleLine = firstChildWidth + correctedSpacing + minForSecondChildWidth;
int maxHeight = Math.max(firstSize.y, secondSize.y);
int firstY = maxHeight / 2 - firstSize.y / 2;
if (doPositionChildren) {
firstChild.setBounds(clientX, clientY + firstY, firstChildWidth, firstSize.y);
}
boolean showOnSingleLine = minForTotalWidthOfSingleLine + spacing <= clientWidth;
if (showOnSingleLine) {
int x = clientX + firstChildWidth + correctedSpacing;
int y = clientY + maxHeight / 2 - secondSize.y / 2;
int width = clientWidth - firstChildWidth - correctedSpacing;
int height = secondSize.y;
if (doPositionChildren) {
secondChild.setBounds(x, y, width, height);
}
int totalWidth = Math.max(minForTotalWidthOfSingleLine, clientWidth);
return new Point(totalWidth, maxHeight);
} else {
int x = clientX;
int y = (int) (clientY + firstSize.y + 1.5 * spacing);
int width = Math.max(clientWidth, minWidthForSecondChild);
int height = secondSize.y;
if (doPositionChildren) {
secondChild.setBounds(x, y, width, height);
}
int totalHeight = (int) (firstSize.y + 1.5 * spacing + secondSize.y);
return new Point(width, totalHeight);
}
}
//#end region
}
Example usage:
package org.treez.core.adaptable.composite;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.Section;
public class ExtendingRowLayoutDemo {
public static void main(String[] args) {
Shell shell = createShell();
shell.setSize(500, 300);
Section section = createSection(shell);
Composite parentComposite = createParentComposite(section);
createRow(parentComposite, "first");
createRow(parentComposite, "second");
createRow(parentComposite, "third");
showUntilClosed(shell);
}
private static Shell createShell() {
Display display = new Display();
Shell shell = new Shell(display);
GridLayout shellGridLayout = new GridLayout(1, false);
shell.setLayout(shellGridLayout);
return shell;
}
private static Section createSection(Shell shell) {
Section section = new Section(
shell,
ExpandableComposite.TWISTIE | ExpandableComposite.EXPANDED | ExpandableComposite.TITLE_BAR);
GridData gridData = new GridData(SWT.FILL, SWT.NONE, true, false);
section.setLayoutData(gridData);
return section;
}
private static Composite createParentComposite(Section section) {
Composite parentComposite = new Composite(section, SWT.NONE);
section.setClient(parentComposite);
parentComposite.setBackground(new Color(null, 0, 0, 255));
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
parentComposite.setLayoutData(gridData);
GridLayout gridLayout = new GridLayout(1, false);
parentComposite.setLayout(gridLayout);
return parentComposite;
}
private static Composite createRow(Composite parent, String text) {
Composite row = new Composite(parent, SWT.NONE);
row.setBackground(new Color(null, 255, 255, 255));
GridData rowGridData = new GridData(SWT.FILL, SWT.FILL, true, false);
row.setLayoutData(rowGridData);
Label label = new Label(row, SWT.NONE);
label.setText(text);
Button checkBox = new Button(row, SWT.CHECK);
checkBox.setBackground(new Color(null, 255, 0, 0));
ExtendingRowLayout rowLayout = new ExtendingRowLayout();
row.setLayout(rowLayout);
//RowLayout standardRowLayout = new RowLayout();
//row.setLayout(standardRowLayout);
return row;
}
private static void showUntilClosed(Shell shell) {
shell.open();
Display display = Display.getCurrent();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
For what you're trying to do, I think it would be safest (and easiest) to avoid extending
Layout
, and instead use an existingLayout
implementation. The first thought that comes to mind when seeing the two images you've attached is that you're switching between having one and two columns in a layout when some arbitrary minimum size is reached.With that in mind, you could instead use a
ControlListener
(actuallyControlAdapter
because we only care about resizing) and update the number of columns when the desired size threshold is reached. For example:Now that we have a listener, the only other changes are to add the listener, and then some minor cosmetic updates:
Minor changes of setting
GridData
on theLabel
andButton
. We then use thewidthHint
on theGridData
for theLabel
so that everything lines up. In an actual setup, that should be computed and passed in (similar to what you're doing currently by passing in80
) so that it's guaranteed to be greater than the length of the text in all cases.It's worth mentioning that I've initially specified the
GridLayout
to have 2 columns because I knew that theShell
would be large enough to accommodate. In reality, if you don't know theShell
size in advance, what you can do is arbitrarily pick 1 or 2, but callsection.layout()
after the listeners are added, so that things are rearranged appropriately.The result: