Диапазоны значений |
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);