Таблицы |
555 |
Мы привыкли, что обычно для сложного компонента задается единственный отображающий объект, который и отвечает за то, как выглядят составные элементы этого компонента. Однако таблица — самый мощный и самый сложный компонент библиотеки Swing, позволяющий выводить гигантские объемы разнородной информации, предоставляет нам более гибкие решения. На самом деле, представьте себе таблицу с десяткомдругим столбцов и сотней строк — написание для нее специализированного отображающего объекта рискует превратиться в адский труд, особенно если каждая ячейка должна выглядеть по-своему. Поэтому таблица обладает более гибким поведением.
Таблица позволяет задавать два типа отображающих объектов для ячеек. Отображающие объекты по умолчанию (default renderers) задаются посредством специальных методов таблицы JTable и отвечают за прорисовку данных определенного типа, который, как и всегда в Java, задается объектом Class. Вы помните, что разбирая модель данных таблицы TableModel, мы обратили особое внимание на метод getColumnClass(), позволяющий указать, данные какого типа будет содержать тот или иной столбец. Тип столбца и определяет, какой отображающий объект будет прорисовывать ячейки этого столбца. Если вы помните, таблица JTable изначально поддерживает несколько типов данных, для которых предоставляет стандартные отображающие объекты — это простые строки, значки, булевы значения, а также числа и даты. Все поддерживаемые таблицей типы данных прорисовываются с помощью стандартного объекта DefaultTreeCellRenderer или одного из его подклассов. Вы с помощью предназначенных для этого методов класса JTable можете регистрировать свои отображающие объекты для новых типов данных или же заменять стандартные отображающие объекты собственными. Отображающие объекты ячеек могут также задаваться и для отдельных столбцов, какой бы тип не имела хранящаяся в них информация, иногда такой подход удобнее.
Все стандартные отображающие объекты унаследованы от надписи JLabel, так что вы без зазрения совести можете применять для записи ячеек таблицы все возможности HTML и даже загружать изображения. Впрочем, загрузка изображений посредством встроенного языка HTML не позволяет затем успешно распространять приложения в виде единого JAR-файла, поэтому давайте применим, как мы не раз это уже делали для других компонентов Swing, отображающий элемент, способный прорисовывать одновременно и заранее имеющийся значок Icon, и текст. Прежде всего нам понадобится специальный объект для хранения текста и значка. Мы не станем изобретать велосипед, а возьмем уже имеющийся у нас со времен работы со списками класс ImageListElement:
//com/porty/swing/ImageListElement.java
//Класс, хранящий значок и текст элемента package com.porty.swing;
import javax.swing.*;
public class ImageListElement { // значок и текст
private Icon icon; private String text; // удобный конструктор
public ImageListElement(Icon icon, String text) { this.icon = icon;
this.text = text;
}
556 |
ГЛАВА 17 |
// методы для доступа к значку и тексту public Icon getIcon() { return icon; } public String getText() { return text; }
public void setIcon(Icon icon) { this.icon = icon; } public void setText(String text) { this.text = text; }
}
В объекте ImageListElement будет храниться текст и значок ячейки. Для удобства имеется конструктор, позволяющий сразу же задать то и другое, и методы для смены данных. На очереди отображающий объект, который займется с прорисовкой текста и значка:
//com/porty/swing/ImageTableCellRenderer.java
//Объект для прорисовки значка и текста в таблице package com.porty.swing;
import javax.swing.*; import javax.swing.table.*; import java.awt.*;
public class ImageTableCellRenderer extends DefaultTableCellRenderer {
// метод возвращает компонент для прорисовки public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
// получаем объект нужного типа
if ( value instanceof ImageListElement ) { ImageListElement imageCell = (ImageListElement)value;
//получаем настроенную надпись от базового класса
JLabel label = (JLabel)super. getTableCellRendererComponent(table,
imageCell.getText(), isSelected, hasFocus, row, column);
//устанавливаем значок и подсказку label.setIcon(imageCell.getIcon()); label.setToolTipText(imageCell.getText()); return label;
}else {
return super.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, column);
}
}
}
Наш новый отображающий объект унаследован от стандартного объекта DefaultTableCellRenderer (а тот, в свою очередь, — от надписи JLabel). Мы переопределяем
Таблицы |
557 |
метод getTableCellRendererComponent(), возвращающий компонент, который и будет прорисовывать данные, хранящиеся в ячейках таблицы. Здесь, кстати, применимо правило, которое мы не раз уже обсуждали при изучении отображающих объектов: компоненты, возвращаемые этими объектами, не появляются на экране, они служат лишь «кистями», которые таблица использует при прорисовке определенных ячеек, поэтому компонент задействуется в единственном числе, а для каждого значения он заново настраивается. Это позволяет сэкономить уйму памяти и ресурсов системы графического интерфейса. Сначала данные преобразуются в тип ImageTableCell; во избежание ошибок преобразования типов мы сначала проверяем, что это подходящий нам тип. Смотрите, что происходит дальше: мы получаем от базового класса настроенную надпись (с подходящими цветами и шрифтами) с текстом, устанавливаем для нее значок, который также хранится в объекте ImageListElement, добавляем подсказку, и возвращаем таблице полученный результат. Именно отображающие объекты запрашиваются таблицей с целью получить подсказку для конкретной ячейки. Класс ImageListElement и новый отображающий объект разместились в пакете com.porty.swing, откуда их удобнее будет загружать в собственные программы.
Теперь остается указать таблице, что в некотором столбце будут храниться данные нового типа и зарегистрировать для нового типа подходящий отображающий объект. Тип данных столбца позволяет указать модель данных TableModel, а регистрацию новых отображающих объектов проводит метод setDefaultRenderer() класса JTable. У нас получается следующая программа:
//RegisteringTableRenderer.java
//Регистрация в таблице собственного
//объекта для отображения
import javax.swing.*; import javax.swing.table.*; import com.porty.swing.*; import java.awt.*;
public class RegisteringTableRenderer extends JFrame { public RegisteringTableRenderer() {
super("RegisteringTableRenderer"); setDefaultCloseOperation(EXIT_ON_CLOSE);
//создаем таблицы на основе нашей модели
JTable table = new JTable(new SpecialModel());
//регистрируем объект для прорисовки новых данных table.setDefaultRenderer(ImageListElement.class,
new ImageTableCellRenderer());
//выводим окно на экран
add(new JScrollPane(table)); pack();
setVisible(true);
}
// модель таблицы
class SpecialModel extends AbstractTableModel { // значки
558 ГЛАВА 17
private Icon
image1 = new ImageIcon("clip1.gif"), image2 = new ImageIcon("clip2.gif");
// названия столбцов
private String[] columnNames = { "Компания", "Адрес"
}; // данные таблицы
private Object[][] data = {
{new ImageListElement(image1, "MegaWorks"), "<html><font color=\"red\">Париж" },
{new ImageListElement(image2, "TerraMedia"), "<html><b>С.-Петербург" }
};
//количество столбцов public int getColumnCount() {
return columnNames.length;
}
//названия столбцов
public String getColumnName(int column) { return columnNames[column];
}
//количество строк public int getRowCount() {
return data.length;
}
//тип данных столбца
public Class getColumnClass(int column) { return data[0][column].getClass();
}
// значение в ячейке
public Object getValueAt(int row, int column) { return data[row][column];
}
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new RegisteringTableRenderer(); } });
}
}
Таблицы |
559 |
В примере мы создаем окно с таблицей в его центре. Данные для таблицы будет поставлять специальная модель, унаследованная от класса AbstractTableModel, благодаря отдельной модели мы сможем указать, какие типы данных (представленные объектами Class) хранятся в каждом столбце. Собственно данные хранятся в двух массивах: в массиве columnNames мы храним названия столбцов, а в массиве data — данные ячеек. Заметьте, что в первом столбце данные хранятся в виде только что созданных нами объектов ImageListElement, позволяющих совместить изображение и текст. Методы модели TableModel нам хорошо знакомы, пристального внимания заслуживает лишь один из этих методов, а именно getColumnClass() — для первого столбца модели он возвращает тип (класс) ImageListElement, и данный тип нужно правильным образом отобразить.
В конструкторе окна создается таблица JTable, которой передается описанная во внутреннем классе модель. Чтобы объекты ImageListElement, хранимые первым столбцом таблицы, правильно отображались, необходимо зарегистрировать для них подходящий отображающий объект. Это и делает метод setDefaultRenderer(), сопоставляющий тип данных и объект, который обязуется эти данные отобразить. Запустив программу с примером, вы увидите наш новый отображающий объект в действии, а также еще раз убедитесь во всемогуществе встроенного в Swing языка HTML, и появлении у каждой ячейки своей подсказки. Стандартный отображающий объект унаследован от надписи JLabel, и все возможности последней остаются в силе.
Отображающие объекты по умолчанию, сопоставляемые с типом данных, очень удобны и позволяют быстро настроить внешний вид таблицы, причем все логично — для каждого типа данных, если он не вписывается в стандартные рамки и ему не хватает возможностей встроенного языка HTML, предоставляется собственный отображающий объект. Благодаря тому, что для необычных типов данных можно предоставить подходящий отображающий объект, таблица является универсальным компонентом, способным хранить и показывать любые данные, даже самые фантастичные.
Однако таблица JTable предоставляет еще одну возможность управления отображением данных, хранимых в ее ячейках. Иногда единое отображение всех данных одного типа (а тип сразу же задается для всего столбца посредством модели) оказывается недостаточно гибким. Вы можете захотеть отображать ячейки в одном и том же столбце поразному. В таких ситуациях таблица позволяет регистрировать разные отображающие объекты для одного столбца независимо от того, данные какого типа там хранятся. Если говорить точнее, отображающий объект регистрируется в объекте с информацией о столбце TableColumn, который нам уже известен.
Для отображения ячеек, как и прежде, используется объект, реализующий интерфейс TreeCellRenderer, только регистрируется он по-иному. Вы получаете нужный столбец из модели TableColumnModel (используя, например метод модели getColumn()) и задаете для его ячеек отображающий объект с помощью метода setCellRenderer(). Стоит отметить, что отображающий объект для вывода ячеек определенного столбца, имеет приоритет перед объектом по умолчанию, работающим в зависимости от типа данных, которые хранятся в столбце. Вы всегда сможете вернуться к объекту по умолчанию, передав в метод setCellRenderer() столбца TableColumn пустую (null) ссылку.