Материал: Портянкин И. Swing

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Искусство расположения

195

Кнопки расположены именно так, как мы и ожидали, а вот с текстовым полем возникла некоторая проблема. Оно не растянуто на всю оставшуюся ширину окна, несмотря на то, что мы подходящим образом задали поля gridwidth и fill, и все равно осталось предпочтительного размера (длиной в 10 символов ввода, что мы задали в конструкторе для поля JTextField). Более того, если вы сделаете окно меньше, вы увидите, что при нехватке пространства поле сжимается до минимального размера «прыжком», не уменьшая свои размеры плавно. Пример показывает нам, что по умолчанию менеджер GridBagLayout ведет себя следующим образом:

компоненты имеют предпочтительный размер и не более

компоненты располагаются в центре контейнера

при нехватке места компонент уменьшается до минимального размера (у кнопок он совпадает с обычным, и «прыжка» нет, а у текстового поля зависит и от текста, в нем содержащегося, попробуйте набрать в нем текст и снова поменять размер окна).

Как же заставить GridBagLayout делать компоненты больше их предпочтительного размера? Для этого есть пара несколько загадочных полей, определяющих так называемый «вес» ячейки.

weightx, weighty — дробные числа, которые указывают менеджеру, сколько свободного места из контейнера следует отдавать ячейке, если таковое имеется. Числа эти нормированные — менеджер GridBagLayout собирает все значения из всех ячеек и вычисляет процент от максимального значения. Как правило, в качестве максимального значения используется единица — это 100% отдача всего свободного пространства ячейке. Числа от нуля до единицы будут означать проценты от свободного пространства, которые отдаются ячейке. Ну а по умолчанию значения полей weightx и weighty равны нулям, а это значит, что ячейка никогда не станет больше предпочтительного размера своего компонента и отступов. Интересным свойством полей weightx и weighty является и то, что если в контейнере, управляемом GridBagLayout, содержится хотя бы одна ячейка, в которой значения этих полей отличны от нуля, GridBagLayout займет все место контейнера. В противном случае будет занято лишь то место, которого достаточно для предпочтительных размеров компонентов, причем все компоненты будут располагаться по центру контейнера, что мы уже имели возможность наблюдать в нашем первом примере.

Теперь, кажется, понятно, что же мы упустили. Если мы хотим чтобы текстовое поле занимало весь остаток места по горизонтали, нам нужно установить поле weightx в 100%, в зависимости от того, какое число вы используете в качестве максимума. Если принять за 100% единицу, то дополнительная строчка кода будет выглядеть так:

textFieldConstraints.weightx = 1.0f;

А результат будет таков:

196

ГЛАВА 7

Изменяя размеры окна, вы сможете убедиться в том, что текстовое поле занимает всю его ширину, и размер его больше не «прыгает» и не зависит от количества набранного в нем текста.

Идея GridBagLayout не так и сложна, и большинство расположений довольно легко поддаются ему. Однако основной проблемой является объект для описания ячеек GridBagConstraints. Названия его полей не всегда ясны (к примеру, gridwidth и weight довольно двусмысленны), а значения для них подстегивают к добавлению к коду цифр (уже упомянутые поля, плюс поле insets), и мы сталкиваемся с проблемой «волшебных чисел», когда цифры в коде не несут очевидной смысловой нагрузки. Огромное количество объектов GridBagConstraints и кода для их настройки, щедро перемешанные с инициализацией компонентов и деловой логикой программы, становятся настоящей головной болью. Поэтому, если есть возможность, всегда стоит отказываться от чистого, «без примесей» расположения GridBagLayout, в пользу описанного нами выше подхода из нескольких вложенных панелей с блочным расположением BoxLayout. Кода будет не больше, а его смысл и простота его чтения намного лучше.

Впрочем, менеджер GridBagLayout является стандартной частью JDK и навсегда там останется. Если вы пишете интерфейс не с нуля и вынуждены поддерживать существующий код с применением GridBagLayout, или не можете переключиться на подход с сложенными воедино «полосками», по крайней мере стоит упростить задачу и сделать чтение и поддержку кода проще, все же не отказываясь от GridBagLayout.

Основная проблема состоит в том, что уровень объекта GridBagConstraints слишком низок, и вместо очевидных операций (растянуть компонент, добавить промежуток между компонентами, выровнять его по левому краю), мы манипулируем полями, и эффект от этих действий не «прочитать» напрямую. Но можно создать вспомогательный объект для работы с GridBagConstraints, и вызывая его методы, намного улучшить и упростить нашу работу с менеджером GridBagLayout. Давайте попробуем:

//com/porty/swing/GridBagHelper.java

//Вспомогательный класс, позволяющий писать

//качественный код для расположения GridBagLayout package com.porty.swing;

import javax.swing.*; import java.awt.*;

public class GridBagHelper {

//координаты текущей ячейки private int gridx, gridy;

//настраиваемый объект GridBagConstraints private GridBagConstraints constraints;

//возвращает настроенный объект GridBagConstraints public GridBagConstraints get() {

return constraints;

}

//двигается на следующую ячейку

public GridBagHelper nextCell() { constraints = new GridBagConstraints();

Искусство расположения

197

constraints.gridx = gridx++; constraints.gridy = gridy;

// для удобства возвращаем себя return this;

}

//двигается на следующий ряд public GridBagHelper nextRow() {

gridy++; gridx = 0;

constraints.gridx = 0; constraints.gridy = gridy; return this;

}

//раздвигает ячейку до конца строки public GridBagHelper span() {

constraints.gridwidth = GridBagConstraints.REMAINDER; return this;

}

//заполняет ячейку по горизонтали

public GridBagHelper fillHorizontally() { constraints.fill = GridBagConstraints.HORIZONTAL; return this;

}

// вставляет распорку справа

public GridBagHelper gap(int size) { constraints.insets.right = size; return this;

}

… остальные вспомогательные методы

}

Класс GridBagHelper весьма прост. Он работает с объектом GridBagConstraints, настраивая его поля и скрывая некоторые не очень очевидные цифры и манипуляции с полями, вместо этого предоставляя нам весьма понятные методы, к примеру, nextRow() переходит на первую ячейку следующей строки таблицы, а gap() вставляет распорку справа от компонента. Для работы с этим объектом нужно создать его экземпляр, для каждой новой ячейки вызывать метод nextCell() (именно там создается новый объект GridBagConstraints), а в конце, когда настройка компонента проведена, получить результат методом get(). Для того чтобы вызывать методы было удобнее, каждый из них возвращает ссылку на объект GridBagHelper, поэтому методы можно вызывать «в ряд».

В качестве маленького примера можно привести надпись и текстовое поле, с распоркой в 5 пикселей между ними:

GridBagHelper helper = new GridBagHelper(); helper.nextCell().gap(5);

frame.add(new JLabel("Имя:"), helper.get());

198

ГЛАВА 7

helper.nextCell().span(); frame.add(new JTextField(20));

При запуске мы получим такой результат:

Мы получим и распорку, не работая со всеми четырьмя размерностями объекта Insets, и скажем полю занять все оставшиеся ячейки более понятным методом span(), и прозрачный способ перехода по ячейкам. В полной версии класса GridBagHelper, которую вы можете найти в исходных текстах программ книги, есть и много других полезных методов с простыми названиями. Такой код не только во много раз быстрее писать, но и легче читать и обновлять. В будущем, если вы решите применить на практике GridBagLayout, класс GridBagHelper или его эквиваленты будут весьма кстати, а применять напрямую запутанный класс GridBagConstraints вряд ли можно рекомендовать. Чуть позже в этой главе, когда мы разработаем диалоговое окно для входа в систему, мы еще раз применим новый класс.

Быстрый язык MigLayout

Сама идея расположения большинства объектов интерфейса по сетке, придавая им разное выравнивание, размеры, и, возможно, количество ячеек сетки, занимаемое ими, позволяет быстро набросать интерфейс, даже не имея никаких визуальных инструментов. Некогда, до достаточного развития таблиц стилей CSS, именно так разрабатывалось расположение содержимого многих Web-страниц.

Идея эта была реализована в Java с помощью GridBagLayout, однако способ указания всех атрибутов расположения компонентов в сетке неудачна, количество объектов огромно, и код уже через пять минут напоминает неудавшееся спагетти. Поддерживать подобный код крайне тяжело.

Однако сама идея неплоха, остается лишь более быстрым и удобным языком указывать менеджеру расположения, как и где должны располагаться компоненты в сетке. Мы уже создали вспомогательный класс, предлагающий удобными методами руководить действиями GridBagLayout. Также одним из удачных примеров реализации такого менеджера является бесплатно распространяемый менеджер MigLayout.

По сути, вспоминая наше рассмотрение табличного менеджера GridBagLayout и его основных концепций, можно сказать, что в целом мы представляем себе, как работает MigLayout. Другим способом осуществляется лишь описание ячеек таблицы — в виде строчек очень компактного, насыщенного смыслом текста, что без сомнения ускоряет процесс создания интерфейса. Вы добавляете компоненты примерно таким методом — add(компонент, “описание ячейки”). Более того, MigLayout обладает несравненно более широким набором функций, в сравнении со стандартными менеджерами Java. Дальнейшее знакомство мы проведем с помощью примера, тем более что язык MigLayout достаточно легко понять.

//MigLayoutStart.java

//Знакомство с MigLayout

Искусство расположения

199

import net.miginfocom.swing.MigLayout; import javax.swing.*;

public class MigLayoutStart extends JFrame { public MigLayoutStart() {

super("MigLayoutStart");

//выход при закрытии окна setDefaultCloseOperation(EXIT_ON_CLOSE);

//устанавливаем менеджер расположения setLayout(new MigLayout());

//добавляем компоненты с описанием ячеек add(new JLabel("Имя:"), "gap, sg 1"); add(new JTextField(10), "wrap");

add(new JLabel("Фамилия:"), "gap, sg 1"); add(new JTextField(10), "wrap");

//выведем окно на экран

pack();

setVisible(true);

}

public static void main(String[] args) { SwingUtilities.invokeLater(

new Runnable() {

public void run() { new MigLayoutStart(); } });

}

}

Мы создаем окно и устанавливаем для него расположение MigLayout. Далее в окно добавляется две надписи JLabel, описывающие текстовые поля для ввода данных, и сами текстовые поля — типичный пример простого интерфейса. Для гармонии надписи обычно делают одинаковых размеров. Обратите внимание, как описываются ячейки при добавлении компонентов: в виде простой строки, команды разделяются запятыми, параметры команд следуют через пробел. Запустив пример, мы без особых усилий получим желаемый результат. Конечно же, для запуска вам понадобится скачать библиотеки MigLayout и добавить их в свой путь CLASSPATH, их легко найти на официальном сайте продукта в Интернете.

Мало того что мы в мановение ока сделали размеры надписей одинаковыми, выровняли надписи и текстовые поля, так еще в качестве приятного сюрприза MigLayout на

Источник: https://studfile.net/preview/15935977/