java 为什么 SWT Composite 有时需要调用 resize() 才能正确布局?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/586414/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 12:58:08  来源:igfitidea点击:

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

javaswt

提问by Jared

Sometimes we encounter an SWT composite that absolutely refuses to lay itself out correctly. Often we encounter this when we have called dispose on a composite, and then replaced it with another; although it does not seem to be strictly limited to this case.

有时我们会遇到绝对拒绝正确布局的 SWT 组合。当我们在一个组合上调用 dispose,然后用另一个替换它时,我们经常会遇到这种情况;虽然它似乎并不严格限于这种情况。

When we run into this problem, about 50 % of the time, we can call pack()and layout()on the offending composite, and all will be well. About 50 % of the time, though, we have to do this:

当我们遇到这个问题时,大约有 50% 的时间,我们可以调用pack()和调用有layout()问题的组合,一切都会好起来的。但是,大约 50% 的时间,我们必须这样做:

Point p = c.getSize();
c.setSize(p.x+1, p.y+1);
c.setSize(p);

We've had this happen with just about every combination of layout managers and such.

几乎所有布局管理器等组合都发生过这种情况。

I wish I had a nice, simple, reproducible case, but I don't. I'm hoping that someone will recognize this problem and say: "Well, duh, you're missing xyz...."

我希望我有一个漂亮、简单、可重复的案例,但我没有。我希望有人会认识到这个问题并说:“好吧,呃,你错过了 xyz......”

回答by Peter Walser

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:

SWT 中的布局支持缓存,并且通常会缓存控件的首选大小,或者他们喜欢缓存的任何内容:

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 otherlayout methods that will also cause the layout to flush its cache:

我对 SWT 编程(前 Swing 程序员)比较陌生,但遇到了布局未正确更新的类似情况。我通常能够使用其他布局方法来解决它们,这些方法也会导致布局刷新其缓存:

layout(boolean changed)

layout(boolean changed, boolean allChildren)

回答by Peter Walser

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.

与此同时,在运行时更改或调整控件层次结构的部分时,我对 SWT 的缺点有了更多了解。ScrolledComposites 和ExpandBars 还需要在应该适应其最小或首选内容大小时显式更新。

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

回答by Boris Bokowski

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

复合的布局负责布置该复合的子代。因此,如果复合的大小没有改变,但需要更新子项的相对位置和大小,则调用layout()复合。但是,如果复合本身的大小或位置需要更新,则必须调用layout()其父复合(依此类推,直到到达外壳)。

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.

经验法则:如果您添加或删除了一个控件,或者做了一些需要重新布局的事情,请沿着小部件层次结构向上移动,直到找到带有滚动条的组合并调用layout()它。停止在带有滚动条的组合处的原因是它的大小不会随着变化而变化——它的滚动条会“吸收”它。

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.

请注意,如果需要布局的更改不是新子项或已删除子项,则应Composite.changed(new Control[] {changedControl})在调用布局之前调用。

回答by stippi

I have just become aware of Composite.changed(Control[] children). There is an extensive article which I read a couple years ago:

我刚刚意识到 Composite.changed(Control[] children)。几年前我读过一篇广泛的文章:

http://www.eclipse.org/articles/article.php?file=Article-Understanding-Layouts/index.html

http://www.eclipse.org/articles/article.php?file=Article-Understanding-Layouts/index.html

This article also mentions to call Composite.layout(boolean changed, boolean all) to update the layout: "Calling layout() is the same as calling layout(true) which tells the ColumnLayout to flush its caches before setting the bounds of the children." That is all correct and what I have been doing ever since. But it is not what one wants, since it basically defeats the benefit of the layout cache when you want to update the layout because one or a few controls have changed requirements.

这篇文章还提到调用 Composite.layout(boolean changed, boolean all) 来更新布局:“调用 layout() 与调用 layout(true) 相同,它告诉 ColumnLayout 在设置孩子的边界之前刷新其缓存.” 那都是正确的,从那以后我就一直在做。但这不是人们想要的,因为当您想要更新布局时,它基本上会破坏布局缓存的好处,因为一个或几个控件已经改变了需求。

Imagine you have a bunch of StyledText widgets in a GridLayout and you need to change the size of one of them. Calling computeSize() on StyledText is very expensive. Instead of this:

想象一下,您在 GridLayout 中有一堆 StyledText 小部件,并且您需要更改其中一个的大小。在 StyledText 上调用 computeSize() 非常昂贵。取而代之的是:

Wrong:

错误的:

parent.layout(true);

... which calls computeSize() on all children, even though their requirements have not changed. You should do this:

... 对所有孩子调用 computeSize(),即使他们的要求没有改变。你应该做这个:

Right:

对:

parent.changed(new Control[] { theChangedChild });

Then either

那么要么

rootComposite.layout(false, true);

or

或者

parent.layout(false);

Not very intuitive. The parameter to layout() is just badly named. Instead of calling it "changed" it should have been called "ignoreCache" or something. The intuitive thing is to pass "true" when something changed. Instead you need to pass "false", but invalidate the cache for just the changed Control with changed() before you do...

不是很直观。layout() 的参数命名不当。与其称其为“已更改”,不如将其称为“ignoreCache”或其他名称。直观的事情是在发生变化时传递“true”。相反,您需要传递“false”,但在执行之前使用 changed() 使更改后的 Control 的缓存无效...

Note that calling changed() will also recursively invalidate the cache for just the parent Control in its own parent, which totally makes sense. So when you call layout(), you should call it on the root composite (most often the Shell) with all=true, unless you know that the size of the parent of the changed control will or can not change in response.

请注意,调用 changed() 还将递归地使父控件的缓存在其自己的父控件中无效,这是完全有道理的。因此,当您调用 layout() 时,您应该在根组合(最常见的是 Shell)上使用 all=true 调用它,除非您知道已更改控件的父控件的大小将或不能作为响应而更改。