SWT 部分在为其子项使用自定义行布局时保留了过多的垂直空间 [英] SWT Section reserves too much vertical space when using a custom row layout for its children

查看:14
本文介绍了SWT 部分在为其子项使用自定义行布局时保留了过多的垂直空间的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用 GridLayout,在 Section 内有一列.网格中有三个行",具有白色背景.我希望 Section 占据水平空间,但 只需要尽可能多的垂直空间:

每一行都使用一个自定义行布局并有两个孩子.此外,该行布局有两种状态:

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

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

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

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

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

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

=> 或者有没有我必须为 Section 设置不保留空间的选项?

我的自定义布局是否应该以某种方式传达"父布局的第二种高度/提示?由于白色区域的高度很好,并且所有行都位于顶部,因此 computeSize 方法似乎很好.

我不希望 Section 保留空间.我希望它根据需要懒惰地/适应它的高度.

相关问题:

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 existing Layout 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 (actually ControlAdapter because we only care about resizing) and update the number of columns when the desired size threshold is reached. For example:

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

Now that we have a listener, the only other changes are to add the listener, and then some minor cosmetic updates:

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 ...
}

Minor changes of setting GridData on the Label and Button. We then use the widthHint on the GridData for the Label 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 in 80) so that it's guaranteed to be greater than the length of the text in all cases.

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

It's worth mentioning that I've initially specified the GridLayout to have 2 columns because I knew that the Shell would be large enough to accommodate. In reality, if you don't know the Shell size in advance, what you can do is arbitrarily pick 1 or 2, but call section.layout() after the listeners are added, so that things are rearranged appropriately.

The result:

这篇关于SWT 部分在为其子项使用自定义行布局时保留了过多的垂直空间的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆