560 |
ГЛАВА 17 |
Задание отображающих объектов для столбцов позволяет проводить интересные эксперименты с процессом прорисовки таблицей содержимого своих столбцов. После изучения модели столбцов TableColumnModel нам известно, что одни и те же столбцы, описанные данными модели TableModel, могут быть несколько раз выведены на экран различными столбцами TableColumn. Задавая для таких столбцов разные отображающие объекты, вы сможете придать столбцам с одинаковыми данными уникальный внешний вид.
В остальном работа отображающих объектов одинакова независимо от того, используются по умолчанию или задаются для конкретных столбцов. Как работать с такими объектами в своей программе, мы только что выяснили. В том случае, если вам понадобится отображать каждую ячейку по-своему, в отображающем объекте придется возвращать различные компоненты в зависимости от выбранной строки таблицы.
Редактирование ячеек таблицы
Таблица JTable разрешает пользователю изменять значения, хранящиеся в ее ячейках. Ячейки, значения которых можно менять, определяются с помощью модели данных TableModel: в ней есть метод isCellEditable(), говорящий, можно ли редактировать ячейку, расположенную в определенной строке и определенном столбце таблицы. Заведует редактированием ячеек таблицы не сама таблица и не ее UI-представитель, а отдельный объект, реализующий интерфейс TableCellEditor. Вы можете настроить стандартный объект для редактирования, поставляемый вместе с библиотекой Swing, или написать собственный объект и таким образом управлять процессом редактирования ячейки от начала до конца. Только не забывайте определять метод setValueAt() в модели данных TableModel: без него ни один редактор не сможет изменить значения в ячейках.
Объекты для редактирования в таблице JTable очень схожи с объектами для отображения — это также отдельные объекты, позволяющие легко наделить таблицу любыми возможностями редактирования. Так же как и отображающие объекты, объекты для редактирования разделяются на два типа: объекты по умолчанию и объекты, используемые для конкретного столбца таблицы (вторые имеют приоритет перед первыми). Объекты по умолчанию регистрируются посредством специальных методов класса JTable и предназначены для редактирования данных определенного типа (тип данных, хранимых в столбце, возвращает метод getColumnClass() модели TableModel). Изначально таблица поставляется с редакторами для строковых данных (строки редактируются так же, как все данные неизвестного типа, если для них не зарегистрировано специальных редакторов), булевых переменных (в качестве редактора используется флажок JCheckBox) и чисел.
Все редакторы, изначально встроенные в таблицу JTable, реализованы в стандартном объекте для редактирования DefaultCellEditor. Стандартный объект реализует интерфейс TableCellEditor и позволяет использовать для редактирования данных текстовое поле JTextField, флажок JCheckBox или раскрывающийся список JComboBox. Таким образом, стандартный объект довольно гибок и может помочь вам в редактировании ячейки без написания своего объекта для редактирования «с нуля»: текстовое поле позволит ввести любые строковые данные, а раскрывающийся список будет к месту, если вам понадобится организовать редактирование ячейки как выбор одного варианта из нескольких.
Интересно, что нам уже доводилось иметь дело со стандартным редактором ячеек DefaultCellEditor. В главе 15 мы использовали его для редактирования узлов дерева: класс DefaultCellEditor одновременно поддерживает интерфейсы TreeCellEditor (для деревьев) и TableCellEditor (для таблиц). Оба этих интерфейса унаследованы от базового интерфейса CellEditor, который описывает основные обязанности редактора ячеек таблицы или узлов дерева. Интерфейс TableCellEditor прибавляет к базовым методам CellEditor лишь один новый метод getTableCellEditorComponent(), который должен возвращать компонент,
Таблицы |
561 |
применяемый для редактирования ячеек. Так что все, что мы узнали о стандартном редакторе DefaultCellEditor в главе 15, применимо и для таблиц.
А теперь применим некоторые возможности стандартного редактора в следующем примере:
//TableDefaultEditing.java
//Применение стандартного редактора для таблиц import javax.swing.*;
import java.awt.*;
public class TableDefaultEditing extends JFrame {
//название столбцов private String[] columns = {
"Имя", "Любимый Цвет" };
//данные для таблицы private String[][] data = {
{"Иван", "Зеленый" },
{"Александр", "Красный"},
{"Петр", "Синий"}
};
public TableDefaultEditing() { super("TableDefaultEditing"); setDefaultCloseOperation(EXIT_ON_CLOSE); // создаем таблицу
JTable table = new JTable(data, columns);
//настраиваем стандартный редактор
JComboBox combo = new JComboBox(
new String[] { "Зеленый", "Желтый", "Белый"}); DefaultCellEditor editor =
new DefaultCellEditor(combo); table.getColumnModel().getColumn(1).
setCellEditor(editor);
//выводим окно на экран
add(new JScrollPane(table)); setSize(400, 300); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new TableDefaultEditing(); } });
}
}
562 |
ГЛАВА 17 |
Здесь в небольшое окно добавляется таблица JTable, созданная на основе двух массивов: одномерного с названиями столбцов и двухмерного с данными таблицы. Мы передаем эти массивы в конструктор таблицы, на их основе будет создана стандартная модель таблицы. Как мы знаем, в стандартной модели редактирование ячеек по умолчанию включено, так что беспокоиться об этом не нужно. Далее идет код настройки стандартного редактора ячеек таблицы. Как мы уже упомянули, стандартный редактор может выполнять редактирование на основе трех компонентов: текстового поля JTextField, флажка JCheckBox и раскрывающегося списка JComboBox. Для нашего примера мы выбираем раскрывающийся список JComboBox. Прежде чем передать его в конструктор стандартного редактора, необходимо наполнить список элементами, среди которых пользователь и будет делать выбор. Для простоты мы создаем список на основе массива из трех строк. Настроенный список передается стандартному редактору, после чего остается указать таблице, для каких ячеек этот редактор потребуется. В нашем примере мы указываем, что полученный редактор необходимо задействовать для второго столбца таблицы (редактор присоединяется к объекту TableColumn, который позволяет получить модель столбцов таблицы TableColumnModel).
Запустив программу с примером, вы увидите, как второй столбец таблицы редактируется с помощью настроенного нами раскрывающегося списка JComboBox. Процесс редактирования можно тонко настроить, применив все те знания о раскрывающихся списках, которые мы получили в главе 11. В нашем случае видно, что списку не хватает места в ячейке таблицы, мы можем попробовать изменить внешний вид списка или размер используемого в нем шрифта, или же применить хорошо известное нам свойство таблицы rowHeight, чтобы немного увеличить высоту ячеек. Первый столбец таблицы также редактируется, но для него по умолчанию применяется редактор, который задействует для редактирования обычное текстовое поле. Для несложных данных, вроде обычных строк или булевых значений, возможностей стандартного редактора может хватать, однако при появлении в таблице более экзотичных данных приходится задумываться о написании собственного редактора.
Редактор дат
Когда мы обсуждали встроенные в таблицу JTable типы данных, то упомянули о том, что она способна отображать даты (представленные объектом Date), но вот встроенного редактора для этих дат нет, точнее, редактировать даты с помощью обычного текстового поля неудобно. Теперь, когда мы знаем обязанности редактора таблицы JTable, мы можем написать собственный редактор для дат, позволяющий комфортно редактировать их. В качестве компонента для редактирования мы применим счетчик JSpinner со специальной моделью — в наборе стандартных компонентов Swing это самый удобный способ менять даты.
Таблицы |
563 |
Реализация «с нуля» интерфейса TableCellEditor — задача довольно трудоемкая, поэтому мы наследуем класс своего нового редактора от абстрактного класса AbstractCellEditor, который реализует большую часть методов базового интерфейса CellEditor. Точно также мы поступили и в главе 15, когда писали собственный редактор для узлов дерева JTree. Вообще, написание редакторов для таблиц и деревьев практически не отличается, и вы сможете легко перенести редакторы, созданные для одного компонента, в другой компонент. После наследования от класса AbstractCellEditor и реализации интерфейса TableCellEditor нам фактически останется определить два метода: метод getTableCellEditorComponent() должен будет возвращать компонент, который применяется при редактировании, а метод getCellEditorValue() сообщать, какое значение находится в данный момент в редакторе. Новый редактор мы поместим в пакет com.porty.swing длятого, чтобы было легко применять его в ваших программах.
//com/porty/swing/DateCellEditor.java
//Редактор для ячеек таблицы, отображающих даты package com.porty.swing;
import javax.swing.*; import javax.swing.table.*; import java.util.*;
public class DateCellEditor extends AbstractCellEditor implements TableCellEditor {
//редактор — прокручивающийся список private JSpinner editor;
//конструктор
public DateCellEditor() {
// настраиваем прокручивающийся список
SpinnerDateModel model = new SpinnerDateModel(
new Date(), null, null, Calendar.DAY_OF_MONTH); editor = new JSpinner(model);
}
// возвращает компонент, применяемый для редактирования public java.awt.Component getTableCellEditorComponent( JTable table, Object value, boolean isSelected,
int row, int column) {
// меняем значение и возвращаем список editor.setValue(value);
return editor;
}
// возвращает текущее значение в редакторе public Object getCellEditorValue() {
return editor.getValue();
}
}
564 |
ГЛАВА 17 |
Новый редактор получился совсем простым. В конструкторе мы создаем и настраиваем счетчик JSpinner, который и будет выступать в роли редактора дат. Как вы помните из главы 12, для того чтобы счетчик JSpinner можно было применять в качестве редактора дат, ему необходимо передать специальную модель SpinnerDateModel. Конструктор этой модели требует четыре параметра. Первый — это текущее значение (это дата, которая будет изначально отображаться в поле счетчика), в качестве которого мы просто передаем новый объект Date. Ограничители дат определяют диапазон изменения даты — для простоты в качестве ограничителей мы передали пустые (null) ссылки, а это означает, что выбор дат будет неограничен. Последним параметром является поле класса Calendar, определяющее, по какой части даты будет осуществляться «прокрутка» — в редакторе это будет день месяца.
Далее следует пара методов, определяющих работу редактора ячеек таблицы. Метод getTableCellEditorComponent() должен возвращать компонент, который и будет редактором таблицы. Здесь все просто: мы, не мудрствуя лукаво, передаем в счетчик значение текущей ячейки (и подразумеваем, что наш редактор будет применяться лишь для дат, представленных объектами Date, в противном случае возникнет исключение) и возвращаем счетчик. Метод getCellEditorValue(), предназначенный для получения текущего значения в редакторе, еще проще: он просто возвращает текущее значение счетчика. Кстати, текущим значением также будет объект Date, но здесь не должно возникать разногласий: счетчик знает, как такие объекты редактировать, а таблица знает, как их отображать (ей нужно лишь сообщить, что некоторый столбец имеет именно такой тип данных). Все остальные методы, определенные в интерфейсе CellEditor, любезно реализованы для нас базовым классом AbstractCellEditor. Вы всегда сможете переопределить их, если вашему редактору понадобится дополнительная функциональность.
Остается проверить в работе новый редактор для ячеек таблицы. Заодно мы увидим, как в таблице JTable регистрируются редакторы по умолчанию — как вы помните, в отличие от редакторов для отдельных столбцов, редакторы по умолчанию регистрируются для типов данных и автоматически применяются для всех столбцов, имеющих указанный тип.
//CustomTableEditor.java
//Применение специализированного редактора
//для ячеек таблицы
import javax.swing.*; import javax.swing.table.*; import java.util.*;
import java.awt.*; import com.porty.swing.*;
public class CustomTableEditor extends JFrame {
//заголовок столбцов таблицы private String[] columns = {
"Операция", "Дата" };
//данные таблицы
private Object[][] data = {
{"Покупка", new Date() },
{"Продажа", new Date() }
};
public CustomTableEditor() {