Why does an SWT Composite sometimes require a call to resize() to layout correctly?

Looks to me like the layout's cache is outdated and needs to be refreshed.

Layouts in SWT support caches, and will usually cache preferred sizes of the Controls, or whatever they like to cache:

public abstract class Layout {
    protected abstract Point computeSize (Composite composite, int wHint, int hHint, boolean flushCache);
    protected boolean flushCache (Control control) {...}
    protected abstract void layout (Composite composite, boolean flushCache);
}

I'm relatively new to SWT programming (former Swing programmer), but encountered similar situations in which the layout wasn't properly updated. I was usually able to resolve them using the other layout methods that will also cause the layout to flush its cache:

layout(boolean changed)

layout(boolean changed, boolean allChildren)

In the meantime I learned a little more about SWT's shortcomings when changing or resizing parts of the control hierarchy at runtime. ScrolledComposites and ExpandBars need also to be updated explicitly when the should adapt their minimal or preferred content sizes.

I wrote a little helper method that revalidates the layout of a control hierarchy for a control that has changed:

public static void revalidateLayout (Control control) {

    Control c = control;
    do {
        if (c instanceof ExpandBar) {
            ExpandBar expandBar = (ExpandBar) c;
            for (ExpandItem expandItem : expandBar.getItems()) {
                expandItem
                    .setHeight(expandItem.getControl().computeSize(expandBar.getSize().x, SWT.DEFAULT, true).y);
            }
        }
        c = c.getParent();

    } while (c != null && c.getParent() != null && !(c instanceof ScrolledComposite));

    if (c instanceof ScrolledComposite) {
        ScrolledComposite scrolledComposite = (ScrolledComposite) c;
        if (scrolledComposite.getExpandHorizontal() || scrolledComposite.getExpandVertical()) {
            scrolledComposite
                .setMinSize(scrolledComposite.getContent().computeSize(SWT.DEFAULT, SWT.DEFAULT, true));
        } else {
            scrolledComposite.getContent().pack(true);
        }
    }
    if (c instanceof Composite) {
        Composite composite = (Composite) c;
        composite.layout(true, true);
    }
}

A composite's layout is responsible for laying out the children of that composite. So if the composite's size does not change, but the relative positions and sizes of the children need to be updated, you call layout() on the composite. If, however, the size or position of the composite itself needs to be updated, you will have to call layout() on its parent composite (and so on, until you reach the shell).

A rule of thumb: If you have added or removed a control, or otherwise done something that requires a relayout, walk up the widget hierarchy until you find a composite with scrollbars and call layout() on it. The reason for stopping at the composite with scrollbars is that its size will not change in response to the change - its scrollbars will "absorb" that.

Note that if the change requiring a layout is not a new child, or a removed child, you should call Composite.changed(new Control[] {changedControl}) before calling layout.

Tags:

Java

Swt