Понимание раскладок компонентов (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.
Вот как выглядит окно после того как Mary Smith ввела Bifford в поле "dog":

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

Обратите внимание на следующее:

  • В форме три колонки и семь рядов.
  • Холст(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 the Group's GridLayout.
  • Кнопки (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.
Вот что получаетя, после того как Mary Smith заполнит форму:

Если растянуть окно приложения, компоненты изменятся точно так же как и в примере 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, чтобы получить специфическое расположение компонентов или повторно использовать код формы

Метки: Java   SWT  

Комментарии.

Внимание.
Комментировать могут только зарегистрированные пользователи.
Возможно использование следующих HTML тегов: <a>, <b>, <i>, <br>.

Аноним Aug 25, 2010 10:12:21 AM
Я встречал перевод слова, которое я не могу здесь написать из-за защиты от спама, но в русском транслите приобретающее вид "лэйаут" как "менеджер расположения".
 
kry4a Apr 7, 2011 10:59:45 AM
Отличная статья, очень помогла! Большое спасибо!
 
Яндекс цитирования Ðåéòèíã@Mail.ru Rambler's Top100