Шаг 6 - Работа с Layout Manager
Загрузить проект
Немного теории
В Java нельзя размещать компоненты в окнах с точно задаными координатами. Что значит нельзя? Технически это возможно и в ряде случаев даже имеет смысл. Например если вы пишете приложение для одной ОС и кросплатформенность его вас не интересует. Но в общем случае вы не можете знать под чем и на каком железе будет исполняться ваше приложение и поэтому не можете размещать компоненты указав точные координаты, а для апплетов это вообще крайне нежелательно. В то же время система Layout Manager способна динамически вибирать размеры компонент и размещать их при различных условиях работы. Это дает некоторую уверенность в том что вашы компоненты на экране пользователя независимо от его железа, размера системного шрифта и др. будут размещены так как вы это планировали. Еще один аргумент: кто програмировал под Win знает какие проблемы возникают с диалоговымы окнами при изменении их размеров (если такое не запретить) - надо обработать сообщение, переместить компоненты на новые места и т.д. В принцыпе система Layout Manager избавляет от этих забот.
При работе с Layout Manager вы выбираете один из его режимов размещения компонет, устанавливаете его для своей панели (окна апплета, приложения) и далее добавляете в панель компоненты используя разновидность метода add для этого режима. Перечислим основные режимы:
- FlowLayout - режим, при котором компоненты размещаются слева на право и сверху вниз построчно (как текст) по мере их добавления. При этом вы не можете знать, например, сколько компонент в какой строке окажется - это решает сама система исходя из размеров компонент и размеров панели на которую они помещаются. В конструкторе класса реализирующего этот режим вы можете задать выравнивание компонент с помощью статических констант класса FlowLayout.CENTER, FlowLayout.LEFT, FlowLayout.RIGHT (по умолчанию первый). Кроме того там же можно указать горизонтальный и вертикальный зазор можду компонентами (по умолчанию он равен 5-ти пикселям). Этот режим размещения компонет мы и использовали в предыдущем шаге, помните в коде для Swing -компонент строку getContentPane().setLayout(new FlowLayout()); ею мы устанавливали для нашего апплета режим FlowLayout. А в коде под AWT компоненты мы этого не делали потому как для класса Applet он установлен по умолчанию, а вот для класса JApplet по умолчанию стоит BorderLayout а нам нужна была схожесть.
- BorderLayout - С помощью этого режима вы можете разместить всего 5 компонент - 4 по сторонам мира и одну в центре, но зато он дает некоторую определенность в каком направлении от центра будет ваша компонента. Задают направление передав методу add в качестве параметра одну из статических констант соответствующего класса : BorderLayout.NORTH, BorderLayout.SOUTH, BorderLayout.EAST, BorderLayout.WEST, BorderLayout.CENTER. Опять же зазор можно задать (по умолчанию он равен 0). Ну насчет всего 5 - это я образно, вы можете разместить компоненты в панели JPanel (для AWT - Panel) а уже их размещать в главное окно приложения (апплета), мы так будем делать в сегодняшнем примере. Кроме того можно помещать панель в панель и так почти до бесконечности разместив таким образом какое угодно количество компонент с помощью режима BorderLayout.
- GridLayout - в этом режиме компоненты размещаются в ячейки таблыцы, причем ячейки эти одинакового размера. Сколько строк и столбцов будет в этой таблице вы задаете в конструкторе, там же задают зазор (0 по умолчанию). Одиному из параметров (но не обеим одновременно) которые задают количество строк и столбцов можно задать 0, что будет значить что в строке или столбце соответственно может содержаться любое количество элементов. Зополнение таблицы идет слева на право и сверху вниз как в FlowLayout но здесь вы точно знаете где в каком ряду какая компонента окажется.
- GridBagLayout - более функциональный аналог режима GridLayout. Здесь ячейки могут быть разного размера, компоненты могут иметь разные зазоры, занимать несколько ячеек и еще много чего. Это самый мощный режим и в то же время самый сложный (ну это не я так утверждаю - так в книжках пишут). При его использавании вы должны для каждого компонента передать классу этого режима условия (данные) как размещать этот компонент. С этой целью существует вспомогательный класс GridBagConstraints который является не больше как обгорткой для данных необходимых для размещения компонент. Это не значит что такой класс надо создавать для каждой компоненты - его можно создать один раз и далее меняя его поля устанавливать его для каждой компоненты с помощью метода setConstraints(ourComponent, ourGridBagConstraints) класса GridBagLayout. Рассмотрим поля которые вы можете задать в классе GridBagConstraints:
- gridx, gridy - номер колонки и ряда ячейки(номер первой - 0) в которой будет размещен левый верхний угол компоненты (или вся компонента, если она занимает одну ячейку).
- gridwidth, gridheight - указывает сколько ячеек по горизонтали и вертикали будет занимать компонента (по умолчанию равны 1). Кроме того можно использовать константы GridBagConstraints.REMAINDER и GridBagConstraints.RELATIVE чтобы указать что компонента будет последней в ряду (колонке) или следующей после существующей в ряду (колонке) соответственно.
- fill - в этом поле вы можете задать что делать с компонентой в случае если выделенная область бальше ее размера. Константы здесь следующие: NONE - не увеличивать компоненту (стоит по умолчанию), HORIZONTAL - расширить компоненту чтоб ее ширина равнялась горизонтальному размеру выделенной области, VERTICAL - расширить компоненту чтоб ее высота равнялась вертикальному размеру выделенной области, BOTH - увеличить оба размера компоненты до размеров области.
- ipadx, ipady - здесь можно задать на колько увеличить размеры компоненты по сравнению с минимальным. Например, ширина компоненты будет равняться minXsize+ipadx*2 пикселей.
- insets - это поле характеризирует внешний зазор компоненты, представлено оно объектом Insets. Поля у этого обьекта следующие: top, left, bottom, right.
- anchor - служит для задания места в выделенной компоненте области если компонентя меньше этой самой области. Значения может прринимать такие: CENTER(по умолчанию),NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST и NORTHWEST (это статические константы класса GridBagConstraints).
- weightx, weighty - эти поля служат для того чтобы сообщить системе как распределить место между колонками и рядами соответственно (вроде приоритета при разделе места). Обычно принимают значения от 0.0 (по умолчанию) до 1.0.
- CardLayout - режим при котором можно создать набор панелей как бы в одном месте и отобрадать по очереди с помощью соответствующих методов. Мы не будем пробовать его в сегоднешнем примере так как для его использования нужно обрабатывать события.
- BoxLayout - этот режим размещает компоненты в один ряд слева направо или сверхзу вниз по вашему выбору. Выбираете вы передав конструктору одну из констант BoxLayout.Y_AXIS или BoxLayout.Х_AXIS, кроме того в конструкторе нужно указать для какого контейнера создается режим. Режим BoxLayout присутствует только библиотеке JFC/Swing в отличие от всех предыдущих режимов которые размещены в библиотеке AWT, поэтому вы не можете использовать его при написании GUI под AWT - компоненты.
Думаю, что комбинируя вышеперечисленные режимы и возможности JPanel можно разместить компоненты любым образом.
Сегодня напишем приложение с использованием JFC/Swing (неконсольное конечно). Для этого унаследуем свой главный класс от класса JFrame. Вот его родословная:
java.lang.Object -> java.awt.Component -> java.awt.Container -> java.awt.Window -> java.awt.Frame -> javax.swing.JFrame
С первыми тремя объектами мы уже знакомы. Класс Window представляет собой окно без меню и рамки и содержит основные методы для работы с окнами. Главная его роль быть базовым для более используемых окон Frame, Dialog и др. Его потомок Frame служит для создания окон которые имеют строку названия, рамку, могут иметь меню. Класс JFrame Swing-аналог своего AWT-родителя.
В нижеприведенном примере мы разместим в окне главного приложения 6 панелей одну под одной, установив для каждой свой режим размещения компонент, и добавим в каждую панель несколько кнопок (в одной из панелей обойдемся без Layout Manager задав компонентам конкретные координаты и посмотрим что из этого выйдет). Посколько кнопок нам понадобиться много создадим их целый массив. Для этого разберемся как создаются в Java массивы.
Массивы в Java являются объектами специального встроенного класса. Для их создания используют оператор []. Чтобы определить размер массива этот клаас содержит поле length.
Пример создания одномерного массива объектов:
AnyObject[] matrixOfAnyObj; //обьявляем массив matrixOfAnyObj
matrixOfAnyObj=new AnyObject[5]; //создаем массив размером в 5 элементов
//создадим объекты - элементы массива в цыкле
//индекс первого элемента массива == 0
for (int i=0; i<5; i++) matrixOfAnyObj[i]=new AnyObject();
Можно создать массив и без оператора new проинициализировав его. Пример:
String[] = {"строка 1", "вторая строка", "третья", "и т.д."};
Многомерные массивы создаются как массивы массивов. Например чтоб создать трехмерный массив создаем его как массив двумерных массивов а те в свою очередь являются массивами одномерных массивов. Каламбуры какие-то получаются, лучше посмотрим пример создания трехмерного массива:
int[][][] NumMatrix=new int[2][3][4]; //обьявим и создадим трехмерный массив
NumMatrix[0]=new int[3][4]; //создаем соответствующие двумерные массивы
NumMatrix[1]=new int[3][4]; //их размер может быть неидентичным
//но я выбрал одинаковый размер чтоб создать одномерные массивы в цыкле, сделаем это:
for(int i=0; i<2;i++)
{
for(int j=0; j<3; j++)
{
NumMatrix[i][j] = new int [4];
}
}
К сказанному пожалуй нужно добавить что в Java происходит контроль выхода за границы массива и если что то не так - создается исключение.
Создаем код
import java.awt.*;
import javax.swing.*;
public class LayoutApplication extends JFrame
{
//Обьявляем массивы кнопок и панелей
JButton[] ArrOfButton;
JPanel[] ArrOfPanel;
public LayoutApplication()
{
//позволим конструктору предка выполнить всю черновую работу
//по созданию окна с нашим заголовком
super("OurApplication");
//создем массивы
ArrOfButton=new JButton[25];
ArrOfPanel=new JPanel[6];
//создаем кнопки и панели как элементы масивов
for (int i=0; i<25;i++)
{
ArrOfButton[i]=new JButton("Butt No "+String.valueOf(i+1));
}
for (int i=0; i<6;i++)
{
ArrOfPanel[i]=new JPanel();
}
//получаем из нашего окна панель которая будет размещать компоненты
//и установим для нее GridLayout, при его создании задаем в параметрах
//6 строк 1 столбец и зазор 5 пикселей
//в эту панел мы будем добавлять панели с кнопками
Container ContPane=getContentPane();
ContPane.setLayout(new GridLayout(6,1,5,5));
/////////////
//для первой панели зададим FlowLayout (хоть он там и по умолчанию
//стоит:)) о окружим эту панель черной рамкой, чтоб отличать панели
//я решил им рамки разноцветные сделать.
//После чего додадим в цыкле пару кнопок
ArrOfPanel[0].setLayout(new FlowLayout());
ArrOfPanel[0].setBorder(BorderFactory.createLineBorder(Color.black));
for (int i=0; i<5;i++)
{
ArrOfPanel[0].add(ArrOfButton[i]);
}
///////////////////////////////
//У этой панели будет BorderLayout и красная рамка
//обратите внимание на функцию add
ArrOfPanel[1].setLayout(new BorderLayout());
ArrOfPanel[1].setBorder(BorderFactory.createLineBorder(Color.red));
ArrOfPanel[1].add(BorderLayout.NORTH, ArrOfButton[5]);
ArrOfPanel[1].add(BorderLayout.SOUTH, ArrOfButton[6]);
ArrOfPanel[1].add(BorderLayout.WEST, ArrOfButton[7]);
ArrOfPanel[1].add(BorderLayout.EAST, ArrOfButton[8]);
ArrOfPanel[1].add(BorderLayout.CENTER, ArrOfButton[9]);
////////////////////////////////
//здесь GridLayout с тремя строками и двумя колонками
ArrOfPanel[2].setLayout(new GridLayout(3,2));
ArrOfPanel[2].setBorder(BorderFactory.createLineBorder(Color.green));
for (int i=10; i<15;i++)
{
ArrOfPanel[2].add(ArrOfButton[i]);
}
///////////////////////////////
//а здесь не будем использовать никакого Layout
//функцией setBounds задаем координаты верхнего левого края компоненты
//и ширину с высотой
ArrOfPanel[3].setLayout(null);
ArrOfPanel[3].setBorder(BorderFactory.createLineBorder(Color.blue));
for (int i=15; i<18;i++)
{
ArrOfPanel[3].add(ArrOfButton[i]);
ArrOfButton[i].setBounds(140*(i-15)+5,5*(i-15)+5,135,20);
}
//////////////////////////
//здесь самый козырный GridBagLayout - я разместил здесь компоненты
//по квазидиагонали как в предыдещей панели
//перед использованием метода add мы здесь сначала заполняем поля класса
//GridBagConstraints и с помощью функции setConstraints асоциируем с
//каждым компонентом свои данные
GridBagLayout gridbag=new GridBagLayout();
ArrOfPanel[4].setLayout(gridbag);
ArrOfPanel[4].setBorder(BorderFactory.createLineBorder(Color.yellow));
GridBagConstraints GrBgCnstr = new GridBagConstraints();
GrBgCnstr.fill = GridBagConstraints.BOTH;
GrBgCnstr.gridx = 0;
GrBgCnstr.gridy = 0;
gridbag.setConstraints(ArrOfButton[18], GrBgCnstr);
ArrOfPanel[4].add(ArrOfButton[18]);
GrBgCnstr.gridx = 1;
GrBgCnstr.gridy = 1;
gridbag.setConstraints(ArrOfButton[19], GrBgCnstr);
ArrOfPanel[4].add(ArrOfButton[19]);
GrBgCnstr.gridx = 2;
GrBgCnstr.gridy = 2;
gridbag.setConstraints(ArrOfButton[20], GrBgCnstr);
ArrOfPanel[4].add(ArrOfButton[20]);
///////////////////////////
//и наконец у последней панели будет режим BoxLayout
ArrOfPanel[5].setLayout(new BoxLayout(ArrOfPanel[5],BoxLayout.X_AXIS));
ArrOfPanel[5].setBorder(BorderFactory.createLineBorder(Color.magenta));
for (int i=21; i<25;i++)
{
ArrOfPanel[5].add(ArrOfButton[i]);
}
//////////////////
//поместим панели с кнопками на главную панель нашего окна
for (int i=0; i<6;i++)
{
ContPane.add(ArrOfPanel[i]);
}
}
public static void main(String args[])
{
//создадим создадим экземпляр нашего главного класса
LayoutApplication OurAppWin = new LayoutApplication();
//возложим обработку выхода из приложения на предка
OurAppWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//скажем главному окну пусть реализирует себя и все свое вместимое
//кроме прочего эта функция задаст окну приложения оптимальный размер
//чтоб все в нем хорошо поместилось
OurAppWin.pack();
//покажем наше главное окно, ибо потомки класса Window
//(каким и является наше окно) зарождаются скрытыми
OurAppWin.setVisible(true);
}
}
Компилируем, запускаем. Кстати вы, наверное, подумаете: а зачем это он такой длиннющий и нечитабельный код наваял?. Отвечаю: я старался показать одновременно все режимы размещения компонент. А теперь потратьте пару минут и проанализируйте что делается в каждой отдельной панели (цветными рамками они окружены) когда вы меняете размер главного окна приложения. Сравните одно с другим! Надеюсь это поможет вам в будущем выбрать необходимый режим размещения для ваших компонент. Если у кого не получилось что - грузите проект, в нем кроме вышеописанного приложения вы найдете аналогичное приложение (с кодом конечно) под AWT - компоненты.
Далее
Обрабатываем события
Автор:
Vitaly