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 ...
}
对 Label
和 Button
的设置 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()
,以便适当地重新排列。
结果:
我使用 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 ...
}
对 Label
和 Button
的设置 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()
,以便适当地重新排列。
结果: