Понимание раскладок компонентов (Layouts) в SWT
Статьи -> Программирование -> Java
Понимание раскладок компонентов (Layouts) в SWT
v:1.0 16.07.2010
Перевод статьи Understanding Layouts in SWT.
Перевод: Петрелевич Сергей
Прим. переводчика.
Хорошего перевода для термина Layout я не нашел, поэтому оставил как есть.
Дословно можно перевести: "раскладка", "расположение", "размещение".
Буду благодарен, если кто-то из читателей подскажет устоявшийся термин на русском языке.
Предисловие
Разрабатывая приложенение на SWT, Вы можете столкнуться с необходимостью использовать layout'ы, чтобы придать приложению особый внешний вид.
Layout'ы управляют расположением и размером дочерних компонентов контейнера Composite
.
Классы layout'ов являются подклассами абстрактного класса Layout
.
В этой статье рассказывается как работать со стандартными layout'ами, и как как написать свой собственный класс.
By Carolyn MacLeod, OTI
Copyright (c) 2001, 2002 Object
Technology International, Inc.
March 22, 2001
Revised by Shantha Ramachandran, OTI
May 02, 2002
Revised by Wayne Beaton, The Eclipse Foundation
Copyright (c) 2008 The Eclipse Foundation, Inc.
May 30, 2008
Revised by Wayne Beaton, The Eclipse Foundation
Copyright (c) 2008 The Eclipse Foundation, Inc.
May 13, 2009
Обзор
Разрабатывая приложенение на Standard Widget Toolkit (SWT),
Вы можете столкнуться с необходимостью использовать layout'ы, чтобы придать приложению особый внешний вид.
Layout'ы управляют расположением и размером дочерних компонентов контейнера Composite
.
Классы layout'ов являются подклассами абстрактного класса Layout
.
SWT предлагает несколько стандартных классов layout'ов, если их не достаточно, пользователь может разработать свой.
В SWT позиция и размер компонентов не рассчитывается автоматически.
Приложения при инициализации могут задать размеры и расположение дочерних элементов Composite
, кроме того приложение может
изменять эти значения, перехватывая событие изменения размера.
Другой способ управлять расположением и размером компонентов заключается в использовании специализированных классов.
Если дочерний компонент не получил размер, то его размеры будут равны нулю и он не будет отображаться.
Следующая картинка иллюстрирует несколько наиболее важных терминов, которые используются при обсуждении layout'ов.
Контейнер Composite
(в данном случае это TabFolder
) имеет location (расположение), clientArea (клиентскую область) и trim (кромка).
Размер контейнера Composite
складывается из размеров клиентской области и размера кромки
.
Этот Composite
содержит два дочерних компонента, расположенных рядом.
Класс Layout
управляет размером и позицией этих дочерних компонентов.
Layout
позволяет задать spacing (промежуток)
между компонентами, и margin (зазор) между компонентами и границей Layout
.
Размер Layout
совпадает с размером клиентской обрасти Composite
'а.

Preferred size (предпочтительный размер) компонента (widget) - это минимальный размер, необходимый для отображения содержимого.
В случае использования Composite
предпочтительный размер - это размер минимального прямоугольника, в котором помещаются все дочерние компоненты.
Если расположением компонентов управляет приложение, то Composite
рассчитывает свой предпочтительный размер, основываясь на размере и позиции компонентов.
Если Composite
использует класс layout для управления своим содержимым, то размер клиентской области
берет из класса Layout и к полученному значению добавляет кромку (trim)
,
таким образом и высчитывается предпочтительный размер.
Стандартные Layout'ы
В состав SWT входят следующие стандартные классы layout'ов:
FillLayout
все компоненты располагаются в один ряд или колонку, размеры всех виджетов делаются одинаковымиRowLayout
все компоненты располагаются в один или несколько рядов, при этом задаются зазоры и расстояния между компонентамиGridLayout
все компоненты располагаются на сеткеFormLayout
все компоненты располагаются, путем создания приложения для каждой стороны компонента
Для использования стандартных классов layout, Вам надо импортировать соответствующий пакет библиотеки SWT:
import org.eclipse.swt.layout.*;
Layout'ы требуют специального подключения. Чтобы назначить layout компонентам контейнера, надо выполнить метод setLayout(Layout)
.
В следующем коде Shell
(дочерний класс от Composite
) получает указание использовать RowLayout
для позиционирования дочерних компонентов:
Shell shell = new Shell(); shell.setLayout(new RowLayout());
Классу layout может соответствовать класс, содержащий данные (дочерний класс от Object
), в котором хранятся данные layout'а для конкретных компонентов.
В соответствии с соглашениями, наименование класса данных layout'а состоит из двух частей "Data" и название Layout'а.
Например, класс хранящий данные для стандартного layout'а RowLayout
будет называться RowData
,
для GridLayout
наименование класса данных должно быть GridData
, и для FormLayout
это будет FormData
.
Использование класса данных продемонстрировано в следующем примере:
Button button = new Button(shell, SWT.PUSH); button.setLayoutData(new RowData(50, 40));
Примеры в этой статье
Большинство примеров в этой статье являются различными вариациями следующего кода. В следующих примерах мы можем менять тип layout'а, параметры или типы и число дочерних компонентов.
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(); } } } * This source code was highlighted with Source Code Highlighter.
Выполнение этого примера даст следующий результат:

Если пользователь изменит размеры окна таким образом, что для Button 3 уже нет места справа, RowLayout
перенесет Button 3 на следующий ряд, как показано ниже:

Как мы видим, использование layout'ов проявляется при изменении размера окна приложения.
В большинстве следующих примеров этой статьи мы будем показывать, что происходит если размеры Composite
становятся больше или меньше,
таким образом мы будем демонстрировать работу классов Layout
.
FillLayout
FillLayout
это самый простой класс.
Этот layout располагает все компоненты в один ряд или колонку и задает всем один размер.
По умолчанию выбираются максимальная длина и ширина, эти размеры и будут заданы всем компонентам.
FillLayout
не может управлять расстояниями между компонентами.
Вы можете использовать этот layout в панели задач или, например, в панели инструментов или в списке checkboxe'ов, объединенных в Группу (Group)
.
FillLayout
также может быть использован, если Composite
имеет только один компонент.
Например, если Shell
содержит только один элемент Группу (Group)
, то FillLayout
заполнит всю область Shell
.
Ниже приведен поясняющий пример кода.
Прежде всего, мы создаем FillLayout
, после чего (если ходим вертикальное расположение компонентов) устанавливаем значение поля type
равным SWT.VERTICAL
,
результат передаем в Composite
(Shell
).
В Shell
находятся три компонента - три кнопки: "B1", "Wide Button 2" и "Button 3".
Обратите внимание, что FillLayout
сделает все кнопки одного размера, и они заполнят все доступное пространство.
FillLayout fillLayout = new FillLayout(); fillLayout.type = SWT.VERTICAL; shell.setLayout(fillLayout); new Button(shell, SWT.PUSH).setText("B1"); new Button(shell, SWT.PUSH).setText("Wide Button 2"); new Button(shell, SWT.PUSH).setText("Button 3"); * This source code was highlighted with Source Code Highlighter.
Следующая таблица демонстрирует различия между горизонтальным и вертикальным режимами работы FillLayout
, приведены первоначальные значения и после увеличения размера окна.
Первоначальное состояние |
После изменения окна |
|
fillLayout.type = SWT.HORIZONTAL (по умолчанию) |
![]() |
![]() |
fillLayout.type = SWT.VERTICAL |
![]() |
![]() |
RowLayout
RowLayout
используется значительно чаще, чем FillLayout
, т.к. обладает возможностью настройки компонентов. Можно задать расстояние между компонентами,
расстояние от компонента до края клиентской области, и.т.д.
RowLayout
имеет ряд конфигурационных полей.
Кроме того, при использовании RowLayout
для каждого компонента могут быть заданы значения ширины и высоты. Эти значения можно задать используя RowData
компонента, при помощи
метода setLayoutData
.
Конфигурационные поля RowLayout
Значение поле type
определяет как будут расположены компоненты горизонтально в ряд или вертикально в колонку.
По умолчанию RowLayouts
распологает компоненты горизонтально.
Поле wrap
определяет должен или нет RowLayout
переносить компонент в следующий ряд, если в текущем ряду недостаточно места.
По умолчанию RowLayouts
перенесет компонент.
Если значение поля pack
true, то RowLayout
отобразит компонент с естественными размерами ("естественный размер" определяется компонентом;
например, компоненты label и button должны быть достаточного размера, чтобы отобразить текстовое содержание), компонент будет выровнен максимально по левому краю.
Если значние поля pack
false, то компоненты заполнят все доступное пространство, по аналогии с компонентами FillLayout
.
По умолчанию RowLayouts
устанавливает компонентам естественные значения.
Если значение поля justify
true, то компоненты распределятся на доступном месте слева на направо.
Если родительский Composite
становится шире, свободное место равномерно распределится между компонентами.
Если значения полей pack
и justify
true, компоненты отрисовываются с естественными размерами, свободное место распределяется равномерно между всеми компонентами.
По умолчанию, значение поля RowLayouts
false.
Поля marginLeft
, marginTop
, marginRight
, marginBottom
и spacing
хранят количество пикселей между компонентами (spacing
) и количество пикселей между компонентом и одной из сторон Composite
(margin).
По умолчанию, значения margin и spacing равны трем.
Следующий рисунок поясняет назначение этих полей.

Примеры RowLayout
В следующем примере создается RowLayout
, устанавливаются значения всех полей, результат передается в Shell
.
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); * This source code was highlighted with Source Code Highlighter.
Если значения по умолчанию устраивают, достаточно такой строчки:
shell.setLayout(new RowLayout());
Результаты установки значений полей показаны ниже:
Первоначальное состояние |
После изменения окна |
|
rowLayout.wrap = true; rowLayout.pack = true; rowLayout.justify = false; rowLayout.type = SWT.HORIZONTAL; (по умолчанию) |
![]() |
![]() и ![]() |
wrap = false (кнопка обрезается, т.к. не хватает места) |
![]() |
![]() |
pack = false (все компоненты одного размера) |
![]() |
![]() |
justify = true (компоненты равномерно распределены на свободном месте) |
![]() |
![]() |
type = SWT.VERTICAL (компоненты упорядочены вертикально widgets are arranged vertically in columns) |
![]() |
![]() |
Использование объектов RowData с RowLayout
Для каждого компонента, управляемого RowLayout
, можно задать первоначальную ширину и первоначальную высоту, это можно сделать при помощи объекта RowData
.
В следующем коде с помощью объектов RowData
задаются первоначальные значения для компонентов Button
.
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(); } } } * This source code was highlighted with Source Code Highlighter.
Вот что получится, если выполнить этот код.

GridLayout
С помощью GridLayout
дочерние компоненты Composite
размещаются на сетке.
В классе GridLayout
есть несколько конфигурационных полей. Как и в случае использования RowLayout
, компонентам, управляемым GridLayout
,
можно назначить объект с дополнительными данными, называемый GridData
.
Мощь GridLayout
заключается в возможности настраивать GridData
индивидуально для каждого компонента, управляемого GridLayout
.
Конфигурационные поля GridLayout
NumColumns
(количество колонок)- это самое важное поле в GridLayout
, обычно в приложении это поле инициализируется первым.
Компоненты размещаются в колонках слева направо. Новый ряд создается, когда в Composite
добавляется numColumns
+ 1 компонент.
По умолчанию, значение поля 1.
В следующем коде создается Shell
с пятью компонентами Button
разной ширины, управляемые GridLayout
.
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("B1"); new Button(shell, SWT.PUSH).setText("Wide Button 2"); new Button(shell, SWT.PUSH).setText("Button 3"); new Button(shell, SWT.PUSH).setText("B4"); new Button(shell, SWT.PUSH).setText("Button 5"); shell.pack(); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } * This source code was highlighted with Source Code Highlighter.
В следующей таблице демонстрируются случаи, когда numColumns
принимает значение один, два и три.
numColumns = 1 |
numColumns = 2 |
numColumns = 3 |
![]() |
![]() |
![]() |
Если значение поля makeColumnsEqualWidth
- true, все колонки будут созданы одинаковой ширины.
По умолчанию значение - false
.
Если в предыдущем примере с тремя колонками мы бы создали все колонки одинаковой ширины, то получили бы такой результат.
Обратите внимание, что по умолчанию компоненты к колонках выровнены по левой стороне.

Поля marginWidth
, marginHeight
, horizontalSpacing
и verticalSpacing
в GridLayout
работают аналогично одноименным полям в RowLayout
.
Поля marginLeft
и marginRight
сгруппированы в marginWidth
, а marginTop
и marginBottom
сгруппированы
в marginHeight
.
Кроме того, для GridLayout
можно независимо задать horizontalSpacing
и verticalSpacing
, в отличии от RowLayout
, где
используется только одно из двух значений в зависимости от текущего типа RowLayout
.
Поля объекта GridData
GridData
это данные layout, ассоциированные с GridLayout
.
Чтобы передать компоненту объект GridData
, надо использовать метод setLayoutData
.
Например, чтобы передать объект GridData
компоненту Button
, мы используем следующий код:
Button button1 = new Button(shell, SWT.PUSH); button1.setText("B1"); button1.setLayoutData(new GridData()); * This source code was highlighted with Source Code Highlighter.
Конечно, этот код создает объект GridData
с полями, значения которых установлены по умолчанию. Использование объекта со
значениями по умолчанию не имеет смысла, т.к. это равноценно не использованию объекта.
Есть два способа создания объекта GridData
с заданными полями.
Первый способ заключается в непосредственном задании значения полей.
GridData gridData = new GridData(); gridData.horizontalAlignment = GridData.FILL; gridData.grabExcessHorizontalSpace = true; button1.setLayoutData(gridData); * This source code was highlighted with Source Code Highlighter.

Еще одно заключительно замечание: не используйте объекты GridData
повторно.
Каждый компонент, который управляется GridLayout
, должен иметь свой уникальный объект с данными.
Если во время отрисовки у компонента нет данных, для него будет создан уникальный объект GridData
.
Значения полей horizontalAlignment
и verticalAlignment
определяют вертикально и/или горизонтально расположить компонент в ячейке сетки.
Каждое поле может принимать одно из следующих значений:
BEGINNING
CENTER
END
FILL
По умолчанию значение поля horizontalAlignment
- BEGINNING (или по левому краю).
По умолчанию значение поля verticalAlignment
- CENTER.
Давайте вернемся к нашему примеру с пятью кнопками в трех колонках и поэкспериментируем со значением horizontalAlignment
для "Button 5".
horizontalAlignment = GridData.BEGINNING (по умолчанию) |
![]() |
horizontalAlignment = GridData.CENTER |
![]() |
horizontalAlignment = GridData.END |
![]() |
horizontalAlignment = GridData.FILL |
![]() |
Поле horizontalIndent
позволяет сдвинуть компонент вправо на заданное количество пикселей.
Обычно использовать это поле имеет смысл, если поле horizontalAlignment
принимает значение BEGINNING
.
Давайте передвинем "Button 5" из нашего примера на четыре пикселя, вот пример кода:
GridData gridData = new GridData(); gridData.horizontalIndent = 4; button5.setLayoutData(gridData); * This source code was highlighted with Source Code Highlighter.

Поля horizontalSpan
и verticalSpan
позволяют занять компоненту более чем одну ячейку сетки.
Часто эти поля используются совместно с выравниванием FILL
.
Мы можем изменить последний пример, указав "Button 5" занять последние две ячейки:
GridData gridData = new GridData(); gridData.horizontalAlignment = GridData.FILL; gridData.horizontalSpan = 2; button5.setLayoutData(gridData); * This source code was highlighted with Source Code Highlighter.

Если надо сделать "Button 2" шириной две ячейки, то получим следующее:
GridData gridData = new GridData(); gridData.horizontalAlignment = GridData.FILL; gridData.horizontalSpan = 2; button2.setLayoutData(gridData); * This source code was highlighted with Source Code Highlighter.

Чтобы сделать высоту "Button 3" две ячейки, надо написать так:
GridData gridData = new GridData(); gridData.verticalAlignment = GridData.FILL; gridData.verticalSpan = 2; button3.setLayoutData(gridData); * This source code was highlighted with Source Code Highlighter.

Поля grabExcessHorizontalSpace
и grabExcessVerticalSpace
обычно используются для больших компонентов, таких как
Text
, List
или Canvas
. Значения этих полей позволяют компонентам занимать дополнительное место, если
растет их дочерний Composite
.
Если компонент Text
расширяется, поглощая дополнительное место по горизонтали, т.е. пользователь делает окно все шире и шире, то
Text
займет все новое место, размеры других компонентов не изменятся.
Конечно, компоненты, которые получили дополнительное место при расширении окна, первыми будут уменьшены при уменьшении окна.
Проще всего думать о полях grabExcessSpace в контексте изменения размеров.
Вот простой пример, давайте еще раз вернемся к нашему предыдущему примеру с кнопками, где "Button 3" занимает две ячейки во вертикале.
Вот этот пример:
Если мы растянем окно, то единственное, что случится, это окно станет просто больше:
А сейчас мы скажем "Button 3" занять новое место по вертикале и горизонтали, а "B1" и "B4" скажем растянуться вертикально (без захвата нового места). После этих дополнений, изменим размеры окна.
Button button1 = new Button(shell, SWT.PUSH); button1.setText("B1"); GridData gridData = new GridData(); gridData.verticalAlignment = GridData.FILL; button1.setLayoutData(gridData); new Button(shell, SWT.PUSH).setText("Wide Button 2"); Button button3 = new Button(shell, SWT.PUSH); button3.setText("Button 3"); 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("B4"); gridData = new GridData(); gridData.verticalAlignment = GridData.FILL; button4.setLayoutData(gridData); new Button(shell, SWT.PUSH).setText("Button 5"); * This source code was highlighted with Source Code Highlighter.

Сейчас "Button 3" растет в двух направлениях, а "B4" только вертикально. Размеры остальных кнопок не изменяются. Т.к. "Button 3" растет вертикально и заполняет два ряда, последний ряд вытягивается больше. Обратите внимание, на то, что кнопка "B1" не вытянулась, хотя у нее установлено свойство растягиваться. Кнопка сохранила свой размер, т.к. ряд, в котором она находится не изменился. Кнопка "Button 3" растягивается по горизонтали, заполняя свободное место, колонка, в которой она находится, становится шире. Кнопка заполняет все пространство колонки.
Часто в окне типового приложения Вы хотите видеть как минимум один компонент, размеры которого изменяются.
Если свободное пространство пытаются захватить несколько компонентов, то свободное место будет равномерно распределено между компонентами.
Смотрите пример.
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(); } } } * This source code was highlighted with Source Code Highlighter.

При растягивании окна, верхнее однострочное текстовое поле удлиняется, заполняя все доступное пространство по горизонтали,
второе текстовое поле - Text
и поле List
растут, заполняя все свободное пространство по горизонтали и вертикали:
И еще одно последнее замечание о растягивании компонентов.
Если компонент настроен на растягивание по горизонтали (вертикали) и его родительский Composite
становится шире (выше), то и вся колонка, содержащая компонент будет шире (выше).
Практическое применение этого правила заключается в том, что если у компонента, который находится в изменяемом ряду или колонке, стоит режим выравнивания fill, то он будет растягиваться
при растягивании окна. Если же у компонента стоит режим выравнивания beginning, center или end, то он меняться не будет.
Поля widthHint
и heightHint
хранят ширину и высоту компонента в пикселях, считая, что они не противоречат с другими требованиями GridLayout
.
Вернемся еще раз к примеру с пятью кнопками в трех колонках. Допустим, мы хотим сделать "Button 5" 70 пикселей шириной и 40 высотой.
У нас получится следующее:
GridData gridData = new GridData(); gridData.widthHint = 70; gridData.heightHint = 40; button5.setLayoutData(gridData); * This source code was highlighted with Source Code Highlighter.
Первоначальные размеры "Button 5" показаны в окне слева, а справа представлено окно с кнопкой 70 пикселей в ширину, 40 пикселей в высоту.
Примечание. Если бы у "Button 5" значение поля horizontalAlignment
было бы FILL
, то GridLayout
не
мог бы выполнить запрос на установку ширины компонента 70 пикселей.
И еще одно замечание по поводу использования параметров компонента - ширина и высота. Иногда то, что хорошо отображается на одной платформе, плохо подходит для другой. Могут быть отличия между размерами шрифтов и первоначальными размерами компонентов. Это означает, что жестко закодированные значения пикселей не является хорошей практикой.
Пример сложного GridLayout
Пример с GridLayout
был очень прост, он всего лишь показывал, как работают поля.
А сейчас мы объединим все что знаем и сделаем более сложный пример.
Начнем, пожалуй, с того, что набросаем схему окна будущего приложения.
На этой схеме мы отметим, сколько колонок будет в сетке и определим потребует ли какой-нибудь компонент более одной ячейки.
Начнем кодировать пример, по уже готовой схеме. Получившийся код смотрите ниже.
Обратите внимание, что мы добавили немного логики, чтобы сделать пример более интересным.
Например,"Browse..." открывает диалог выбора файла (FileDialog
), чтобы прочитать файл с картинкой (Image
), которая будет отображена на холсте (Canvas
),
По нажатию на кнопку "Delete" картинка будет удалена, "Enter" напечатает текущую картинку и дополнительную информацию.
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 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; } } * This source code was highlighted with Source Code Highlighter.

Если окно будет растянуто, что получится следующее:

Обратите внимание на следующее:
- В форме три колонки и семь рядов.
- Холст(
Canvas
)dogPhoto
растягивается шире и выше, т.к. установлены свойства масштабирования по горизонтали и вертикали (мы не изменяли размеры картинки, хотя могли бы). - Выпадающий список(
Combo
)dogBreed
растягивается по горизонтали, т.к. у него установлено соответствующее свойство и он находится в одной колонке сCanvas
. - Текстовое поле (
Text
)dogName
растягивается по горизонтали, т.к. у него установлено соответствующее свойство и часть текстового поля располагается в колонке с холстом (Canvas
). - Список (
List
)categories
растягивается вертикально, т.к. у него установлено соответствующее свойство, и он располагается в одном ряду с холстом (Canvas
). - Т.е. список (
List
)categories
стал выше, пропала полоса прокрутки (стала ненужной). - Группа (
Group
)ownerInfo
стала шире, т.к. у нее установлено соответствующее свойство, и часть группы располагается в одной колонке с холстом (Canvas
). - Группа (
Group
)ownerInfo
как дочерний классComposite
содержит свойGridLayout
с двумя колонками и двумя рядами. - Текстовые поля (
Texts
)ownerName
иownerPhone
стали шире, т.к. группа (Group
) стала шире, and they are filling and grabbing horizontally in theGroup
'sGridLayout
. - Кнопки (
Buttons
)browse
иdelete
неммного сдвинулись, сохранив при этом ширину. - Кнопка (
Button
)delete
выравнивается по вертикали в верху своего ряда. - Надпись (
Label
) "Categories" выравнивается по центру листа (List
)categories
. - Кнопка (
Button
)enter
Button
выравнивается по горизонтали у правого края третей колонки. - Холст (
Canvas
) создается с указанием точных значений ширины и высоты, т.к. мы хотим по возможности отображать картинку (Image
) 80 x 80 пикселей. - Список (
List
)categories
создан с заданной шириной и высотой, чтобы была возможность показывать двенадцать строчек шрифтом times 12.
FormLayout
FormLayout
работает, создавая FormAttachment
("привязки формы") для каждой стороны компонента и сохраняя их в данных layout'а.
"Привязки" привязывают заданную сторону компонента или к позиции родительского Composite
или к другому компоненту внутри layout'а.
Этот механизм обладает завидной гибкостью, т.к. дает возможность указать положение каждого компонента.
Конфигурационные поля FormLayout
Поля marginWidth
и MarginHeight
в FormLayout
аналогичны одноименным полям в GridLayout
.
Левый и правый зазора определяются marginWidth
, верхний и нижний зазоры - marginHeight
. Зазоры можно определить и для каждого компонента отдельно.
В FormLayout
по умолчанию зазоры равны нулю.
Для задания зазоров надо создать объект FormLayout
и заполнить соответствующие поля. Следующий код устанавливает зазоры до родительского Composite
равными 5 пикселей:
Display display = new Display (); Shell shell = new Shell (display); FormLayout layout= new FormLayout (); layout.marginHeight = 5; layout.marginWidth = 5; shell.setLayout(layout); * This source code was highlighted with Source Code Highlighter.
Поля объекта FormData
Объекты FormData
определяют каким образом каждый компонент будет расположен на FormLayout
.
Каждый объект FormData
определяет привязки для всех четырех сторон компонента.
Привязки задают позицию каждой стороны компонента. Чтобы передать компоненту объект FormData
, надо использовать метод setLayoutData(Object)
, например:
Button button1 = new Button(shell, SWT.PUSH); button1.setText("B1"); button1.setLayoutData(new FormData()); * This source code was highlighted with Source Code Highlighter.
Этот код создает объект FormData
без указания дополнительных параметров.
В этом случае у параметров остаются значения по умолчанию, что нивелируют всю полезность использования FormLayout
.
По умолчанию компонент привязывается к верхнему левому углу родительского Composite
.
Если все компоненты в FormLayout
используют привязки по умолчанию, то все они будут расположены один над другим в верхнем левом углу родительского Composite
.
Поля left
, right
, top
и bottom
описывают объекты FormAttachment
,
которы ассоциируются с левой, правой, верхней и нижней стороной компонента. Эти поля демонстрируются в следующем примере:
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); * This source code was highlighted with Source Code Highlighter.

Объект FormAttachment
определяет привязку стороны компонента.
Есть несколько способов привязки стороны компонента, привязать можно: к позиции в родительском Composite
, к краю Composite
, к ближайшей стороне другого компонента,
к противоположной стороне другого компонента, к центу другого компонента.
Привязывание к позиции располагает компонент таким образом, что относительное расстояние, выраженное в процентах, от компонента до Composite
остается постоянным.
При привязывание к краю Composite
процент принимает значение 0% или 100%.
Привязывая сторону к ближайшей стороне другого компонента, можно быть уверенным, что эта сторона компонента всегда будет ближайшей к другому компоненту.
Привязываясь к противоположной стороне другого компонента, можно быть уверенным, что эта сторона компонента будет выровнена по дальней стороне другого компонента.
И наконец, привязка к центру другого компонента центрирует компонент относительно выбранного другого компонента. Все эти привязывания могут дополняться смещениями.
Поля width
и height
объекта FormData
определяют требуемую ширину и высоту компонента.
Если установленные таким образом ширина и высота противоречат параметрам привязывания, то они не будут использованы.
Хотя с помощью механизма привязки можно косвенно задать ширину и высоту, могут быть случаи, когда вы не захотите задавать параметры для всех четырех сторон.
В таких случаях может быть полезным задать ширину и высоту компонента как показа но ниже:
FormData formData = new FormData(20,30); formData.top = new FormAttachment(0,60); formData.left = new FormAttachment(20,0); button1.setLayoutData(formData); * This source code was highlighted with Source Code Highlighter.
Если Вы хотите задать только ширину или высоту, то вы можете напрямую передать значение в нужное поле объекта FormData
:
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); * This source code was highlighted with Source Code Highlighter.
Обратите внимание, если кнопка привязана к обоим сторонам родительского Composite
, то при измении размера Composite
кнопка тоже будет менять свой размер.
Объекты FormAttachment
FormAttachment
- это объект, который определяет привязывание заданной стороны компонента.
Не всегда обязательно определять этот объект для всех четырех сторон компонента.
Часто, описание одной или нескольких сторон компонента полностью определяет расположение.
Чтобы четко указать расположение компонента, вам надо определить привязку для как минимум одной из сторон left
или right
, и
одной из top
или bottom
.
Если Вы хотите привязать только левую сторону, то позиция компонента будет основываться на положении его левой стороны. В этом случае компонент примет свои естественные размеры (или заданные размеры,
если таковые имеются).
Если Вы не привяжете левый или правый край, то по умолчанию, компонент будет привязан к левой стороне формы. Эти же рассуждения справедливы и для верхней и нижней сторон.
Привязываение к позиции
Есть много видов привязывания. Первый - привязывание компонента к позиции родительского Composite
. Это можно сделать, задав процент в диапазоне от 0 до 100, например:
FormData formData = new FormData(); formData.top = new FormAttachment(50,0); button1.setLayoutData(formData); * This source code was highlighted with Source Code Highlighter.

В этом примере верхний край кнопки устанавливается в позицию, соответствующую 50% высоты родительского
Composite
(в данном случае Shell
), смещение задается 0.
Если размер окна изменится, то верхняя сторона кнопки по прежнему будет находиться на 50%, примерно так:
Если задать дополнительное смещение, то верхняя сторона кнопки будет находиться на позиции 50% высоты Composite
плюс или минус смещение, заданное в пикселях.
Также мы можем задать позицию кнопки, используя произвольный масштаб, например:
FormData formData = new FormData(); formData.top = new FormAttachment(30,70,10); button1.setLayoutData(formData); * This source code was highlighted with Source Code Highlighter.
Если высота Composite
равна 70 единиц, то в этом примере верхний край кнопки будет расположен на 30 единиц ниже верхнего края Composite
,
плюс 10 пикселей (т.е. 3/7 высоты формы плюс 10 пикселей).
Чтобы привязать сторону компонента к краю родительского Composite
, становите позицию, равной 0% или 100%.
Значение 0 расценивается как верхний край Composite
в случае вертикальной привязки и как левый край в случае привязки горизонтальной.
Правый и левый края Composite
соответствуют значению 100%.
Таким образом, если мы хотим привязать компонент к правому краю Composite
, мы просто должны выполнить привязывание со значением 100%:
FormData formData = new FormData(); formData.right = new FormAttachment(100,-5); button1.setLayoutData(formData); * This source code was highlighted with Source Code Highlighter.

В этом примере правая сторона Button
привязывается к правому краю родителя (сейчас это Shell
), со смещением пять пикселей.
Обратите внимание, смещение всегда отсчитывается в одном направлении.
Если Вы хотите задать смещение компонента вниз или вправо, смещение должно быть положительным.
Для смещений компонента вверх или влево смещение должно быть отрицательным.
Даже если размеры окна изменятся, кнопка все равно останется 5 пикселей от правого края:
Привязывание к другому компоненту
Третий тип привязывания - привязывание стороны компонента к другому компоненту этого же родительского Composite
.
Сторона может быть привязана к ближайшей стороне другого компонента (вариант по умолчанию), противоположной стороне другого компонента или
компонент может центрироваться по другому компоненту, при всех вариантах привязывания возможно указать дополнительное смещение.
Наиболее распространенный способ привязывания к другому компоненту - привязывание к ближайшей стороне.
Пример:
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); * This source code was highlighted with Source Code Highlighter.

В этом примере верхняя сторона button2 привязывается к нижней стороне button1.
Обратите внимание, когда размер окна приложения изменяется, button1 сдвинется таким образом, чтобы верхняя сторона располагалась на расстоянии 20% от границы окна.
Button2 переместится таким образом, чтобы верхняя сторона находилась на расстоянии 10 пикселей до нижней стороны (ближайшей) button1.
Привязывание по умолчанию к ближайшей стороне компонента можно изменить, можно создать FormAttachment
и установить привязку к противоположной стороне.
Этот вариант полезен, если компоненты выстраиваются в линию.
В этом случае надо использовать выравнивание TOP
, BOTTOM
, LEFT
или RIGHT
, например:
formData2.top = new FormAttachment(button1,0,SWT.TOP);
В следующем примере верхняя сторона button1
располагается на расстоянии 20% от границы окна.
Верхняя сторона button2
в соответствии с режимом TOP
выравнивается по верхней стороне button1
.
Это означает, что верхняя сторона button2
тоже находится на 20% от границы окна.
Обратите внимание, если задать выравнивание только по высоте, то будет определено вертикальное расположение компонента.
Для button2
требуется установить еще и выравнивание левой стороны, иначе кнопки будут расположены друг на друге.
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); * This source code was highlighted with Source Code Highlighter.

Последний способ привязать компонент к другому компоненту - центрировать по другому компоненту.
Эту возможность можно применить с пользой, если размеры компонентов отличаются.
В этом случае надо выполнить привязывание с выравниванием CENTER
, см. пример:
formData.top = new FormAttachment(button1,0,SWT.CENTER);
Таким образом, верхняя сторона компонента будет расположена таким образом, что сам компонент будет выровнен по центру другого компонента, смещение 0. Центрирование верхней, нижней стороны или двух сразу приведет к одному результату. Задание выравнивания для верхней стороны не имеет смысл, если весь компонент центрирован, см. пример:
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); * This source code was highlighted with Source Code Highlighter.

Наличие различных типов FormAttachment
позволяет описывать layout'ы многими различными способами.
FormLayout
решает задачи, которые не могут быть решены с помощью FillLayout
,
RowLayout
или GridLayout
, это делает его очень полезным классом.
Важное замечание: Не создавайте циклические привязывания. Например, не привязывайте правую сторону button1 к левой стороне button2 и потом левую сторону button2 к правой button1. Циклическое переназначение противоречит ограничениям layout'а и служит причиной неопределенного поведения и непредсказуемого результата. Поэтому не допускайте зацикливание.
Пример FormLayout
Во всех предыдущих примерах FormLayout
присутствуют одна или две кнопки для демонстрации работы FormAttachment
.
В следующем примере мы продемонстрируем использование нескольких кнопок, чтобы более полно продемонстрировать возможности привязывания.
Начнем с того, что нарисуем схему.
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); * This source code was highlighted with Source Code Highlighter.
button1
и button2
, они будут привязаны к верхней границе окна.
С помощью определения равных процентов и смещений центрируется button3
.
Кнопки button4
и button5
привязаны к нижней границе окна со смещением пять пикселей.
После растягивания окна становится понятна роль привязывания.
Кнопка button1
привязана к левой с правой сторонам, поэтому при увеличении окна она растягивается.
Обратите внимание, что правая сторона всегда находится на расстоянии 25% от правой стороны окна.
Тот же результат мы видим и для button2
, т.к. привязаны две стороны.
Левая сторона привязана к button1
, поэтому она всегда будет находится на 25% плюс пять пикселей.
Кнопка button3
остается в центре окна по горизонтали. Кнопка button4
привязана в верхней и нижней границам,
поэтому она вытягивается вертикально при растяжении окна, однако она привязана только к левой границе, поэтому и не растягивается горизонтально.
Кнопка button5
не меняется в размерах, но всегда находится пять пикселей правее button4
и пять пикселей выше нижней границы.

Сложный пример FormLayout
Чтобы продемонстрировать как FormLayout
может применяться в более сложных случаях, переделан пример Dog Show Entry, сделанный для GridLayout
.
Расположение компонентов на форме осталось прежним, но изменились средства расположения.
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; } } * This source code was highlighted with Source Code Highlighter.

Если растянуть окно приложения, компоненты изменятся точно так же как и в примере GridLayout
.
Написание собственного класса Layout
Кода-нибудь вам может понадобиться сделать свой собственный класс Layout
.
Это может потребоваться для реализации сложной формы.
Возможно, в нескольких местах требуются одинаковые формы и есть возможность получить преимущества повторного использования ранее написанного кода.
Или Вы хотите сделать очень эффективный класс.
Независимо от причин, есть несколько моментов, о которых надо помнить перед тем как написать свой класс:
- Возможно ли сделать форму, используя
GridLayout
илиFormLayout
, может быть используя вложенные формы? - Можно ли получить требуемый эффект более просто, используя resize listener?
- Вы разрабатываете общий алгоритм или просто размещаете компоненты?
Если Вы не создаете общий алгоритм, который будет использоваться в нескольких Composite
, то часто значительно проще и лучше посчитать размеры и позицию компонентов в resize listener.
Многие пользовательские компоненты SWT написаны именно таким способом.
Хотя новый компонент может быть реализован как пара Composite
/Layout
, проще будет реализация в виде Composite
,
который имеет свой layout в resize listener, и рассчитывает свои предпочтительные размеры функцией computeSize
.
Такой упрощенный способ не предполагает написание дополнительного кода.
Прежде всего, мы посмотрим на то как работают layout'ы, после чего создадим новый класс Layout
.
Другой пример написания своего класса Layout
можно найти в секции Compound Widget Example руководства
Creating Your Own Widgets Using SWT,
к котором рассказывается как достичь тот же самый внешний вид используя resize listener или новый класс Layout
.
Как работает Layouts
Layout
- это абстрактный суперкласс для всех layout'ов.
В его состав входят всего два метода: computeSize
и layout
.
Класс определен следующий образом:
public abstract class Layout { protected abstract Point computeSize(Composite composite, int widthHint, int heightHint, boolean flushCache); protected abstract void layout(Composite composite, boolean flushCache); } * This source code was highlighted with Source Code Highlighter.
Метод computeSize
вычисляет ширину и высоту прямоугольника, который включает все компоненты Composite
, которым назначены размеры и
которые помещены в Composite
в соответствии с алгоритмом, описанном в классе Layout
.
Дополнительные параметры управляют ограничениями ширины и/или высоты. Например, layout может расти в одном направлении, если его рост ограничен в другом.
Дополнительные параметры SWT.DEFAULT означает использовать предпочтительный размер.
Метод layout
размещает и задает размер дочерних компонентов Composite
.
Layout
может кэшировать внутреннюю служебную информацию, например, предпочтительное расширение каждого компонента.
Параметр flushCache
запрещает Layout
кэшировать данные.
Т.к. Layout
управляет размером и размещением компонентов Composite
, есть несколько методов в Composite
котрые используются с Layout
.
Первые два метода позволяют устанавливать и получить объект Layout
в Composite
.
public void setLayout(Layout layout); public Layout getLayout(); * This source code was highlighted with Source Code Highlighter.
Приложение может явным образом выполнить метод Layout
родительского Composite
, чтобы пересчитать размеры и положение компонентов.
public void layout(boolean changed); public void layout(); // calls layout(true); * This source code was highlighted with Source Code Highlighter.
Выполнить метод Layout
потребуется после действий, которые могли привести к изменению компонента. Например, изменение размеров или позиций, изменение шрифтов, изменение текста или
картинки, добавление новых компонентов, изменение шрифта или текста многострочного компонента с прокруткой (Text
).
Если эти действия были выполнены программно, то они не генерируют соответствующее событие.
Поэтому, родитель ничего об этом не знает и требует явного вызова метода layout
, который бы сообщил, что что-то произошло.
Необходимость вызова layout
обусловлена тем, что приложение может выполнить несколько изменений и только после их завершения сообщить родителю, таким образом, все
компоненты перерисуются одновременно.
Если layout()
не вызывался, а изменения были сделаны после того как окно было открыто, компоненты могут быть некорректно расположены до тех пор пока окно не будет перерисовано.
Обратите внимание, что shell.open()
вызывает метод layout
.
Метод computeSize
объекта Composite
вычисляет предпочтительный размер Composite, который является размером клиентской области, определяемой Layout
плюс дополнительный зазор.
public Point computeSize(int widthHint, int heightHint, boolean changed); public Point computeSize(int widthHint, int heightHint); // calls computeSize(widthHint, heightHint, true); * This source code was highlighted with Source Code Highlighter.
Поле СlientArea объекта Composite
- это прямоугольник, который содержит все дочерние компоненты.
Метод Layout
позиционирует компоненты внутри клиентской области.
public Rectangle getClientArea ();
Поле trim объекта Composite
- это область за границами клиентской области.
Для некоторых компонентов размер trim равен нулю.
Trim можно вычислить, передав размеры клиентской области в метод computeTrim
.
public Rectangle computeTrim (int x, int y, int width, int height);
Выполнене метода pack
объекта Composite
приведет к установке предпочтительного размера.
public void pack(boolean changed); // calls setSize(computeSize(SWT.DEFAULT, SWT.DEFAULT, changed)); public void pack(); // calls pack(true); * This source code was highlighted with Source Code Highlighter.
У методов layout
, computeSize
и pack
есть параметр типа boolean - флаг changed
.
Если значение true
, то это говорит о том, что компоненты Composite
изменились, и это изменение влияет на их предпочтительный размер,
поэтому, возможно, следует очистить кэш.
Когда изменяются размеры Composite
, он просит свой Layout
перерисовать компоненты, вызвав layout(false);
поэтому кэш клиентской области не обнуляется.
Описанный механизм позволяет Layout
выполнять ресурсоемкую операцию только в случае необходимости.
Кэширование данных улучшает быстродействие, но вместе с тем усложняет логику работы. Аппарат кэширования можно отключить. Лучше не использовать кэширование, пока код нестабильный. Если используете кэш, то убедитесь, что не храните состояние компонента, такие как текст надписи или количество записей в листе.
Пример пользовательского Layout
Если в приложении есть несколько компонентов Composite
, ориентированных вертикально, возможно создание ColumnLayout
.
Ниже будет продемонстрирована простая версия класса Layout
, который размещает компоненты Composite
в одну колонку.
Этот класс устраняет промежутки и зазоры.
Всем компонентам передается одна ширина, высота у компонентов остается своя.
(Обратите внимание, RowLayout
будет работать как ColumnLayout
, если его тип установлен в SWT.VERTICAL
.
Этот пример, тем не менее, просто пример. Если на практике Вам понадобится расположить компоненты в колонку, используйте RowLayout.
)
Код для класса ColumnLayout
приведен ниже.
Обратите внимание, кэшируются ширина самого широкого компонента и сумма высот компонентов (плюс промежутки), эти значения используются для
вычисления размера и расположения компонентов. Значения пересчитываются, если flushCache
установлен в true
.
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; } } * This source code was highlighted with Source Code Highlighter.
Ниже приведен простой код для тестирования ColumnLayout
.
Кнопки grow и shrink демонстрируют метод layout()
класса Shell
, который вызывается для перерисовки формы после
изменения ширины одного из компонентов.
Вызов layout()
аналогичен вызову layout(true
), который сообщает ColumnLayout
,
что нужно очистить кэш перед установкой границ компонентов.
После расположения компонентов выполняется метод pack()
класса Shell
, который изменяет размеры окна приложения.
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(); } } } * This source code was highlighted with Source Code Highlighter.
После запуска приложения появится окно, приведенное ниже слева.
Нажатие на "Grow Button 3" приведет к результатам, показанным справа. Изменение размеров экрана с помощью мыши тоже приведет к тому, что кнопки станут или шире или уже, но их высота не изменится.
Переопределение Composite
Если Вы разрабатываете свой компонент, как это показано в Creating Your Own Widgets Using SWT, и вашим родительским классом является Composite, то обратите внимание на следующие аспекты:
- Если Вы предлагаете функции обрезания в новом
Composite
, проверьте, что переопределяете иcomputeTrim()
иgetClientArea()
. - Никогда не переопределяйте
layout()
, однако есть возможность переопределитьlayout(boolean)
.
Иногда может потребоваться предать Composite
особое отображение, и при этом надо запретить приложению задавать свой layout.
Новый Composite
будет или использовать layout в обработчике изменения размера или использовать внутренний пользовательский layout.
В любом случае может потребоваться сделать следующее:
- Переопределить
setLayout()
, чтобы он не делал ничего. - Переопределить
layout(boolean)
, чтобы он вызывал Ваш код. - Переопределить
computeSize()
, чтобы он корректно вычислял размер ВашегоComposite
.
Заключение
SWT предлагает несколько способов расположения компонентов в форме.
Самый простой и в то же время наиболее часто используемый способ: применение стандартных классов Layout
: FillLayout
, RowLayout
,
GridLayout
и FormLayout
.
В некоторых случаях, Вам потребуется разработать свой собственный класс Layout
, чтобы получить специфическое расположение компонентов или
повторно использовать код формы
Внимание.
Комментировать могут только зарегистрированные пользователи.
Возможно использование следующих HTML тегов: <a>, <b>, <i>, <br>.
Аноним | Aug 25, 2010 10:12:21 AM | |
Я встречал перевод слова, которое я не могу здесь написать из-за защиты от спама, но в русском транслите приобретающее вид "лэйаут" как "менеджер расположения". | ||
kry4a | Apr 7, 2011 10:59:45 AM | |
Отличная статья, очень помогла! Большое спасибо! | ||