blob: a6be422552e065fe21d98fbddedf15b9a7f87eb4 [file] [log] [blame]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=windows-1252">
<title>Understanding Layouts in SWT</title>
<link rel="stylesheet" href="../article.css" type="text/css" />
</head>
<body>
<h1>Understanding Layouts in SWT</h1>
<div class="summary">
<h2>Summary</h2>
<p>When writing applications in SWT, you may need to use <i>layouts</i>
to give your windows a specific look. A layout controls the position and
size of children in a <code>Composite</code>. Layout classes are
subclasses of the abstract class <code>Layout</code>. This article shows
you how to work with standard layouts, and write your own custom layout
class.</p>
<div class="author">By Carolyn MacLeod, OTI</div>
<div class="copyright">Copyright &copy; 2001, 2002 Object
Technology International, Inc.</div>
<div class="date">March 22, 2001</div>
<div class="author">Revised by Shantha Ramachandran, OTI</div>
<div class="date">May 02, 2002</div>
<div class="author">Revised by Wayne Beaton, The Eclipse Foundation</div>
<div class="copyright">Copyright &copy; 2008 The Eclipse Foundation, Inc.</div>
<div class="date">May 30, 2008</div>
<div class="author">Revised by Wayne Beaton, The Eclipse Foundation</div>
<div class="copyright">Copyright &copy; 2008 The Eclipse Foundation, Inc.</div>
<div class="date">May 13, 2009</div>
</div>
<div class="content">
<h2>Overview</h2>
<p>When writing applications in the <a
href="http://www.eclipse.org/swt">Standard Widget Toolkit</a> (SWT),
you may need to use <i>layouts</i> to give your windows a specific look.
A layout controls the position and size of children in a <code>Composite</code>.
Layout classes are subclasses of the abstract class <code>Layout</code>.
SWT provides several standard layout classes, and you can write custom
layout classes.</p>
<p>In SWT, positioning and sizing does not happen automatically.
Applications can decide to size and place a <code>Composite</code>'s
children initially, or in a resize listener. Another option is to
specify a layout class to position and size the children. If children
are not given a size, they will have zero size and they cannot be seen.</p>
<p>The diagram below illustrates a few general terms that are used
when discussing layouts. The <code>Composite</code> (in this case, a <code>TabFolder</code>)
has a <i>location</i>, <i>clientArea</i> and <i>trim</i>. The size of
the <code>Composite</code> is the size of the <i>clientArea</i> plus the
size of the <code>trim</code>. This <code>Composite</code> has two
children that are laid out side by side. A <code>Layout</code> is
managing the size and position of the children. This <code>Layout</code>
allows <code>spacing</code> between the children, and a <i>margin</i>
between the children and the edges of the <code>Layout</code>. The size
of the <code>Layout</code> is the same as the size of the <code>Composite</code>'s
<i>clientArea</i>.</p>
<img src="images/GeneralTerms.jpg" />
<p>The <i>preferred size</i> of a widget is the minimum size needed
to show its content. In the case of a <code>Composite</code>, the
preferred size is the smallest rectangle that contains all of its
children. If children have been positioned by the application, the <code>Composite</code>
computes its own preferred size based on the size and position of the
children. If a <code>Composite</code> is using a layout class to
position its children, it asks the <code>Layout</code> to compute the
size of its <code>clientArea</code>, and then it adds in the <code>trim</code>
to determine its preferred size.</p>
<h3>Standard Layouts</h3>
<p>The standard layout classes in the SWT library are:</p>
<ul>
<li><code>FillLayout</code> lays out equal-sized widgets in a
single row or column</li>
<li><code>RowLayout</code> lays out widgets in a row or rows, with
fill, wrap, and spacing options</li>
<li><code>GridLayout</code> lays out widgets in a grid</li>
<li><code>FormLayout</code> lays out widgets by creating
attachments for each of their sides</li>
</ul>
<p>To use the standard layouts, you need to import the SWT layout
package:</p>
<pre>import org.eclipse.swt.layout.*;</pre>
<p>Layouts are pluggable. To set a <code>Composite</code> widget's
layout, you use the widget's <code>setLayout(Layout)</code> method. In
the following code, a <code>Shell</code> (a subclass of <code>Composite</code>)
is told to position its children using a <code>RowLayout</code>:</p>
<pre>Shell shell = new Shell();
shell.setLayout(new RowLayout());</pre>
<p>A layout class may have a corresponding layout data class: a
subclass of <code>Object</code> that contains layout data for a specific
child. By convention, layout data classes are identified by substituting
&quot;Data&quot; for &quot;Layout&quot; in the class name. For example,
the standard layout class <code>RowLayout</code> has a layout data class
called <code>RowData</code>, the layout class <code>GridLayout</code>
uses a layout data class called <code>GridData</code>, and the layout
class <code>FormLayout</code> has a layout data class called <code>FormData</code>.
A widget's layout data class is set as follows:</p>
<pre>Button button = new Button(shell, SWT.PUSH);
button.setLayoutData(new RowData(50, 40));</pre>
<h3>Examples in this Document</h3>
<p>Most of the snapshots in this document were taken by running
variations on the following example code. We may change the type of
layout, the options used, or the type or number of children.</p>
<pre>import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class LayoutExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
// Create the layout.
RowLayout layout = new RowLayout();
// Optionally set layout fields.
layout.wrap = true;
// Set the layout into the composite.
shell.setLayout(layout);
// Create the children of the composite.
new Button(shell, SWT.PUSH).setText("B1");
new Button(shell, SWT.PUSH).setText("Wide Button 2");
new Button(shell, SWT.PUSH).setText("Button 3");
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
}
}</pre>
<p>Running the above code results in the following:</p>
<img src="images/LayoutExample.png" />
<p>If the user resizes the shell so that there is no longer room for
Button 3 on the right, the <code>RowLayout</code> wraps Button 3 to the
next row, as follows:</p>
<img src="images/LayoutExampleResized.png" />
<p>Using layouts is closely tied with resize, as we shall see.
Consequently, most of the examples in this document show what would
happen if the <code>Composite</code> becomes smaller or larger, in order
to illustrate how the <code>Layout</code> works.</p>
<h2>FillLayout</h2>
<p><code>FillLayout</code> is the simplest layout class. It lays out
widgets in a single row or column, forcing them to be the same size.
Initially, the widgets will all be as tall as the tallest widget, and as
wide as the widest. <code>FillLayout</code> does not wrap, and you
cannot specify margins or spacing. You might use it to lay out buttons
in a task bar or tool bar, or to stack checkboxes in a <code>Group</code>.
<code>FillLayout</code> can also be used when a <code>Composite</code>
only has one child. For example, if a <code>Shell</code> has a single <code>Group</code>
child, <code>FillLayout</code> will cause the <code>Group</code> to
completely fill the <code>Shell</code>.</p>
<p>Here is the relevant portion of the example code. First we create
a <code>FillLayout</code>, then (if we want vertical) we set its <code>type</code>
field to <code>SWT.VERTICAL</code>, and set it into the <code>Composite</code>
(a <code>Shell</code>). The <code>Shell</code> has three push button
children, &quot;B1&quot;, &quot;Wide Button 2&quot;, and &quot;Button
3&quot;. Note that in a <code>FillLayout</code>, children are always the
same size, and they fill all available space.</p>
<pre>FillLayout fillLayout = new FillLayout();
fillLayout.type = SWT.VERTICAL;
shell.setLayout(fillLayout);
new Button(shell, SWT.PUSH).setText(&quot;B1&quot;);
new Button(shell, SWT.PUSH).setText(&quot;Wide Button 2&quot;);
new Button(shell, SWT.PUSH).setText(&quot;Button 3&quot;);</pre>
<p>The following table shows the differences between a horizontal
and vertical <code>FillLayout</code>, initially and after the parent has
grown.</p>
<table>
<tr>
<td>&nbsp;</td>
<td>
<p>Initial</p>
</td>
<td>
<p>After resize</p>
</td>
</tr>
<tr>
<td><code>fillLayout.type = SWT.HORIZONTAL</code>
<p>(default)</p>
</td>
<td><img src="images/FillLayoutSampleHorizontal.png" /></td>
<td><img src="images/FillLayoutSampleHorizontalResized.png" /></td>
</tr>
<tr>
<td><code>fillLayout.type = SWT.VERTICAL</code></td>
<td><img src="images/FillLayoutSampleVertical.png" /></td>
<td><img src="images/FillLayoutSampleVerticalResized.png" /></td>
</tr>
</table>
<h2>RowLayout</h2>
<p><code>RowLayout</code> is more commonly used than <code>FillLayout</code>
because of its ability to wrap, and because it provides configurable
margins and spacing. <code>RowLayout</code> has a number of
configuration fields. In addition, the height and width of each widget
in a <code>RowLayout</code> can be specified by setting the widget's <code>RowData</code>
object using <code>setLayoutData</code>.</p>
<h3>RowLayoutConfiguration Fields</h3>
<p>The <code>type</code> field controls whether the <code>RowLayout</code>
lays out widgets in horizontal rows, or vertical columns. <code>RowLayouts</code>
are horizontal by default.</p>
<p>The <code>wrap</code> field controls whether or not the <code>RowLayout</code>
will wrap widgets into the next row if there isn't enough space in the
current row. <code>RowLayouts</code> wrap by default.</p>
<p>If the <code>pack</code> field is true, widgets in a <code>RowLayout</code>
will take their natural size (&quot;natural size&quot; varies by widget;
the natural size for a label or push button, for example, is large enough to
display its textual contents), and they will be aligned as far to the
left as possible. If pack is false, widgets will fill the available
space, similar to the widgets in a <code>FillLayout</code>. <code>RowLayouts</code>
pack by default.</p>
<p>If the <code>justify</code> field is true, widgets in a <code>RowLayout</code>
are spread across the available space from left to right. If the parent
<code>Composite</code> grows wider, the extra space is distributed
evenly among the widgets. If both <code>pack</code> and <code>justify</code>
are true, widgets take their natural size, and the extra space is placed
between the widgets in order to keep them fully justified. By default, <code>RowLayouts</code>
do not justify.</p>
<p>The <code>marginLeft</code>, <code>marginTop</code>, <code>marginRight</code>,
<code>marginBottom</code> and <code>spacing</code> fields control the
number of pixels between widgets (<code>spacing</code>) and the number
of pixels between a widget and the side of the parent <code>Composite</code>
(margin). By default, <code>RowLayouts</code> leave 3 pixels for margins
and spacing. The margin and spacing fields are shown in the following
diagram.</p>
<img src="images/RowLayoutMargins.png" />
<h3>RowLayout Examples</h3>
<p>The following example code creates a <code>RowLayout</code>, sets
all of its fields to non-default values, and then sets it into a <code>Shell</code>.</p>
<pre>RowLayout rowLayout = new RowLayout();
rowLayout.wrap = false;
rowLayout.pack = false;
rowLayout.justify = true;
rowLayout.type = SWT.VERTICAL;
rowLayout.marginLeft = 5;
rowLayout.marginTop = 5;
rowLayout.marginRight = 5;
rowLayout.marginBottom = 5;
rowLayout.spacing = 0;
shell.setLayout(rowLayout);</pre>
<p>If you are using the default field values, you only need one line
of code:</p>
<pre>shell.setLayout(new RowLayout());</pre>
<p>The results of setting specific fields is shown below:</p>
<table>
<tr>
<td width="150">&nbsp;</td>
<td>
<p>Initial</p>
</td>
<td>
<p>After resize</p>
</td>
</tr>
<tr>
<td><pre>rowLayout.wrap = true;
rowLayout.pack = true;
rowLayout.justify = false;
rowLayout.type = SWT.HORIZONTAL;</pre>
<p>(defaults)</p>
</td>
<td><img src="images/RowLayoutSample01.png" /></td>
<td><img src="images/RowLayoutSample01Resized.png" /><p>and</p><img src="images/RowLayoutSample01ResizedNarrower.png" /></td>
</tr>
<tr>
<td><pre>wrap = false</pre>
<p>(clips if not enough space)</p>
</td>
<td><img src="images/RowLayoutSample02.png" /></td>
<td><img src="images/RowLayoutSample02Resized.png" /></td>
</tr>
<tr>
<td><pre>pack = false</pre>
<p>(all widgets are the same size)</p>
</td>
<td><img src="images/RowLayoutSample03.png" /></td>
<td><img src="images/RowLayoutSample03Resized.png" /></td>
</tr>
<tr>
<td><pre>justify = true</pre>
<p>(widgets are spread across the available space)</p>
</td>
<td><img src="images/RowLayoutSample04.png" /></td>
<td><img src="images/RowLayoutSample04Resized.png" /></td>
</tr>
<tr>
<td><pre>type = SWT.VERTICAL</pre>
<p>(widgets are arranged vertically in columns)</p>
</td>
<td><img src="images/RowLayoutSample05.png" /></td>
<td><img src="images/RowLayoutSample05Resized.png" /></td>
</tr>
</table>
<h3>Using RowData Objects with RowLayout</h3>
<p>Each widget controlled by a <code>RowLayout</code> can have its
initial width and height specified by setting its <code>RowData</code>
object. The following code uses <code>RowData</code> objects to change
the initial size of the <code>Buttons</code> in a <code>Shell</code>.</p>
<pre>package org.eclipse.articles.layouts.samples;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class RowDataExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new RowLayout());
Button button1 = new Button(shell, SWT.PUSH);
button1.setText("Button 1");
button1.setLayoutData(new RowData(50, 40));
Button button2 = new Button(shell, SWT.PUSH);
button2.setText("Button 2");
button2.setLayoutData(new RowData(50, 30));
Button button3 = new Button(shell, SWT.PUSH);
button3.setText("Button 3");
button3.setLayoutData(new RowData(50, 20));
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
}
}</pre>
<p>Here is what you see when you run this code.</p>
<img src="images/RowDataExample.png" />
<h2>GridLayout</h2>
<p>With a <code>GridLayout</code>,
the widget children of a <code>Composite</code> are laid out in a grid.
<code>GridLayout</code> has a number of configuration fields, and&mdash;like
<code>RowLayout</code>&mdash;the widgets it lays out can have an associated
layout data object, called <code>GridData</code>. The power of <code>GridLayout</code>
lies in the ability to configure <code>GridData</code> for each widget
controlled by the <code>GridLayout</code>.</p>
<h3>GridLayout Configuration Fields</h3>
<p>The <code>numColumns</code> field is the most important field in
a <code>GridLayout</code>, and it is usually the first field an
application will set. Widgets are laid out in columns from left to
right, and a new row is created when <code>numColumns</code> + 1 widgets
are added to the <code>Composite</code>. The default is to have only 1
column. The following code creates a <code>Shell</code> with five <code>Button</code>
children of various widths, managed by a <code>GridLayout</code>. The
table below shows the grid when <code>numColumns</code> is set to one, two,
or three.</p>
<pre>Display display = new Display();
Shell shell = new Shell(display);
GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = 3;
shell.setLayout(gridLayout);
new Button(shell, SWT.PUSH).setText(&quot;B1&quot;);
new Button(shell, SWT.PUSH).setText(&quot;Wide Button 2&quot;);
new Button(shell, SWT.PUSH).setText(&quot;Button 3&quot;);
new Button(shell, SWT.PUSH).setText(&quot;B4&quot;);
new Button(shell, SWT.PUSH).setText(&quot;Button 5&quot;);
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}</pre>
<table>
<tr>
<td><pre>numColumns = 1</pre></td>
<td><pre>numColumns = 2</pre></td>
<td><pre>numColumns = 3</pre></td>
</tr>
<tr>
<td><img src="images/GridLayoutSampleNumColumns1.png" /></td>
<td><img src="images/GridLayoutSampleNumColumns2.png" /></td>
<td><img src="images/GridLayoutSampleNumColumns3.png" /></td>
</tr>
</table>
<p>The <code>makeColumnsEqualWidth</code> field forces the columns
to be the same width. The default is <code>false</code>. If we change the example
above to have three columns of equal width, this is what we would get (note
that in the absence of further instruction, widgets are left-justified
in their columns).</p>
<img src="images/GridLayoutSampleEqualWidth.png" />
<p>The <code>marginWidth</code>, <code>marginHeight</code>, <code>horizontalSpacing</code>,
and <code>verticalSpacing</code> fields in a <code>GridLayout</code> are
similar to those in a <code>RowLayout</code>. The difference is that the
left and right margins are grouped into <code>marginWidth</code>, and
the top and bottom margins are grouped into <code>marginHeight</code>.
Also, in a <code>GridLayout</code> you can specify <code>horizontalSpacing</code>
and <code>verticalSpacing</code> independently, whereas in a <code>RowLayout</code>,
<code>spacing</code> applies to horizontal or vertical depending on the
type of the <code>RowLayout</code>.</p>
<h3>GridData Object Fields</h3>
<p><code>GridData</code> is the layout data object associated with <code>GridLayout</code>.
To set a widget's <code>GridData</code> object, you use the <code>setLayoutData</code>
method. For example, to set the <code>GridData</code> for a <code>Button</code>,
we could do the following:</p>
<pre>Button button1 = new Button(shell, SWT.PUSH);
button1.setText("B1");
button1.setLayoutData(new GridData());</pre>
<p>Of course, this code just creates a <code>GridData</code> object
with all of its fields set to their default values, which is the same as
not setting the layout data at all. There are two ways to create a <code>GridData</code>
object with certain fields set. The first is to set the fields directly:</p>
<pre>GridData gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
gridData.grabExcessHorizontalSpace = true;
button1.setLayoutData(gridData);</pre> <img
src="images/GridDataSampleHorizontalFill.png" />
<p>One final note about <code>GridData</code> objects before we get
into their fields: do not reuse <code>GridData</code> objects. Every
widget in a <code>Composite</code> that is managed by a <code>GridLayout</code>
must have a unique <code>GridData</code> object. If the layout data for
a widget in a <code>GridLayout</code> is null at layout time, a unique <code>GridData</code>
object is created for it.</p>
<p>The <code>horizontalAlignment</code> and <code>verticalAlignment</code>
fields specify where to place a widget horizontally and/or vertically
within its grid cell. Each alignment field can have one of the following
values:</p>
<ul>
<li><code>BEGINNING</code></li>
<li><code>CENTER</code></li>
<li><code>END</code></li>
<li><code>FILL</code></li>
</ul>
<p>The default horizontalAlignment is BEGINNING (or left-aligned).
The default verticalAlignment is CENTER.</p>
<p>Let's go back to our five-button example with three columns, and
we will vary the <code>horizontalAlignment</code> of Button 5.</p>
<table>
<tr>
<td><pre>horizontalAlignment = GridData.BEGINNING</pre>
<p>(default)</p>
</td>
<td><img src="images/GridDataSampleBeginning.png" /></td>
</tr>
<tr>
<td><pre>horizontalAlignment = GridData.CENTER</pre></td>
<td><img src="images/GridDataSampleCenter.png" /></td>
</tr>
<tr>
<td><pre>horizontalAlignment = GridData.END</pre></td>
<td><img src="images/GridDataSampleEnd.png" /></td>
</tr>
<tr>
<td><pre>horizontalAlignment = GridData.FILL</pre></td>
<td><img src="images/GridDataSampleFill.png" /></td>
</tr>
</table>
<p>The <code>horizontalIndent</code> field allows you to move a
widget to the right by a specified number of pixels. This field is
typically only useful when the <code>horizontalAlignment</code> is <code>BEGINNING</code>.
We cannot use a style bit to set the indent, so we will indent &quot;Button 5&quot;
in our example by four pixels as follows:</p>
<pre>GridData gridData = new GridData();
gridData.horizontalIndent = 4;
button5.setLayoutData(gridData);</pre> <img
src="images/GridDataSampleIndent.png" />
<p>The <code>horizontalSpan</code> and <code>verticalSpan</code>
fields let widgets occupy more than one grid cell. They are often used
in conjunction with <code>FILL</code> alignment. We can make &quot;Button 5&quot; in our example
span the last two cells as follows:</p>
<pre>GridData gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
gridData.horizontalSpan = 2;
button5.setLayoutData(gridData);</pre> <img
src="images/GridDataSampleSpan01.png" />
<p>If we decide to make &quot;Wide Button 2&quot; span two cells instead, we
would end up with this:</p>
<pre>GridData gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
gridData.horizontalSpan = 2;
button2.setLayoutData(gridData);</pre> <img
src="images/GridDataSampleSpan02.png" />
<p>Or we could make &quot;Button 3&quot; span two cells vertically:</p>
<pre>GridData gridData = new GridData();
gridData.verticalAlignment = GridData.FILL;
gridData.verticalSpan = 2;
button3.setLayoutData(gridData);</pre> <img
src="images/GridDataSampleSpan03.png" />
<p>The <code>grabExcessHorizontalSpace</code> and <code>grabExcessVerticalSpace</code>
fields are typically used for larger widgets such as <code>Text,</code>
<code>List</code> or <code>Canvas</code> to allow them to grow if their
containing <code>Composite</code> grows. If a <code>Text</code> is
grabbing excess horizontal space and the user resizes the <code>Shell</code>
wider, then the <code>Text</code> will get all of the new horizontal
space and other widgets in the same row will stay their original width.
Of course, the widget that is grabbing excess space is also the first
one to shrink when the <code>Shell</code> gets smaller. It is easiest to
always think of the <b>grabExcessSpace</b> fields in the context of
resizing. For a simple example, let's reuse the previous example where
&quot;Button 3&quot; spanned two cells vertically. Here it is again:</p>
<img src="images/GridDataSampleSpan03.png" />
<p>If we resize this window, the only thing that happens is that the
window gets bigger:</p>
<img src="images/GridDataSampleSpan03Resized.png" />
<p>Now we will tell &quot;Button 3&quot; to grab excess horizontal and vertical
space, and &quot;B1&quot; and &quot;B4&quot; to fill vertically (without grabbing), and we
resize the window again:</p>
<pre>Button button1 = new Button(shell, SWT.PUSH);
button1.setText(&quot;B1&quot;);
GridData gridData = new GridData();
gridData.verticalAlignment = GridData.FILL;
button1.setLayoutData(gridData);
new Button(shell, SWT.PUSH).setText(&quot;Wide Button 2&quot;);
Button button3 = new Button(shell, SWT.PUSH);
button3.setText(&quot;Button 3&quot;);
gridData = new GridData();
gridData.verticalAlignment = GridData.FILL;
gridData.verticalSpan = 2;
gridData.grabExcessVerticalSpace = true;
gridData.horizontalAlignment = GridData.FILL;
gridData.grabExcessHorizontalSpace = true;
button3.setLayoutData(gridData);
Button button4 = new Button(shell, SWT.PUSH);
button4.setText(&quot;B4&quot;);
gridData = new GridData();
gridData.verticalAlignment = GridData.FILL;
button4.setLayoutData(gridData);
new Button(shell, SWT.PUSH).setText(&quot;Button 5&quot;);</pre> <img
src="images/GridDataSampleGrab01.png" />
<p>This time, &quot;Button 3&quot; grew in both directions, and &quot;B4&quot; grew
vertically. The other buttons stayed their original sizes. Because
&quot;Button 3&quot; was grabbing vertically and it spans two rows, the <i>last</i>
row that it spans grew taller. Note that &quot;B1&quot; did not
grow&mdash;although it is filling vertically&mdash;because its row did
not grow. Since &quot;Button 3&quot; was grabbing horizontally, its column grew
wider, and since it was filling horizontally, it grew wider to fill the
column.</p>
<p>In a typical application window, you often want to have at least
one widget that is grabbing. If more than one widget is trying to grab
the same space, then the excess space is shared evenly among the
grabbing widgets:</p>
<pre>import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
public class SampleGrabExcess {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout(2, false));
Label nameLabel = new Label(shell, SWT.NONE);
nameLabel.setText("Name:");
Text nameText = new Text(shell, SWT.BORDER);
GridData gridData = new GridData();
gridData.horizontalAlignment = SWT.FILL;
gridData.grabExcessHorizontalSpace = true;
nameText.setLayoutData(gridData);
nameText.setText("Text grows horizontally");
Label addressLabel = new Label(shell, SWT.NONE);
addressLabel.setText("Address:");
gridData = new GridData();
gridData.verticalAlignment = SWT.TOP;
addressLabel.setLayoutData(gridData);
Text addressText = new Text(shell, SWT.BORDER | SWT.WRAP | SWT.MULTI);
gridData = new GridData();
gridData.horizontalAlignment = SWT.FILL;
gridData.grabExcessHorizontalSpace = true;
gridData.verticalAlignment = SWT.FILL;
gridData.grabExcessVerticalSpace = true;
addressText.setLayoutData(gridData);
addressText.setText("This text field and the List\nbelow share any excess space.");
Label sportsLabel = new Label(shell, SWT.NONE);
sportsLabel.setText("Sports played:");
gridData = new GridData();
gridData.horizontalSpan = 2;
sportsLabel.setLayoutData(gridData);
List sportsList = new List(shell, SWT.BORDER | SWT.MULTI);
gridData = new GridData();
gridData.horizontalSpan = 2;
gridData.horizontalAlignment = SWT.FILL;
gridData.grabExcessHorizontalSpace = true;
gridData.verticalAlignment = SWT.FILL;
gridData.grabExcessVerticalSpace = true;
sportsList.setLayoutData(gridData);
sportsList.add("Hockey");
sportsList.add("Street Hockey");
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
}
}</pre>
<p><img src="images/GridLayoutGrabExcess.png" /></p>
<p>When resized, the single line (top-most) <code>Text</code> grows
to consume all available horizontal space, and the second <code>Text</code>
and the <code>List</code> grow to consume all remaining space (both
vertically and horizontally):</p>
<p><img src="images/GridLayoutGrabExcessResized.png" /></p>
<p>One final point to note about grabbing: if a widget is set to
grab excess horizontal space and its parent <code>Composite</code> grows
wider, then the entire <i>column</i> containing that widget grows wider.
If a widget is grabbing excess vertical space and its parent <code>Composite</code>
grows taller, then the entire <i>row</i> containing that widget grows
taller. The implication of this is that if any other widget in the
affected column or row has <i>fill</i> alignment, then it will stretch
also. Widgets that have beginning, center, or end alignment will not
stretch: they will stay at the beginning, center or end of the wider
column or taller row.</p>
<p>The <code>widthHint</code> and <code>heightHint</code> fields
indicate the number of pixels wide or tall that you would like a widget
to be, assuming that it does not conflict with other requirements in the
<code>GridLayout</code>'s constraint system. Looking back at the
five-button, three-column example, say we want &quot;Button 5&quot; to be 70 pixels
wide and 40 pixels tall. We code it as follows:</p>
<pre>GridData gridData = new GridData();
gridData.widthHint = 70;
gridData.heightHint = 40;
button5.setLayoutData(gridData);</pre>
<p>The natural size of &quot;Button 5&quot; is shown in the window on the left,
below, and the 70-pixel wide, 40-pixel tall &quot;Button 5&quot; is on the right.</p>
<img src="images/GridLayoutSampleNumColumns3.png" /> <img
src="images/GridDataSampleSpan04.png" />
<p>Note, however, that if the <code>horizontalAlignment</code> of
&quot;Button 5&quot; was <code>FILL</code>, then the <code>GridLayout</code> would not have been
able to honor the request for a width of 70 pixels.</p>
<p>One final comment about using width and height hints: something
that looks good on one platform may not look good on another. The
variation between font sizes and natural widget sizes across platforms
means that hard-coding pixel values is not usually the best way to lay
out windows. So, keep the use of size hints to a minimum, if you use
them at all.</p>
<h3>A Complex GridLayout Example</h3>
<p>So far, the <code>GridLayout</code> examples have been fairly
simple, in order to show how each field works. Now, we will put them all
together to create a more complicated example. We start by hand-drawing
a rough sketch of the window we want to create, to determine things like
how many columns the grid should contain, and whether or not any widgets
need to span.</p>
<img src="images/DogShowHandDrawn.gif" />
<p>Then we start coding the example from the diagram. The code is
below. Note that we have added a bit of logic to make the code more
interesting, for example, &quot;Browse...&quot; opens a <code>FileDialog</code>
to read an <code>Image</code> file which the <code>Canvas</code>
displays in a paint listener, &quot;Delete&quot; deletes the <code>Image</code>, and
Enter prints the current dog and owner info.</p>
<pre>package org.eclipse.articles.layouts.samples;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
public class DogShowRegistrationWindow {
Text dogName;
Combo dogBreed;
Canvas dogPhoto;
Image dogImage;
List categories;
Text ownerName;
Text ownerPhone;
public static void main(String[] args) {
Display display = new Display();
Shell shell = new DogShowRegistrationWindow().createShell(display);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
}
public Shell createShell(final Display display) {
final Shell shell = new Shell(display);
shell.setText("Dog Show Entry");
GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = 3;
shell.setLayout(gridLayout);
new Label(shell, SWT.NONE).setText("Dog's Name:");
dogName = new Text(shell, SWT.SINGLE | SWT.BORDER);
GridData gridData = new GridData(GridData.FILL, GridData.CENTER, true, false);
gridData.horizontalSpan = 2;
dogName.setLayoutData(gridData);
new Label(shell, SWT.NONE).setText("Breed:");
dogBreed = new Combo(shell, SWT.NONE);
dogBreed.setItems(new String[] { "Collie", "Pitbull", "Poodle",
"Scottie", "Black Lab" });
dogBreed.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false));
Label label = new Label(shell, SWT.NONE);
label.setText("Categories");
label.setLayoutData(new GridData(GridData.CENTER, GridData.CENTER, true, false));
new Label(shell, SWT.NONE).setText("Photo:");
dogPhoto = new Canvas(shell, SWT.BORDER);
gridData = new GridData(GridData.FILL, GridData.FILL, true, true);
gridData.widthHint = 80;
gridData.heightHint = 80;
gridData.verticalSpan = 3;
dogPhoto.setLayoutData(gridData);
dogPhoto.addPaintListener(new PaintListener() {
public void paintControl(final PaintEvent event) {
if (dogImage != null) {
event.gc.drawImage(dogImage, 0, 0);
}
}
});
categories = new List(shell, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
categories.setItems(new String[] { "Best of Breed", "Prettiest Female",
"Handsomest Male", "Best Dressed", "Fluffiest Ears",
"Most Colors", "Best Performer", "Loudest Bark",
"Best Behaved", "Prettiest Eyes", "Most Hair", "Longest Tail",
"Cutest Trick" });
gridData = new GridData(GridData.FILL, GridData.FILL, true, true);
gridData.verticalSpan = 4;
int listHeight = categories.getItemHeight() * 12;
Rectangle trim = categories.computeTrim(0, 0, 0, listHeight);
gridData.heightHint = trim.height;
categories.setLayoutData(gridData);
Button browse = new Button(shell, SWT.PUSH);
browse.setText("Browse...");
gridData = new GridData(GridData.FILL, GridData.CENTER, true, false);
gridData.horizontalIndent = 5;
browse.setLayoutData(gridData);
browse.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
String fileName = new FileDialog(shell).open();
if (fileName != null) {
dogImage = new Image(display, fileName);
}
}
});
Button delete = new Button(shell, SWT.PUSH);
delete.setText("Delete");
gridData = new GridData(GridData.FILL, GridData.BEGINNING, true, false);
gridData.horizontalIndent = 5;
delete.setLayoutData(gridData);
delete.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
if (dogImage != null) {
dogImage.dispose();
dogImage = null;
dogPhoto.redraw();
}
}
});
Group ownerInfo = new Group(shell, SWT.NONE);
ownerInfo.setText("Owner Info");
gridLayout = new GridLayout();
gridLayout.numColumns = 2;
ownerInfo.setLayout(gridLayout);
gridData = new GridData(GridData.FILL, GridData.CENTER, true, false);
gridData.horizontalSpan = 2;
ownerInfo.setLayoutData(gridData);
new Label(ownerInfo, SWT.NONE).setText("Name:");
ownerName = new Text(ownerInfo, SWT.SINGLE | SWT.BORDER);
ownerName.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false));
new Label(ownerInfo, SWT.NONE).setText("Phone:");
ownerPhone = new Text(ownerInfo, SWT.SINGLE | SWT.BORDER);
ownerPhone.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false));
Button enter = new Button(shell, SWT.PUSH);
enter.setText("Enter");
gridData = new GridData(GridData.END, GridData.CENTER, false, false);
gridData.horizontalSpan = 3;
enter.setLayoutData(gridData);
enter.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
System.out.println("\nDog Name: " + dogName.getText());
System.out.println("Dog Breed: " + dogBreed.getText());
System.out.println("Owner Name: " + ownerName.getText());
System.out.println("Owner Phone: " + ownerPhone.getText());
System.out.println("Categories:");
String cats[] = categories.getSelection();
for (int i = 0; i > cats.length; i++) {
System.out.println("\t" + cats[i]);
}
}
});
shell.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent arg0) {
if (dogImage != null) {
dogImage.dispose();
dogImage = null;
}
}
});
shell.pack();
return shell;
}
}</pre>
<p>Here is what the window looks like after Mary Smith enters
Bifford in the dog show:</p>
<img src="images/DogShowBiff.png" />
<p>If this window is resized larger, the layout adjusts as follows:</p>
<img src="images/DogShowBiffResized.png" />
<p>Notice the following:</p>
<ul>
<li>There are three columns and seven rows.</li>
<li>The <code>dogPhoto</code> <code>Canvas</code> grew wider and
taller because it is filling and grabbing horizontally and vertically
(we did not resize the <code>Image</code>, but we could have).</li>
<li>The <code>dogBreed</code> <code>Combo</code> grew wider
because it is filling horizontally, and it is in the same column as the
<code>Canvas</code>.</li>
<li>The <code>dogName</code> <code>Text</code> grew wider because
it is filling horizontally, and one of the columns it spans is the
column containing the <code>Canvas</code>.</li>
<li>The <code>categories</code> <code>List</code> grew taller
because it is filling vertically, and it spans the same rows that the <code>Canvas</code>
does.</li>
<li>Because the <code>categories</code> <code>List</code> grew
taller, its vertical scrollbar disappeared (it did not grow wider).</li>
<li>The <code>ownerInfo</code> <code>Group</code> grew wider
because it is filling horizontally, and one of the columns it spans is
the column containing the <code>Canvas</code>.</li>
<li>The <code>ownerInfo</code> <code>Group</code>, as a subclass
of <code>Composite</code>, has its own <code>GridLayout</code> with 2
columns and 2 rows.</li>
<li>The <code>ownerName</code> and <code>ownerPhone</code> <code>Texts</code>
grew wider because the <code>Group</code> grew wider, and they are
filling and grabbing horizontally in the <code>Group</code>'s <code>GridLayout</code>.</li>
<li>The <code>browse</code> and <code>delete</code> <code>Buttons</code>
are indented slightly, and because they both fill horizontally, they
are the same width.</li>
<li>The <code>delete</code> <code>Button</code> is vertically
aligned at the top of its row.</li>
<li>The &quot;Categories&quot; <code>Label</code> is centered over
the <code>categories</code> <code>List</code>.
<li>The <code>enter</code> <code>Button</code> is horizontally
aligned to the right of the 3 columns it spans.</li>
<li>The <code>dogPhoto</code> <code>Canvas</code> was created with
width and height hints because we want the <code>Image</code> to be 80
pixels x 80 pixels, if possible.</li>
<li>The <code>categories</code> <code>List</code> was created with
a height hint that was based on the <code>List</code>'s font times 12,
because we want try to get the <code>List</code> to show 12 items
initially.</li>
</ul>
<h2>FormLayout</h2>
<p><code>FormLayout</code> works by creating <code>FormAttachment</code>s
for each side of the widget, and storing them in the layout data. An
attachment 'attaches' a specific side of the widget either to a position
in the parent <code>Composite</code> or to another widget within the
layout. This provides tremendous flexibility when laying out, as it
allows you to specify the placement of individual widgets within the
layout.</p>
<h3>FormLayout Configuration Fields</h3>
<p>The <code>marginWidth</code>, and <code>MarginHeight</code>
fields in <code>FormLayout</code> are similar to those in <code>GridLayout</code>.
Left and right margins are defined by <code>marginWidth</code>, and top
and bottom margins are defined by <code>marginHeight</code>. Margins can
also be defined on a per-widget basis in the attachments. <code>FormLayout</code>
margins are zero by default.</p>
<p>To set the margins, we create a <code>FormLayout</code>, and set
the margin fields. The following code will set a margin of five pixels
around all four sides of the parent <code>Composite</code>:</p>
<pre>Display display = new Display ();
Shell shell = new Shell (display);
FormLayout layout= new FormLayout ();
layout.marginHeight = 5;
layout.marginWidth = 5;
shell.setLayout(layout);</pre>
<h3>FormData Object Fields</h3>
<p><code>FormData</code> objects specify how each widget in a <code>FormLayout</code>
will be laid out. Each <code>FormData</code> object defines the
attachments for all four sides of the widget. These attachments tell
where to position each side of the widget. To set a widget's <code>FormData</code>
object, you use the <code>setLayoutData(Object)</code> method, for example:</p>
<pre>Button button1 = new Button(shell, SWT.PUSH);
button1.setText(&quot;B1&quot;);
button1.setLayoutData(new FormData());</pre>
<p>This code creates a <code>FormData</code> object with no
attachments. In this case, default attachments are defined, which
defeats the whole purpose and utility of <code>FormLayout</code>. The
default attachments attach the widget to the top and left edges of the
parent <code>Composite</code>. If every widget in a <code>FormLayout</code>
used the default attachments, they would all be laid out one on top of
another in the top left corner of the parent <code>Composite</code>.</p>
<p>The <code>left</code>, <code>right</code>, <code>top</code>, and
<code>bottom</code> fields of <code>FormData</code> specify the <code>FormAttachment</code>
objects that are associated with the left, right, top and bottom sides
of the widget, respectively. These fields are set in the following
example:</p>
<pre>FormData formData = new FormData();
formData.top = new FormAttachment(0,60);
formData.bottom = new FormAttachment(100,-5);
formData.left = new FormAttachment(20,0);
formData.right = new FormAttachment(100,-3);
button1.setLayoutData(formData);</pre> <img
src="images/FormLayoutSample01.png" />
<p>A <code>FormAttachment</code> object defines the attachment of a
specific side of a widget. There are many ways that a side can be
attached: to a position in the parent <code>Composite</code>, to an edge
of the <code>Composite</code>, to the adjacent side of another widget,
to the opposite side of another widget, or centered on another widget.
Attaching to a position places the side of the widget so that it is
always at a percentage of the <code>Composite</code>. To attach to an
edge of the <code>Composite</code>, the percentage is either 0% or 100%.
Attaching to the adjacent side of another widget ensures that the
specified side of the widget is always next to the closest side of the
other widget. Attaching to the opposite side of another widget ensures
that the specific side of the widget is aligned with the furthest side
of the other widget. Finally, attaching to the center of another widget
centers the widget on the other widget. Any of these ways can be done
with or without an offset.</p>
<p>The <code>width</code> and <code>height</code> fields of <code>FormData</code>
specify the requested width and the height of the widget. If a requested
width or height conflicts with constraints set by the attachments, then
that width or height will not be honored. Although setting attachments can
also determine width and height, there are some cases when you do not
want to define attachments for all sides of the widget. In this case, it
may be useful to set the width and height of the widget as follows:</p>
<pre>FormData formData = new FormData(20,30);
formData.top = new FormAttachment(0,60);
formData.left = new FormAttachment(20,0);
button1.setLayoutData(formData);</pre>
<p>If you wish to set only the width or the height, you can directly
set the width or height field in the <code>FormData</code> object:</p>
<pre>FormData formData = new FormData();
formData.width = 30;
formData.top = new FormAttachment(0,60);
formData.bottom = new FormAttachment(100,-5);
formData.left = new FormAttachment(20,0);
button1.setLayoutData(formData);</pre>
<p>Note that if a button is attached to the parent <code>Composite</code>
on both sides, when the <code>Composite</code> is resized, the button
will grow or shrink along with it.</p>
<h3>FormAttachment Objects</h3>
<p>A <code>FormAttachment</code> is an object that defines the
attachment for a specific side of a widget. It is not always necessary
to set an attachment for all four sides of a widget. Often, specifying
one or more sides of a widget can fully specify its placement in the
layout. In order to properly place your widgets, you should define an
attachment for at least one of <code>left</code> or <code>right</code>
in the <code>FormData</code>, and at least one of <code>top</code> or <code>bottom</code>.
If you only wish to attach the left side of a widget and not the right,
then the widget will be positioned based on its left side, and the
widget will take its natural size (or its requested size, if one was set
for it). If you do not attach the left or the right, default positioning
will attach your widget to the left side of the form. The same logic
applies for the top and bottom sides.</p>
<h3>Attaching to a Position</h3>
<p>There are many types of attachment. The first is to attach the
widget to a position in the parent <code>Composite</code>. This can be
done by defining a percentage value out of 100, for example:</p>
<pre>FormData formData = new FormData();
formData.top = new FormAttachment(50,0);
button1.setLayoutData(formData);</pre> <img
src="images/FormLayoutSamplePosition50.png" />
<p>This sets the top of the <code>Button</code> to a position that
represents 50% of the height of the parent <code>Composite</code> (a <code>Shell</code>),
with an offset of 0. When the shell is resized, the top side of the <code>Button</code>
will still be at 50%, like so:</p>
<img src="images/FormLayoutSamplePosition50Resized.png" />
<p>If we chose to set an offset value, the top side of the <code>Button</code>
would have been set to 50% of the <code>Composite</code> plus or minus
the number of pixels set for the offset.</p>
<p>We can also define the position of the button using an arbitrary scale, for example:</p>
<pre>FormData formData = new FormData();
formData.top = new FormAttachment(30,70,10);
button1.setLayoutData(formData);</pre>
<p>If the height of the <code>Composite</code> is defined as being
70 units, this sets the top of the <code>Button</code> to a position
representing 30 units down from the top of the <code>Composite</code>,
plus 10 pixels (i.e. 3/7ths of the height of the composite plus 10 pixels).</p>
<p>To attach a side of a widget to an edge of
the parent <code>Composite</code>, set the position to either 0% or 100%. The 0
position is defined as the top of the <code>Composite</code> when going
vertically, and the left when going horizontally. The right and bottom
edges of the <code>Composite</code> are defined as the 100 position.
Therefore, if we want to attach a widget to the right edge of the <code>Composite</code>,
we simply have to create an attachment that sets the position to 100:</p>
<pre>FormData formData = new FormData();
formData.right = new FormAttachment(100,-5);
button1.setLayoutData(formData);</pre> <img
src="images/FormLayoutSampleParent100.png" />
<p>This attaches the right side of the <code>Button</code> to the
right edge of the parent (a <code>Shell</code>), with an offset of five
pixels. Note that the offsets go in one direction only. If you want a
widget offset down or to the right, the offset should be positive. For
offsets that shift the widget up or to the left, the offset should be
negative. When the <code>Shell</code> is resized, the <code>Button</code>
will always be five pixels away from the right edge:</p>
<img src="images/FormLayoutSampleParent100Resized.png" />
<h3>Attaching to Another Widget</h3>
<p>The third type of attachment is to attach the side of the widget
to another control within the parent <code>Composite</code>. The side
can be attached to the adjacent side of the other control (the default),
to the opposite side of the other control, or the widget can be centered
on the other control, all with or without and offset.</p>
<p>The most common way to attach to another control is to attach to
its adjacent side. For example, the following code:</p>
<pre>FormData formData = new FormData();
formData.top = new FormAttachment(20,0);
button1.setLayoutData(formData);
FormData formData2 = new FormData();
formData2.top = new FormAttachment(button1,10);
button2.setLayoutData(formData2);</pre> <img
src="images/FormLayoutSampleAttachWidget01.png" />
<p>This example attaches the top of button2 to the bottom of
button1. Note that when the window is resized, button1 will move so that
its top side is always positioned at 20% of the <code>Shell</code>, and
button2 will move so that its top side is always 10 pixels below the
adjacent (bottom) side of button1.</p>
<img src="images/FormLayoutSampleAttachWidget01Resized.png" />
<p>While the default is to attach the side of a widget to the
adjacent side of a control, <code>FormAttachment</code>s can also be
created to attach to the opposite side of a control. This is useful when
lining up widgets. In this case, you create the attachment to the other
control using <code>TOP</code>, <code>BOTTOM</code>, <code>LEFT</code>
or <code>RIGHT</code> alignment, for example:</p>
<pre>formData2.top = new FormAttachment(button1,0,SWT.TOP);</pre>
<p>In the following example, the top side of <code>button1</code> is positioned
at 20% of the <code>Shell</code>. <code>button2</code>'s top side is aligned with
<code>button1</code>'s top side, using <code>TOP</code> alignment. This means that the top side of
<code>button2</code> is also positioned at 20% of the <code>Shell</code>. Note that
when specifying the top attachment, only the vertical placement of the
widget is being defined. It is still necessary to set the left
attachment for <code>button2</code> so that the <code>Button</code>s are not stacked
on top of each other.</p>
<pre>FormData formData = new FormData(50,50);
formData.top = new FormAttachment(20,0);
button1.setLayoutData(formData);
FormData formData2 = new FormData();
formData2.left = new FormAttachment(button1,5);
formData2.top = new FormAttachment(button1,0,SWT.TOP);
button2.setLayoutData(formData2);</pre> <img
src="images/FormLayoutSampleAttachWidget02.png" />
<p>The final way to attach a widget to another control is to center
it on the other control. This is useful when the widgets are different
sizes. In this case, you create the attachment to the other control with
<code>CENTER</code> alignment, for example:</p>
<pre>formData.top = new FormAttachment(button1,0,SWT.CENTER);</pre>
<p>This will place the top of the widget in a position that will
allow the widget to be centered on the other control, with an offset of
0. Setting only the top, or the bottom, or both as a center attachment
will produce the same result. The top side of the widget is not
centered, but the entire widget is centered, so this only needs to be
specified once. Here is an example:</p>
<pre>FormData formData1 = new FormData (50,50);
button1.setLayoutData(formData1);
FormData formData2 = new FormData ();
formData2.left = new FormAttachment (button1,5);
formData2.top = new FormAttachment (button1,0,SWT.CENTER);
button2.setLayoutData(formData2);</pre> <img
src="images/FormLayoutSampleAttachWidget03.png" />
<p>Using the different types of <code>FormAttachment</code> allows
layouts to be defined in many different ways. <code>FormLayout</code>
covers certain cases that cannot be solved using <code>FillLayout</code>,
<code>RowLayout</code> or <code>GridLayout</code>, making it a very
useful class for defining layouts.</p>
<p><b>Important: </b>Do <b>not</b> define circular
attachments. For example, do not attach the right edge of button1
to the left edge of button2 and then attach the left edge button2 to the
right edge of button1. This will over-constrain the layout, causing
undefined behavior. The algorithm will terminate, but the results
are undefined. Therefore, make sure that you do not over-constrain your
widgets. Only provide the attachments necessary to properly lay out the
widgets.</p>
<h3>A FormLayout Example</h3>
<p>So far, all the examples using <code>FormLayout</code> have
involved one or two <code>Button</code>s, to show how <code>FormAttachment</code>s
work. Next, we will do a simple example using more <code>Button</code>s
to show how a layout can be arranged using the attachments. We'll start
by drawing a basic diagram outlining the attachments that we wish to
create.</p>
<p><img src="images/FormExampleHandDrawn.jpg" /></p>
<pre>FormData data1 = new FormData();
data1.left = new FormAttachment(0,5);
data1.right = new FormAttachment(25,0);
button1.setLayoutData(data1);
FormData data2 = new FormData();
data2.left = new FormAttachment(button1,5);
data2.right = new FormAttachment(100,-5);
button2.setLayoutData(data2);
FormData data3 = new FormData(60,60);
data3.top = new FormAttachment(button1,5);
data3.left = new FormAttachment(50,-30);
data3.right = new FormAttachment(50,30);
button3.setLayoutData(data3);
FormData data4 = new FormData();
data4.top = new FormAttachment(button3,5);
data4.bottom = new FormAttachment(100,-5);
data4.left = new FormAttachment(25,0);
button4.setLayoutData(data4);
FormData data5 = new FormData();
data5.bottom = new FormAttachment(100,-5);
data5.left = new FormAttachment(button4,5);
button5.setLayoutData(data5);</pre>
<p>In this case, since no top attachment was defined for <code>button1</code> or
<code>button2</code>, they are attached to the top of the layout. <code>button3</code> is centred
in the layout using percentages and offsets on the left and right sides.
<code>button4</code> and <code>button5</code> are attached to the bottom of the layout with a five
pixel offset.</p>
<img src="images/FormLayoutSample5Buttons01.png" />
<p>When we resize, the attachments become more visible. <code>button1</code> is
attached on the left and the right side, so when the window is resized,
it grows. Note that the right side will always be at 25% of the window.
The same resize results apply for <code>button2</code>, as both sides are attached.
The left side is attached to <code>button1</code>, so it will always be at 25% plus five
pixels. <code>button3</code> stays in the center of the window, horizontally. <code>button4</code>
is attached at the top and the bottom, so it grows vertically when the
window is resized, but it is only attached on the left and not the
right, so it does not grow horizontally. <code>button5</code> will not grow or
shrink, but it will always stay five pixels away from <code>button4</code> on the left,
and five pixels away from the bottom of the window.</p>
<img src="images/FormLayoutSample5Buttons01Resized.png" />
<h3>A Complex FormLayout Example</h3>
<p>To illustrate how <code>FormLayout</code> can be used for more
complicated arrangements, the Dog Show Entry example done previously for
<code>GridLayout</code> is redone using <code>FormLayout</code>. This
code produces an identical layout, but uses different concepts to
achieve it.</p>
<pre>import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
public class DogShowRegistrationWindowWithFormLayout {
Image dogImage;
Text dogNameText;
Combo dogBreedCombo;
Canvas dogPhoto;
List categories;
Text nameText;
Text phoneText;
public static void main(String[] args) {
Display display = new Display();
Shell shell = new DogShowRegistrationWindow().createShell(display);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
}
public Shell createShell(final Display display) {
final Shell shell = new Shell(display);
FormLayout layout = new FormLayout();
layout.marginWidth = 5;
layout.marginHeight = 5;
shell.setLayout(layout);
shell.setText("Dog Show Entry");
Group ownerInfo = new Group(shell, SWT.NONE);
ownerInfo.setText("Owner Info");
FormLayout ownerLayout = new FormLayout();
ownerLayout.marginWidth = 5;
ownerLayout.marginHeight = 5;
ownerInfo.setLayout(ownerLayout);
Label dogName = new Label(shell, SWT.NONE);
dogName.setText("Dog's Name:");
dogNameText = new Text(shell, SWT.SINGLE | SWT.BORDER);
Label dogBreed = new Label(shell, SWT.NONE);
dogBreed.setText("Breed:");
dogBreedCombo = new Combo(shell, SWT.NONE);
dogBreedCombo.setItems(new String[] { "Collie", "Pitbull", "Poodle",
"Scottie", "Black Lab" });
Label photo = new Label(shell, SWT.NONE);
photo.setText("Photo:");
dogPhoto = new Canvas(shell, SWT.BORDER);
Button browse = new Button(shell, SWT.PUSH);
browse.setText("Browse...");
Button delete = new Button(shell, SWT.PUSH);
delete.setText("Delete");
Label cats = new Label(shell, SWT.NONE);
cats.setText("Categories");
categories = new List(shell, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL
| SWT.H_SCROLL);
categories.setItems(new String[] { "Best of Breed", "Prettiest Female",
"Handsomest Male", "Best Dressed", "Fluffiest Ears",
"Most Colors", "Best Performer", "Loudest Bark",
"Best Behaved", "Prettiest Eyes", "Most Hair", "Longest Tail",
"Cutest Trick" });
Button enter = new Button(shell, SWT.PUSH);
enter.setText("Enter");
FormData data = new FormData();
data.top = new FormAttachment(dogNameText, 0, SWT.CENTER);
dogName.setLayoutData(data);
data = new FormData();
data.left = new FormAttachment(dogName, 5);
data.right = new FormAttachment(100, 0);
dogNameText.setLayoutData(data);
data = new FormData();
data.top = new FormAttachment(dogBreedCombo, 0, SWT.CENTER);
dogBreed.setLayoutData(data);
data = new FormData();
data.top = new FormAttachment(dogNameText, 5);
data.left = new FormAttachment(dogNameText, 0, SWT.LEFT);
data.right = new FormAttachment(categories, -5);
dogBreedCombo.setLayoutData(data);
data = new FormData(80, 80);
data.top = new FormAttachment(dogBreedCombo, 5);
data.left = new FormAttachment(dogNameText, 0, SWT.LEFT);
data.right = new FormAttachment(categories, -5);
data.bottom = new FormAttachment(ownerInfo, -5);
dogPhoto.setLayoutData(data);
dogPhoto.addPaintListener(new PaintListener() {
public void paintControl(final PaintEvent event) {
if (dogImage != null) {
event.gc.drawImage(dogImage, 0, 0);
}
}
});
data = new FormData();
data.top = new FormAttachment(dogPhoto, 0, SWT.TOP);
photo.setLayoutData(data);
data = new FormData();
data.top = new FormAttachment(photo, 5);
data.right = new FormAttachment(dogPhoto, -5);
browse.setLayoutData(data);
browse.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
String fileName = new FileDialog(shell).open();
if (fileName != null) {
dogImage = new Image(display, fileName);
}
}
});
data = new FormData();
data.left = new FormAttachment(browse, 0, SWT.LEFT);
data.top = new FormAttachment(browse, 5);
data.right = new FormAttachment(dogPhoto, -5);
delete.setLayoutData(data);
delete.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
if (dogImage != null) {
dogImage.dispose();
dogImage = null;
dogPhoto.redraw();
}
}
});
data = new FormData(90, 140);
data.top = new FormAttachment(dogPhoto, 0, SWT.TOP);
data.right = new FormAttachment(100, 0);
data.bottom = new FormAttachment(enter, -5);
categories.setLayoutData(data);
data = new FormData();
data.bottom = new FormAttachment(categories, -5);
data.left = new FormAttachment(categories, 0, SWT.CENTER);
cats.setLayoutData(data);
data = new FormData();
data.right = new FormAttachment(100, 0);
data.bottom = new FormAttachment(100, 0);
enter.setLayoutData(data);
enter.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
System.out.println("\nDog Name: " + dogNameText.getText());
System.out.println("Dog Breed: " + dogBreedCombo.getText());
System.out.println("Owner Name: " + nameText.getText());
System.out.println("Owner Phone: " + phoneText.getText());
System.out.println("Categories:");
String cats[] = categories.getSelection();
for (int i = 0; i < cats.length; i++) {
System.out.println("\t" + cats[i]);
}
}
});
data = new FormData();
data.bottom = new FormAttachment(enter, -5);
data.left = new FormAttachment(0, 0);
data.right = new FormAttachment(categories, -5);
ownerInfo.setLayoutData(data);
Label name = new Label(ownerInfo, SWT.NULL);
name.setText("Name:");
Label phone = new Label(ownerInfo, SWT.PUSH);
phone.setText("Phone:");
nameText = new Text(ownerInfo, SWT.SINGLE | SWT.BORDER);
phoneText = new Text(ownerInfo, SWT.SINGLE | SWT.BORDER);
data = new FormData();
data.top = new FormAttachment(nameText, 0, SWT.CENTER);
name.setLayoutData(data);
data = new FormData();
data.top = new FormAttachment(phoneText, 0, SWT.CENTER);
phone.setLayoutData(data);
data = new FormData();
data.left = new FormAttachment(phone, 5);
data.right = new FormAttachment(100, 0);
nameText.setLayoutData(data);
data = new FormData();
data.left = new FormAttachment(nameText, 0, SWT.LEFT);
data.right = new FormAttachment(100, 0);
data.top = new FormAttachment(55, 0);
phoneText.setLayoutData(data);
shell.pack();
return shell;
}
}</pre>
<p>This is what the layout looks like after Mary Smith enters
Bifford in the dog show:</p>
<img src="images/DogShowBiffWithFormLayout.png" />
<p>When the window is resized, the same controls are resized as in
the <code>GridLayout</code> example.</p>
<img src="images/DogShowBiffWithFormLayoutResized.png" />
<h2>Writing Your Own Layout Class</h2>
<p>Occasionally, you may want to write your own <code>Layout</code>
class. Perhaps your layout needs are very complex. Maybe you have the
same look in many places, and you want to take advantage of code reuse.
Or you want to leverage domain knowledge to create a very efficient
layout class. Whatever the reason, there are things to consider before
writing a new class:</p>
<ul>
<li>Can the layout be done using a <code>GridLayout</code> or <code>FormLayout</code>,
with maybe a few nested layouts?</li>
<li>Can the desired effect be more easily achieved with a resize
listener?</li>
<li>Are you defining a general layout algorithm or just
positioning widgets?</li>
</ul>
<p>Unless you are writing a very generic <code>Layout</code> type
that will be used by several <code>Composite</code> widgets, it is often
better and easier to simply calculate sizes and position children in a
resize listener. Many of the SWT custom widgets were written this way.
Although a new widget can be implemented as a <code>Composite</code>/<code>Layout</code>
pair, implementing it as a <code>Composite</code> that does its layout
in a resize listener and computes its preferred size in <code>computeSize</code>
is clearer, and does not involve writing an extra class.</p>
<p>First, we will look at how layouts work, and then we will create
a new <code>Layout</code> class. Another example of writing your own <code>Layout</code>
can be found in the <i>Compound Widget Example</i> section of <a
href="http://www.eclipse.org/articles/Article-Writing%20Your%20Own%20Widget/Writing%20Your%20Own%20Widget.htm">Creating
Your Own Widgets Using SWT</a>, which shows how to achieve the same look
using either a resize listener or a new <code>Layout</code> class.</p>
<h3>How Layouts Work</h3>
<p><code>Layout</code> is the abstract superclass of all layouts. It
only has two methods: <code>computeSize</code> and <code>layout</code>.
The class is defined as follows:</p>
<pre>public abstract class Layout {
protected abstract Point computeSize(Composite composite, int widthHint, int heightHint, boolean flushCache);
protected abstract void layout(Composite composite, boolean flushCache);
}</pre>
<p>The <code>computeSize</code> method calculates the width and
height of a rectangle that encloses all of the <code>Composite</code>'s
children once they have been sized and placed according to the layout
algorithm encoded in the <code>Layout</code> class. The hint parameters
allow the width and/or height to be constrained. For example, a layout
may choose to grow in one dimension if constrained in another. A hint of
SWT.DEFAULT means to use the preferred size.</p>
<p>The <code>layout</code> method positions and sizes the <code>Composite</code>'s
children. A <code>Layout</code> can choose to cache layout-related
information, such as the preferred extent of each of the children. The <code>flushCache</code>
parameter tells the <code>Layout</code> to flush cached data.</p>
<p>Since a <code>Layout</code> controls the size and placement of
widgets in a <code>Composite</code>, there are several methods in <code>Composite</code>
that are used with <code>Layout</code>s.</p>
<p>The first two methods allow setting and getting a <code>Layout</code>
object in a <code>Composite</code>.</p>
<pre>public void setLayout(Layout layout);
public Layout getLayout();</pre>
<p>An application can force a <code>Layout</code> to recalculate the
sizes of and reposition children by sending <code>layout()</code> to the
parent <code>Composite</code>.</p>
<pre>public void layout(boolean changed);
public void layout();
// calls layout(true);</pre>
<p>You would do this after changing anything about the children that
might affect their size or position, such as changing the font of a
child, changing the text or image of a child, adding a new child, or
adding children to a child (If the child can accommodate the change,
then layout may not be necessary; for example, changing the font or
text of a scrollable multi-line <code>Text</code>). Since these changes
are done programmatically, they do not cause events to happen.
Consequently, the parent doesn't know about the changes, and has to be
told through the <code>layout</code> method. This strategy reduces flash
because the application can make several changes and then tell the
parent to layout, and the children are only redrawn once instead of once
per change. If <code>layout()</code> is not called and changes are made
after the shell is opened, then the children may not be correctly laid
out until the shell is somehow resized. Note that <code>shell.open()</code>
causes a layout to occur.</p>
<p>The <code>computeSize</code> methods of a <code>Composite</code>
calculate the Composite's preferred size, which is the size of its
client area as determined by the <code>Layout</code>, plus its trim.</p>
<pre>public Point computeSize(int widthHint, int heightHint, boolean changed);
public Point computeSize(int widthHint, int heightHint);
// calls computeSize(widthHint, heightHint, true);</pre>
<p>The <b>clientArea</b> of a <code>Composite</code> is the
rectangle that will contain all of the children. A <code>Layout</code>
positions the children inside the client area.</p>
<pre>public Rectangle getClientArea ();</pre>
<p>The <b>trim</b> of a <code>Composite</code> is the area outside
the client area. For some composites, the size of the trim is zero. The
trim can be computed by passing the dimensions of the client area into
the method <code>computeTrim</code>.</p>
<pre>public Rectangle computeTrim (int x, int y, int width, int height);</pre>
<p>Sending <code>pack</code> to a <code>Composite</code> resizes it
to its preferred size.</p>
<pre>public void pack(boolean changed);
// calls setSize(computeSize(SWT.DEFAULT, SWT.DEFAULT, changed));
public void pack();
// calls pack(true);</pre>
<p>The boolean parameter to the <code>layout</code>, <code>computeSize</code>,
and <code>pack</code> methods is the <code>changed</code> flag. If <code>true</code>,
it indicates that the <code>Composite</code>'s contents have changed in
some way that affects its preferred size, therefore any caches that the
<code>Layout</code> may have been keeping need to be flushed. When a <code>Composite</code>
is resized, it asks its <code>Layout</code> to lay out its children by
calling layout(false); therefore widget content caches are <i>not</i>
flushed. This lets the <code>Layout</code> perform any expensive
calculations only when necessary.</p>
<p>Caching can increase performance, but it can also be tricky. You
can choose not to cache at all: in fact, it is best not to try caching
until your code is stable. When considering what to cache, be certain
not to store any widget state, such as the text of a label, or the
number of items in a list.</p>
<h3>Custom Layout Example</h3>
<p>If you have several vertically oriented <code>Composite</code>
widgets in your application, you might choose to write <code>ColumnLayout</code>.
We will show a simple version of a <code>Layout</code> class that lays
out <code>Composite</code> children into a single column. The class has
fixed margins and spacing. Children are given the same width, but they
take their natural height. (Note that <code>RowLayout</code> will have <code>ColumnLayout</code>
behaviour if its type is set to <code>SWT.VERTICAL</code>. This example is,
therefore, just an example. In practice, if you need to lay widgets out
in a column, you would use <code>RowLayout.</code>)</p>
<p>The code for the <code>ColumnLayout</code> class is below. Note
that we cache the width of the widest child, and the sum of the child
heights (plus spacing), and these values are used to compute the size
and lie out the children. They are recalculated if <code>flushCache</code>
is <code>true</code>.</p>
<pre>import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;
public class ColumnLayout extends Layout {
// fixed margin and spacing
public static final int MARGIN = 4;
public static final int SPACING = 2;
// cache
Point[] sizes;
int maxWidth, totalHeight;
protected Point computeSize(Composite composite, int wHint, int hHint,
boolean flushCache) {
Control children[] = composite.getChildren();
if (flushCache || sizes == null || sizes.length != children.length) {
initialize(children);
}
int width = wHint, height = hHint;
if (wHint == SWT.DEFAULT)
width = maxWidth;
if (hHint == SWT.DEFAULT)
height = totalHeight;
return new Point(width + 2 * MARGIN, height + 2 * MARGIN);
}
protected void layout(Composite composite, boolean flushCache) {
Control children[] = composite.getChildren();
if (flushCache || sizes == null || sizes.length != children.length) {
initialize(children);
}
Rectangle rect = composite.getClientArea();
int x = MARGIN, y = MARGIN;
int width = Math.max(rect.width - 2 * MARGIN, maxWidth);
for (int i = 0; i < children.length; i++) {
int height = sizes[i].y;
children[i].setBounds(x, y, width, height);
y += height + SPACING;
}
}
void initialize(Control children[]) {
maxWidth = 0;
totalHeight = 0;
sizes = new Point[children.length];
for (int i = 0; i < children.length; i++) {
sizes[i] = children[i].computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
maxWidth = Math.max(maxWidth, sizes[i].x);
totalHeight += sizes[i].y;
}
totalHeight += (children.length - 1) * SPACING;
}
}</pre>
<p>Here is some simple test code to test the <code>ColumnLayout</code>.
The <b>grow</b> and <b>shrink</b> <code>Buttons</code> show a call to
the <code>Shell</code>'s <code>layout()</code> method to force a
re-layout after changing the width of one of the children. Calling <code>layout()</code>
is the same as calling <code>layout(true</code>) which tells the <code>ColumnLayout</code>
to flush its caches before setting the bounds of the children. The <code>Shell</code>
is also told to <code>pack()</code> after laying out the children. This
forces the <code>Shell</code> to take the new size.</p>
<pre>import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.events.*;
public class ColumnLayoutTest {
static Shell shell;
static Button button3;
public static void main(String[] args) {
Display display = new Display();
shell = new Shell(display);
shell.setLayout(new ColumnLayout());
new Button(shell, SWT.PUSH).setText("B1");
new Button(shell, SWT.PUSH).setText("Very Wide Button 2");
(button3 = new Button(shell, SWT.PUSH)).setText("Button 3");
Button grow = new Button(shell, SWT.PUSH);
grow.setText("Grow Button 3");
grow.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
button3.setText("Extreemely Wide Button 3");
shell.layout();
shell.pack();
}
});
Button shrink = new Button(shell, SWT.PUSH);
shrink.setText("Shrink Button 3");
shrink.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
button3.setText("Button 3");
shell.layout();
shell.pack();
}
});
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
}
}</pre>
<p>If we run the test code, the window on the left appears. Pressing
the Grow Button 3 button results in the window on the right. Resizing
the window with the mouse will also make the buttons wider (or narrower)
but they do not grow taller.</p>
<img src="images/CustomLayout.png" /> <img
src="images/CustomLayoutResized.png" />
<h3>Overriding Composite</h3>
<p>If you are writing your own widget, as outlined in <a
href="http://www.eclipse.org/articles/Article-Writing%20Your%20Own%20Widget/Writing%20Your%20Own%20Widget.htm">Creating
Your Own Widgets Using SWT</a>, and you subclass Composite, then here are a
few points to consider for your implementation:</p>
<ul>
<li>If you are providing trimmings in your new <code>Composite</code>,
make sure to override both <code>computeTrim()</code> and <code>getClientArea()</code>.</li>
<li>Never override <code>layout()</code>, but you may override <code>layout(boolean)</code>.</li>
</ul>
<p>Sometimes you want your new <code>Composite</code> to have a
specific look, and you don't want the application to be able to specify
a layout. Your new <code>Composite</code> would either do its layout in
a resize handler or using a private custom layout. In either case, you
will probably want to do the following:</p>
<ul>
<li>Override <code>setLayout()</code> to do nothing.</li>
<li>Override <code>layout(boolean)</code> to call your layout
code.</li>
<li>Override <code>computeSize()</code> to correctly compute the
size of your <code>Composite</code>.</li>
</ul>
<h2>Summary</h2>
<p>SWT provides several different ways to lay out widgets. The
simplest method, and the one you will typically use, is to use one of
the standard <code>Layout</code> classes: <code>FillLayout</code>, <code>RowLayout</code>,
<code>GridLayout</code> or <code>FormLayout</code>.</p>
<p>In certain cases you may want to write your own <code>Layout</code>
class to provide a very specific look or to reuse very similar layout
code, but often a resize listener on the parent widget will suffice.</p>
<p>For further assistance in understanding the standard SWT <code>Layout</code>
classes, see the <a href="http://www.eclipse.org/swt/snippets">SWT Snippets</a>.</p>
</div>
</body>
</html>