Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Портянкин И. Swing

.pdf
Скачиваний:
143
Добавлен:
07.10.2020
Размер:
4.63 Mб
Скачать

Диапазоны значений

365

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

}

}

В примере мы создаем пару счетчиков JSpinner, которые позволят пользователю выбирать дату (точнее, один из параметров даты, такой как день недели). Как мы уже знаем, для выбора даты используется специальная модель счетчика SpinnerDateModel. Модель для первого счетчика позволит нам организовать выбор дня месяца, причем без ограничений: пользователь сможет выбрать тот день месяца, который ему приглянется, даже если тот прошел несколько столетий назад. Конструктор модели SpinnerDateModel требует задать четыре параметра: первым идет начальное значение в виде объекта-даты Date, которое будет отображать поле счетчика. Как правило, начальным значением выбирают настоящий момент времени, получить его не составляет труда: надо просто создать объект Date8. Так мы и поступаем. Далее в конструкторе надо указать диапазон дат, минимально возможную и максимально возможную даты. Мы передаем вместо этих параметров пустые ссылки null, это означает, что выбор дат будет неограничен. Наконец, последним параметром должно идти поле, по которому будет изменяться дата. Доступные поля перечислены в виде целочисленных констант в классе Calendar, для нашего первого счетчика мы выбираем поле «день месяца» (DAY_OF_MONTH). Настроенную модель мы передаем в конструктор счетчика.

Вторая модель позволяет выбирать месяц, причем она сложнее, так как ограничивает доступный для выбора диапазон дат. В качестве «ограничителей» должны выступать объекты, реализующие интерфейс Comparable и выполняющие сравнение выбранной пользователем даты с минимальной и максимальной границами диапазона. В принципе, в качестве «ограничителя» должен был бы годиться любой объект, реализующий интерфейс Comparable, но не тут то было. Объект-«ограничитель» обязательно должен представлять собой экземпляр класса Date (или экземпляр его подкласса). В противном случае вас вместо поля со счетчиком поджидает маловразумительная цепочка исключений.

В нашем примере мы наследуем наши объекты от класса Date и реализуем интерфейс Comparable. Сравнение дат проводится по году, вы видите, что год даты позволяет получить класс Calendar (в классе Date есть, правда, метод getYear(), но он плохо справляется с временными зонами и не рекомендован к использованию). Класс MinDate следит за тем, чтобы год выбираемой пользователем даты был не меньше 2005 (если год меньше, возвращается единица, а это значит, что минимальная дата больше той, с которой проводится сравнение, то есть такая дата не подходит). Аналогичным образом класс MaxDate требует, чтобы год выбираемой даты не превышал 2011. «Ограничители» мы передаем в конструктор модели и последним параметром указываем, что прокрутка должна производиться помесячно. Вы можете и не писать методы compareTo(), а просто указать даты, между которыми должен лежать выбор пользователя. В классе Date уже имеется реализация интерфейса Comparable. Как мы уже упоминали, настроить время, заданное в объекте Date, позволяет все тот же класс Calendar.

8 Получить объект Date, представляющий другой момент во времени, позволяет класс Calendar. Метод set() данного класса дает возможность изменить любое поле даты, к примеру, изменить день месяца можно так: set(Calendar.DAY_OF_MONTH, 1).

366

ГЛАВА 12

Созданные на основе настроенных моделей счетчики мы добавляем в панель содержимого и выводим на экран. Запустив программу с примером, вы увидите, как первый счетчик позволяет выбирать даты из неограниченного диапазона и как ограничивает выбор второй счетчик, дающий вам возможность «прокрутить» месяцы только до конца 2011 года, но и не позволяющий уходить в прошлое на срок раньше 2005 года. Счетчики очень полезны при выборе дат, хотя вам может показаться, что формат, в котором выводится дата, не слишком удобен для восприятия и подходит не ко всем ситуациям. Однако эту проблему легко обойти, настроив редактор, используемый счетчиком для отображения данных модели SpinnerDateModel, мы вскоре увидим, как это делается.

Остается отметить, что компонент JSpinner иногда способен «удивить» пользователя, а в деловых интерфейсах это не приветствуется. К примеру, когда в нашем примере фокус ввода попадает на второй счетчик, прокрутка все равно ведется с первого поля, то есть по дням, несмотря на то, чтобы просили счетчик прокручивать дату по месяцам. Более того, счетчик всегда позволяет ввести в поле ввода любую строку, и только после того как пользователь покинет поле счетчика, сбрасывается на последнее подходящее значение, например дату. Все это делает счетчики, по крайней мере стандартные счетчики JDK, не совсем удобными для эффектных интерфейсов. Но кое-что улучшить всегда возможно, это мы сейчас и увидим.

Редактор элементов

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

Как нетрудно догадаться, по умолчанию для отображения своих элементов счетчик использует стандартные редакторы, поставляемые вместе с ним. Для каждой рассмотренной нами стандартной модели (как мы знаем, для класса JSpinner их три) имеется свой стандартный редактор. Правда, все стандартные редакторы JSpinner чрезвычайно похожи друг на друга: все они унаследованы от редактора JSpinner.DefaultEditor, который представляет собой текстовое поле с поддержкой данных в специальном формате JFormattedTextField. Унаследованные от редактора DefaultEditor подклассы просто задают для текстового поля подходящее форматирующее выражение, например, выражение, описывающее дату. В табл. 12.3 перечислены редакторы, соответствующие стандартным моделям.

Таблица 12.3. Редакторы для стандартных моделей

Модель

Редактор

 

 

SpinnerListModel

JSpinner.ListEditor

SpinnerNumberModel

JSpinner.NumberEditor

SpinnerDateModel

JSpinner.DateEditor

 

 

Диапазоны значений

367

Как мы уже отметили, стандартные редакторы отличаются лишь форматом информации, которая будет появляться в текстовом поле JFormattedTextField. Их довольно легко настроить: вы получаете стандартный редактор методом getEditor(), преобразуете его

кбазовому классу DefaultEditor, получаете текстовое поле и настраиваете его по своему вкусу. Иногда это единственный способ придать счетчику нужный вам вид, не обращаясь

кнаписанию собственного редактора. Попробуем теперь настроить стандартные редакторы в следующем примере:

//SpinnerEditors.java

//Стандартные редакторы счетчиков

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

public class SpinnerEditors extends JFrame { // данные для первого счетчика

private String[] data = {

"Первый", "Второй", "Последний"

};

public SpinnerEditors() { super("SpinnerEditors"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//счетчик на основе массива

JSpinner spinner1 = new JSpinner( new SpinnerListModel(data));

//настраиваем редактор

((JSpinner.ListEditor)spinner1.getEditor()). getTextField().setColumns(15);

//выбор дат

SpinnerDateModel dates = new SpinnerDateModel(

new Date(), null, null, Calendar.DAY_OF_MONTH); JSpinner spinner2 = new JSpinner(dates);

//настраиваем редактор

((JSpinner.DateEditor)spinner2.getEditor()). getTextField().setEditable(false);

//добавляем счетчики в панель содержимого setLayout(new FlowLayout()); add(spinner1);

add(spinner2);

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

setSize(400, 300); setVisible(true);

}

368

ГЛАВА 12

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

new Runnable() {

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

}

}

Мы создаем два счетчика, один на основе модели SpinnerListModel, данные ему мы передали в виде массива строк, другой на основе модели SpinnerDateModel, так что он послужит для выбора дат (легко видеть, что выбор будет проводиться по дню месяца и без ограничений). После создания моделей и присоединения их к счетчикам производится простая настройка внешнего вида и поведения редакторов счетчиков. Для первого счетчика мы устанавливаем новое количество столбцов текстового поля (заметьте, что текстовое поле JFormattedTextField можно получить методом getTextField()) и таким образом сразу же избавляемся от неприятной проблемы, замеченной нами еще в самом начале знакомства со списками JSpinner: элементы различной длины могут не «влезать» в счетчик или динамически менять размеры счетчика. После задания определенного количества столбцов счетчик будет иметь твердо заданный размер, подходящий под ваши нужды.

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

вполе счетчика для выбора дат. Текстовое поле JFormattedTextField мы подробнее изучим

вглаве 16.

Действия с редакторами не ограничиваются настройкой стандартных редакторов класса JSpinner, вы с легкостью сможете написать свой редактор. Мы уже знаем, что в качестве редактора счетчика JSpinner может выступать любой компонент, унаследованный от базового класса библиотеки JComponent. Особенно полезно создание нового редактора в случае применения собственной модели с экзотичными данными: поставляя вместе с такой моделью подходящий редактор, вы полностью настраиваете счетчик, говоря, какие данные он должен «прокручивать» и как их надо отображать. В качестве примера давайте попробуем создать редактор на основе надписи JLabel — нам прекрасно известны ее возможности по отображению красочного содержимого. Для того чтобы редактор смог вовремя узнавать о смене текущего элемента счетчика, он должен присоединить к нему слушателя ChangeListener, который будет оповещаться при смене элементов. Итак, вот пример:

//SpinnerLabelEditor.java

//Редактор счетчика JSpinner на основе надписи import javax.swing.*;

Диапазоны значений

369

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

public class SpinnerLabelEditor extends JFrame { // данные для списка

private String[] data = { "Красный", "Зеленый", "Синий"

};

public SpinnerLabelEditor() { super("SpinnerLabelEditor"); setDefaultCloseOperation(EXIT_ON_CLOSE); // создаем счетчик

JSpinner spinner = new JSpinner( new SpinnerListModel(data));

//присоединяем наш редактор

LabelEditor editor = new LabelEditor(); spinner.setEditor(editor);

//регистрируем слушателя spinner.addChangeListener(editor);

//для появления на экране необходимо

//чтобы до редактора дошло событие spinner.getModel().setValue(data[1]);

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

setLayout(new FlowLayout()); add(spinner);

setSize(300, 200); setVisible(true);

}

// специальный редактор для счетчика class LabelEditor extends JLabel

implements ChangeListener { // метод слушателя событий

public void stateChanged(ChangeEvent e) { // получаем счетчик

JSpinner spinner = (JSpinner)e.getSource();

//получаем текущий элемент

Object value = spinner.getValue();

//устанавливаем новое значение if ( value.equals(data[0]) ) {

setText("<html><h2><font color=\"red\">"

+value);

370

ГЛАВА 12

}

if ( value.equals(data[1]) ) { setText("<html><h3><font color=\"green\">"

+ value);

}

if ( value.equals(data[2]) ) { setText("<html><h4><font color=\"blue\">"

+ value);

}

}

// размер редактора

public Dimension getPreferredSize() { return new Dimension(100, 30);

}

}

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

new Runnable() {

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

}

}

Здесь в окно добавляется один простой счетчик на основе модели SpinnerListModel, данные которой хранятся в массиве. Самое интересное — это внутренний класс LabelEditor, унаследованный от надписи JLabel и реализующий интерфейс слушателя событий ChangeListener (интерфейс этого слушателя обязательно нужно реализовывать любому редактору, иначе он не сможет узнавать об изменении текущего элемента счетчика). Вся работа происходит в методе слушателя stateChanged(), он вызывается при смене текущего значения счетчика. Мы находим источник события, подразумевая, что это счетчик JSpinner (хотя можно присоединить слушателя и к модели SpinnerModel), получаем текущее значение счетчика и обрабатываем его. Нам известно, что значения у нас всего три, так что каждое из них редактор обрабатывает по своему, «выкрашивая» в подходящий цвет. Обратите внимание на метод getPreferredSize(): он возвращает намеренно больший размер, в противном случае размер надписи менялся бы динамически в зависимости от отображаемого элемента, а это привело бы не к самому лучшему результату на экране.

После создания редактора остается присоединить его к счетчику. Для этого нужно не только передать его счетчику методом setEditor(), но и зарегистрировать как слушателя событий ChangeListener. В примере это сделано не слишком элегантно, лучше было бы передать ссылку на счетчик в конструктор редактора, и уже в этом конструкторе провести регистрацию слушателя. Также имеется и еще одна маленькая хитрость — наша надпись отображает что-либо только после получения события ChangeListener, а пока пользователь не начнет прокручивать счетчик, это событие не произойдет. Нам приходится вручную поменять значение модели, что приведет к запуску события ChangeListener, и обновлению нашей надписи (в противном случае после запуска счетчик отображал бы пустое поле). Пожалуй, проще было бы присоединить модель после того, как присоединен редактор. Запустив программу с примером, вы увидите, как отображаются разные элементы счетчика, и это только начало — нам прекрасно известно, на что способна надпись JLabel.

Диапазоны значений

371

В качестве красочного редактора можно использовать и компонент JEditorPane, примерно так, как мы это делали в главе 11 для раскрывающихся списков JComboBox, и тем самым еще больше повысить гибкость и элегантность своего приложения.

Резюме

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

Глава 13. Управление пространством

Вглаве 7 мы уже знакомились с вариантами размещения компонентов в контейнере

изнаем, что для этого используются разнообразные менеджеры расположения. С их помощью вы сможете создать любой необходимый вам пользовательский интерфейс и расположить компоненты в контейнере так, как планировалось. Однако не все ситуации можно разрешить только средствами контейнеров и подходящих менеджеров расположения — в вашем приложении, особенно если оно сложное или обладает множеством вариантов настройки, может быть огромное количество разнообразных компонентов, которые просто физически не смогут уместиться на экране. Кроме того, существуют ситуации, когда размер какого-либо компонента намного превышает доступное пространство экрана. В решении проблемы иногда может помочь применение дополнительных диалоговых окон, но их излишнее количество запутывает пользователя, особенно если одни диалоговые окна приходится вызывать из других.

С недостатком места в контейнере призваны справляться специальные компоненты, которые мы и будем рассматривать в этой главе. К таким компонентам, прежде всего, относится панель с вкладками JTabbedPane, которая в удобной и наглядной форме

компактно размещает несколько элементов пользовательского интерфейса так, что работать вы можете только с одним из них, пока остальные скрыты. Таким образом можно сэкономить пространство контейнера и при этом разбить его на логически связанные группы.

Далее мы рассмотрим разделяемую панель JSplitPane, в обязанности которой входит гибкое распределение пространства между двумя компонентами. С ее помощью пользователь может сделать больше тот компонент, который его в данный момент интересует, произвольным образом меняя соотношение доступных размеров компонентов.

Наконец, остается панель прокрутки JScrollPane, отображающая только часть некоторого компонента и с помощью специальных полос прокрутки (JScrollBar) легко позволяющая перемещаться к любой другой его части. Первые два компонента довольно просты, и их функции очевидны, так что их изучение не займет у нас много времени, а вот панель прокрутки настолько часто гостит в пользовательских интерфейсах, что ее стоит рассмотреть подробнее, к тому же, как вы узнаете, она способна на многое.

Панель с вкладками JTabbedPane

Как нетрудно догадаться по названию, панель с вкладками JTabbedPane позволяет выводить на экран так называемыми вкладки (tabs) — панели с небольшими ярлычками с краю (это может быть любой из краев панелей — верхний, нижний, левый или правый). При щелчке пользователем на ярлычке панель JTabbedPane выводит на экран соответствующие выбранной вкладке элементы пользовательского интерфейса. Таким образом у вас появляется возможность сэкономить место в контейнере: вместо размещения всех компонентов пользовательского интерфейса в одной панели содержимого вы разбиваете их (чаще всего по смыслу и предназначению) на несколько групп, добавляете каждую группу в отдельную панель, и передаете эти панели в компонент JTabbedPane, заодно

Управление пространством

373

указывая, какая надпись (и значок, если захотите) должна соответствовать данной группе компонентов. Пользователь будет видеть только одну группу компонентов (которая занимает гораздо меньше места на экране), а чтобы увидеть и воспользоваться другой группой, нужно перейти на подходящую вкладку. Панель JTabbedPane позаботится о том, чтобы при выборе вкладки на экране появилась соответствующая группа компонентов. Особенно часто панели с вкладками используются в диалоговых окнах настройки приложения: в них бывает много компонентов с различным предназначением, и вкладки не только помогают компактно разместить эти компоненты, но и дают пользователю возможность быстро найти то, что его интересует.

Использовать панель с вкладками JTabbedPane очень просто. В основном работа с ней заключается в вызове метода add() или addTab() (лучше остановить свой выбор на втором методе, его название лучше соответствует производимому действию и параметры этого метода удобнее настраивать), которому необходимо передать компонент, соответствующий вкладке, надпись для ярлычка вкладки и значок, если вы его используете. После добавления всех вкладок панель JTabbedPane выводится на экран, при этом стоит проследить за тем, чтобы занимаемого ею места было достаточно не только для тех вкладок, которые вы добавили в нее, но и для соответствующих этим вкладкам компонентов. Рассмотрим небольшой пример использования панели JTabbedPane и убедимся, что она на самом деле проста:

//SimpleTabbedPanes.java

//Использование панелей с вкладками import javax.swing.*;

import java.awt.*;

public class SimpleTabbedPanes extends JFrame { public SimpleTabbedPanes() {

super("SimpleTabbedPanes"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//первая панель с вкладками

JTabbedPane tabsOne = new JTabbedPane( JTabbedPane.BOTTOM, JTabbedPane.SCROLL_TAB_LAYOUT);

//добавляем вкладки

for (int i=1; i<8; i++) { JPanel tab = new JPanel();

tab.add(new JButton("Просто кнопка " + i)); tabsOne.addTab("Вкладка №: " + i, tab);

}

//вторая панель с вкладками

JTabbedPane tabsTwo = new JTabbedPane(JTabbedPane.TOP);

//добавляем вкладки

for (int i=1; i<8; i++) { JPanel tab = new JPanel();

tab.add(new JButton("Снова кнопка " + i)); tabsTwo.addTab("<html><i>Вкладка №: " + i,

374

ГЛАВА 13

new ImageIcon("icon.gif"), tab, "Нажмите " + i + "!");

}

//добавляем вкладки в панель содержимого setLayout(new GridLayout()); add(tabsOne);

add(tabsTwo);

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

setSize(600, 250); setVisible(true);

}

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

new Runnable() {

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

}

}

В примере мы создаем окно (довольно большого размера — нам нужно, чтобы в нем поместились все наши вкладки) и размещаем в нем две панели с вкладками JTabbedPane. Первая панель создается с помощью конструктора, принимающего два параметра: первый отвечает за расположение ярлычков вкладок (мы выбрали расположение внизу панели, но вы можете выбрать любую из четырех сторон панели, каждой из сторон соответствует своя константа класса JTabbedPane), а второй позволяет указать, как вкладки будут располагаться на экране в том случае, если их не удастся разместить в один ряд. По умолчанию вкладки располагаются в несколько рядов (аналогично действует менеджер последовательного расположения FlowLayout, когда ему не хватает места для размещения всех компонентов в одну строку), такому поведению соответствует константа WRAP_ TAB_LAYOUT, но для первой панели мы выбрали второй вариант расположения вкладок (которому отвечает константа SCROLL_TAB_LAYOUT): в одну строку вместе с небольшими кнопками прокрутки. Такое расположение вкладок позволяет сэкономить место, хотя и не дает возможности видеть ярлычки сразу всех вкладок. Вкладки мы добавляем в цикле методом addTab(), в качестве содержимого каждой вкладки используется панель JPanel, в которую помещена кнопка. Чтобы вы могли отличать вкладки и их содержимое друг от друга, мы добавили в надписи на ярлычках вкладок и на кнопках их порядковые номера. Метод addTab() очень прост: ему нужно передать строку с названием вкладки и ее содержимое.

Вторая панель JTabbedPane создается с помощью более простого конструктора: в нем мы указываем лишь расположение ярлычков вкладок (наверху панели), а способ размещения вкладок на экране оставляем заданным по умолчанию (как мы уже упоминали, по умолчанию вкладки располагаются в несколько рядов). Вкладки мы добавляем в цикле, так же как и в первом случае, но на этот раз используем более функциональную перегруженную версию метода addTab(). Она принимает сразу четыре параметра: надпись для ярлычка вкладки, значок, компонент, который будет соответствовать вкладке, и текст всплывающей подсказки (панель JTabbedPane позаботится о том, чтобы у каждой вкладки была собственная подсказка, если вы ее задаете). Кроме того что вы можете задать текст вместе со значком, для текста доступен прекрасно вам «знакомый» формат HTML: надо лишь указать в начале строки «волшебные» символы <html> (впрочем, текст может быть и пустой ссылкой null, в таком случае у вкладки будет только значок). Учитывая,