SWT 部分在为其 children 使用自定义行布局时保留了过多的垂直 space

SWT Section reserves too much vertical space when using a custom row layout for its children

我使用 GridLayout,在 Section 中有一列。网格中有三个"rows",背景为白色。我希望 Section 抓住水平 space 但 只需要尽可能多的垂直 space :

每行使用 自定义行布局 并且有两个 children。此外,该行布局有两种状态:

a) 在单行显示(如果总宽度足够大)

b) 在额外的一行显示第二个 child(红色)(如果 window 的总宽度对于两个 children 来说都太小)

对于这两种状态,我希望部分内容(蓝色)的总高度等于行高的总和(忽略间距)。

但是,该部分的总高度是状态 a) 是我对情况 b) 的预期高度。该部分似乎以某种方式保留了垂直 space 以便在调整宽度时不需要更改其高度。

如果 GridLayout 没有放在一个部分内,高度就可以了。如果我使用标准 RowLayout 而不是自定义行布局,则总高度也可以。多余的垂直 space 随着行数的增加而增加。

=> 我想我的自定义行布局有问题?

=> 或者我必须设置一个选项让 Section 不保留 space?

我的自定义布局 "somehow communicate" 应该是 parent 布局的第二种 height/hint 吗?由于白色区域的高度很好并且所有行都位于顶部,因此computeSize方法似乎没问题。

我不想 Section 保留 space。我希望它懒惰地/根据需要调整它的高度。

相关问题:

我的自定义布局:

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

}

用法示例:

    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();
    }

}

对于您正在尝试做的事情,我认为避免扩展 Layout 并使用现有的 Layout 实现是最安全(也是最简单)的。当看到您附加的两张图片时,第一个想到的是,当达到某个任意最小尺寸时,您正在布局中的一列和两列之间切换。

考虑到这一点,您可以改为使用 ControlListener(实际上是 ControlAdapter,因为我们只关心调整大小)并在达到所需的大小阈值时更新列数。例如:

public class RowControlListener extends ControlAdapter {

    // This can be computed from other values instead of being constant
    private static final int MIN_WIDTH = 200;

    private final Composite row;
    private final Section section;

    public RowControlListener(final Composite row, final Section section) {
        this.row = row;
        this.section = section;
    }

    @Override
    public void controlResized(final ControlEvent e) {
        final int width = row.getClientArea().width;
        final GridLayout rowLayout = (GridLayout) row.getLayout();
        final int numColumns = rowLayout.numColumns;
        final int updatedNumColumns = width < MIN_WIDTH ? 1 : 2;
        // Only do this if there's a change
        if (numColumns != updatedNumColumns) {
            rowLayout.numColumns = updatedNumColumns;
            section.layout(true, true);
        }
    }
}

现在我们有了一个侦听器,唯一的其他更改是添加侦听器,然后是一些小的外观更新:

public static void main(final String[] args) {
    // ... other setup ...
    final Composite row1 = createRow(parentComposite, "first");
    final Composite row2 = createRow(parentComposite, "second");
    final Composite row3 = createRow(parentComposite, "third");

    row1.addControlListener(new RowControlListener(row1, section));
    row2.addControlListener(new RowControlListener(row2, section));
    row3.addControlListener(new RowControlListener(row3, section));
    // ... other setup ...
}

LabelButton 的设置 GridData 进行了细微更改。然后,我们将 GridData 上的 widthHint 用于 Label,以便一切都对齐。在实际设置中,应该计算并传入(类似于您当前通过传入 80 所做的事情),以确保在所有情况下它都大于文本的长度。

public static Composite createRow(final Composite parent, final String text) {
    // ... other setup ...
    final Label label = new Label(row, SWT.NONE);
    final GridData labelGridData = new GridData(SWT.FILL, SWT.FILL, false,
            false);
    labelGridData.widthHint = 80;
    label.setLayoutData(labelGridData);
    label.setText(text);

    final Button checkBox = new Button(row, SWT.CHECK);
    checkBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
    checkBox.setBackground(new Color(null, 255, 0, 0));

    final GridLayout rowLayout = new GridLayout(2, false);
    rowLayout.marginHeight = 0;
    rowLayout.marginWidth = 0;
    row.setLayout(rowLayout);

    return row;
}

值得一提的是,我最初指定 GridLayout 有 2 列,因为我知道 Shell 足以容纳。实际上,如果你事先不知道 Shell 的大小,你可以做的是任意选择 1 或 2,但在添加侦听器后调用 section.layout(),以便适当地重新排列。

结果: