
Портянкин И. Swing
.pdfТаблицы |
535 |
dsn = "jdbc:odbc:Library", uid = "",
pwd = "";
public static void main(String[] args) throws Exception {
//инициализация JDBC Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
//объект-соединение с БД
Connection conn = DriverManager.getConnection(dsn, uid, pwd); Statement st = conn.createStatement();
// выполняем запрос
ResultSet rs = st.executeQuery( "select * from readers.csv");
// наша модель
final DatabaseTableModel dbm =
new DatabaseTableModel(true);
//считываем данные в таблицу dbm.setDataSource(rs);
//таблица и окно
SwingUtilities.invokeLater( new Runnable() {
public void run() {
JTable table = new JTable(dbm);
JFrame frame = new JFrame("DatabaseTable"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(400, 300);
frame.add(new JScrollPane(table)); frame.setVisible(true);
}});
rs.close();
conn.close();
}
}
Впрограмме мы непосредственно в методе main() создаем небольшое окно JFrame
иразмещаем в нем таблицу, которая будет получать данные из только что созданной модели для работы с базами данных. Конечно же, мы работаем с компонентами из потока рассылки событий, так как хорошо знаем правила Swing. Для правильной работы модели нам нужно настроить и открыть подключение к базе данных JDBC. Здесь используется все то же подключение к базе данных некой гипотетической библиотеки, мы уже применяли его в главе 11, когда создавали модели списков JList и JComboBox для работы
с базами данных. В качестве драйвера задействован стандартный мост JDBC-ODBC, годящийся в большинстве случаев жизни. Для работы программы с вашей базой данных вам нужно сменить лишь идентификатор базы данных, имя пользователя и пароль, а также, если это необходимо, драйвер JDBC, в том случае если вы используете специальную базу данных с собственным драйвером. Открыв соединение с базой данных, программа вы-

536 |
ГЛАВА 17 |
полняет запрос (в примере запрос получает список всех читателей библиотеки), и если последний успешен, то результат запроса возвращается в виде объекта ResultSet. Заметьте, что мы передаем объект ResultSet нашей модели еще до того, как таблица присоединяется к ней, добавляется в окно, и последнее выводится на экран. Это позволяет нам загрузить данные из отдельного потока выполнения метода main(), так как таблица пока не обращается к модели. В противном случае, если модель уже присоединена к таблице, необходимо выполнять все действия из потока рассылки событий, иначе конфликты неизбежны.
Запустив программу с примером, предварительно настроив любую вашу базу данных
ивыполнив запрос по получению любых, даже самых сложных данных, вы увидите, что они правильно появляются в таблице, и структура таблицы имеет нужный вид (название
итип столбцов), несмотря на всю простоту написанной нами модели. Действительно, JDBC и таблица JTable словно созданы друг для друга.
Конечно, рассмотренная нами модель на самом деле простовата для работы с реальными базами данных, в которых, как правило, хранится информация впечатляющего размера. Она будет занимать слишком много места в памяти, так как вычитывает всю таблицу в списки и будет вызывать замедление в интерфейсе, поскольку процесс считывания данных необходимо выполнять в потоке рассылки событий одним вызовом, что на время остановит прорисовку и отзывчивость интерфейса. Можно развить наш пример в различных направлениях:– например, вычитывать ограниченный набор данных, видимый в данный момент на экране, а при прокрутке таблицы читать недостающие строки, либо применять объект ResultSet с поддержкой прокрутки по записям таблицы в произвольном направлении, что позволит вообще не хранить данных в модели, но может быть не слишком производительным. Эта увлекательная тема, однако, относится скорее к работе с базами данных, чем к библиотеке Swing. Способы добиться максимальной производительности мы уже неоднократно обсуждали при работе с другими сложными компонентами Swing, списками JList и деревьями JTree — точечное обновление данных, работа с блоками данных и взаимодействие с потоком рассылки событий вы можете найти в главах 11 и 15. Все эти техники в одинаковой мере относятся ко всем ситуациям с большим количеством данных в модели.
Мы можем подвести предварительный итог: основная модель таблицы TableModel удивительно изящна и гибка; она позволяет быстро описать даже самые сложные двухмерные данные с учетом всей их специфики. Удачной следует признать и концепцию типа столбца (представленного объектами Class) — вы можете быстро указать, как следует отображать (и при необходимости редактировать) данные этого столбца, потому что при наличии встроенной поддержки таких фундаментальных типов, как значки, булевы значения и числа, таблица даже без специализированной настройки становится мощным инструментом в руках создателя пользовательского интерфейса.
Таблицы |
537 |
Модель столбцов таблицы
Столбцы таблицы JTable обладают по-настоящему гибким поведением: вы можете менять их размеры, менять их местами друг с другом, настраивать объекты для отображения в столбце и для редактирования ячеек столбца, тонко управлять заголовком столбца и делать многое другое — все аспекты поведения столбцов таблицы настраиваются. В тоже время в модели данных таблицы TableModel столбцы представлены только своим названием и типом хранящихся в них данных. Остальная информация о столбцах, а ее немало, хранится в специальной предназначенной специально для них модели
TableColumnModel.
Модель TableColumnModel отвечает за то, какие столбцы, в каком порядке и с какими параметрами выводит на экран таблица JTable. Грубо говоря, это специализированный контейнер данных, который хранит список столбцов таблицы в том порядке, в каком они представлены на экране (в отличие от модели данных TableModel, в которой порядок следования столбцов всегда один и тот же независимо от того, как столбцы располагаются на экране). Каждый столбец представлен объектом TableColumn. С помощью этого объекта вы можете указать размеры столбца (у столбца, как и у обычного компонента, может быть предпочтительный, максимальный и минимальный размеры), индекс данного столбца в модели данных TableModel (по этому индексу таблица определяет, какой набор данных отображает столбец), объекты для отображения и редактирования данных, хранящихся в столбце (об этих объектах мы вскоре поговорим подробнее, когда доберемся до детального обсуждения внешнего вида ячеек таблицы), и настроить многие другие параметры, в том числе и модель выделения столбцов.
По умолчанию в таблице для хранения информации о столбцах используется стандартная модель DefaultTableColumnModel, и ее возможностей хватает в подавляющем большинстве ситуаций. Работа модели столбцов таблицы не слишком сложна: она хранит список столбцов TableColumn и при смене какого-либо свойства столбцов или их расположения оповещает об этом событии слушателей TableColumnModelListener. Стандартная модель прекрасно справляется с оповещением слушателей, а столбцы хранит в динамическом массиве (векторе) Vector. Как правило, писать собственную модель столбцов таблицы «с нуля» или наследуя от стандартной модели не имеет особого смысла — стандартная модель позволяет динамически манипулировать столбцами и проводить с ними все необходимые операции. Также несложно с помощью слушателей TableColumnModelListener постоянно быть в курсе всех изменений в структуре столбцов таблицы.
А сейчас в несложном примере рассмотрим основные возможности стандартной модели столбцов таблицы и объектов TableColumn:
//UsingTableColumnModel.java
//Использование стандартной модели столбцов
//таблицы и объектов TableColumn
import javax.swing.*; import javax.swing.table.*; import java.awt.event.*; import java.awt.*;
import java.util.Enumeration;
public class UsingTableColumnModel extends JFrame { // модель столбцов
private TableColumnModel columnModel; // названия столбцов таблицы
538 |
ГЛАВА 17 |
private String[] columnNames = { "Имя", "Любимый цвет", "Напиток"
}; // данные для таблицы
private String[][] data = {
{"Иван", "Зеленый", "Апельсиновый сок"},
{"Александр", "Бежевый", "Зеленый чай"}
};
public UsingTableColumnModel() { super("UsingTableColumnModel"); setDefaultCloseOperation(EXIT_ON_CLOSE); // наша таблица
JTable table = new JTable(data, columnNames);
//получаем стандартную модель columnModel = table.getColumnModel();
//меняем размеры столбцов
Enumeration e = columnModel.getColumns(); while ( e.hasMoreElements() ) {
TableColumn column = (TableColumn)e.nextElement();
column.setMinWidth(30);
column.setMaxWidth(90);
}
// создадим пару кнопок
JPanel buttons = new JPanel();
JButton move = new JButton("Поменять местами"); move.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { // меняем местами первые два столбца columnModel.moveColumn(0, 1);
}
});
buttons.add(move);
JButton add = new JButton("Добавить"); add.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { // добавляем столбец
TableColumn newColumn =
new TableColumn(1, 100); newColumn.setHeaderValue("<html><b>Новый!"); columnModel.addColumn(newColumn);
Таблицы |
539 |
}
});
buttons.add(add);
// выводим окно на экран add(new JScrollPane(table)); add(buttons, "South"); setSize(400, 300); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new UsingTableColumnModel(); } });
}
}
Мы создаем небольшое окно, в центре которого будет располагаться небольшая таблица JTable. Таблицу мы создаем на основе двухмерного массива с данными и массива с названиями столбцов. Все это нам уже прекрасно знакомо. Дальше интереснее: мы получаем стандартную модель столбцов таблицы методом getColumnModel() и можем приступать к работе с ней. Прежде всего, обратите внимание на то, как модель столбцов позволяет получить перечисление (Enumeration) имеющихся в данный момент в таблице столбцов и изменить их размеры. По умолчанию минимальным размером для любого столбца является 15 пикселов, предпочтительным — 75 пикселов, а максимальный размер его не ограничен. Однако столбцы у таблиц бывают разные, и эти значения на все случаи жизни не подходят, так что нет ничего удивительного в том, что чаще всего работа со столбцами сводится к настройке их размеров. В примере у нас нет особых требований к столбцам, так что мы можем сделать их любого понравившегося нам размера, видно, как меняются минимальный и максимальный размеры столбцов. Запустив программу с примером, вы сразу оцените разницу: если раньше свободное пространство в таблице было бы равномерно распределено между столбцами, то теперь, благодаря ограниченному максимальному размеру, свободное место просто ничем не занято — столбцы никогда не становятся длиннее своего максимального размера (и соответственно, короче минимального).
Далее пример показывает, каким образом модель выделения столбцов (в лице ее стандартной реализации) позволяет динамически манипулировать столбцами таблицы. В южную часть окна добавляется панель с двумя кнопками, к каждой из которых присоединен слушатель ActionListener. Первая кнопка меняет местами первый и второй столбцы таблицы, специально для этого в модели имеется удобный метод move(). Еще в самом первом примере данной главы мы отметили, что по умолчанию таблица позволяет нам мышью «тасовать» столбцы, меняя их местами. Подобное поведение обеспечивает именно модель столбцов TableColumnModel, и мы видим, что совсем нетрудно переставлять столбцы программно. Вторая кнопка показывает, как можно добавить в таблицу еще один столбец, даже если в модели основных данных TableModel этот столбец и не присутствует. В слушателе создается объект TableColumn, инкапсулирующий информацию о столбце таблицы. Мы использовали конструктор, который требует указать индекс столбца в модели данных TableModel (это обязательно, и такой столбец в модели данных должен присутствовать, иначе неизбежны исключения — столбцу будет нечего показывать) и предпочтительный размер столбца (остальные размеры задаются по

540 |
ГЛАВА 17 |
умолчанию, мы уже знаем их значения). Есть и более полные конструкторы, которые вы сможете отыскать в интерактивной документации, но всегда для любого столбца TableColumn должен иметься соответствующий столбец в модели данных TableModel. Полученный столбец добавляется в модель с помощью метода addColumn(). Правда, приходится вручную задавать для него текст заголовка (методом setHeaderValue(), и здесь поддерживается HTML): объект TableColumn ничего не знает о модели данных и о том, какой у него должен быть заголовок.
Таким образом, мы видим, что один столбец, описанный в модели данных, может отображаться на экране несколькими столбцами благодаря отдельной модели столбцов и ее гибкости. Впрочем, все же лучше, если изменения в количестве столбцов идут из одного источника, где их легко обнаружить и поддерживать: таким источником должна быть модель данных TableModel. Добавление столбцов посредством модели TableColumnModel имеет смысл, только если вы «копируете» некоторые столбцы, несколько раз воспроизводя их на экране. К тому же после запуска примера возникнет небольшая неприятность: если вы меняете данные в ячейке в одном столбце, дополнительный столбец не перерисовывается автоматически, он будет перерисован лишь тогда, когда поменяется его размер или выделение строки таблицы. Столбец таблицы — довольно низкоуровневая ее часть, и при работе с ним стоит это учитывать. В нашем случае необходимо вручную попросить все столбцы, отображающие изменившийся столбец, перерисовать себя, что конечно не совсем удобно.
Запустив программу с примером, вы сможете увидеть, как модель столбцов таблицы позволяет менять их размеры и динамически манипулировать местоположением и количеством столбцов, даже если в модели данных TableModel (бесспорно, главной модели таблицы JTable) новые столбцы не имеют своего представительства. Сведем наиболее полезные методы и свойства модели TableColumnModel в табл. 17.4.
Таблица 17.4. Самые полезные методы и свойства модели TableColumnModel
Методы и свойства |
Описание |
addColumn(), removeColumn() |
Методы дают возможность динамически управлять количеством |
|
хранимых в модели TableColumnModel столбцов. Первый ме- |
|
тод присоединяет к модели новый столбец (заданный объектом |
|
TableColumn), второй удаляет из модели заданный столбец |
addColumnModelListener(), |
Эти два метода поддерживают присоединение и отсоединение |
removeColumnModelListener() |
слушателей TableColumnModelListener. Присоединив к модели |
|
такого слушателя, вы будете получать полную информацию обо |
|
всех происходящих со столбцами таблицы действиях |

Таблицы |
541 |
Таблица 17.4 (продолжение) |
|
|
|
Методы и свойства |
Описание |
|
|
getColumnCount(), getColumn(), |
Позволяют получить исчерпывающую информацию о содер- |
getColumns() |
жащихся в модели столбцах. Первый метод сообщает количе- |
|
ство столбцов, второй возвращает столбец по его порядково- |
|
му номеру, и наконец, третий метод возвращает перечисление |
|
(Enumeration) всех содержащихся в модели столбцов |
moveColumn() |
Меняет местами два столбца таблицы, заданных (своими индек- |
|
сами в модели) в виде параметров данного метода. Не забывай- |
|
те, что отсчет столбцов ведется с нуля |
columnMargin (методы get/set) |
Это свойство управляет расстоянием между столбцами таблицы |
|
(в пикселах) |
Это основные методы модели столбцов таблицы, но не забывайте также о том, что львиная доля свойств отдельного столбца задается в объекте TableColumn, который мы кратко рассмотрели. Основными свойствами любого столбца являются его размеры (и мы видели, как они меняются), а также собственные объекты для отображения и редактирования ячеек, если они заданы (до них мы вскоре доберемся и обсудим подробно). Чтобы при случае основные свойства столбца таблицы были у вас «под рукой», соорудим еще одну полезную таблицу (табл. 17.5).
Таблица 17.5. Важнейшие свойства столбца TableColumn
Свойства |
Описание |
modelIndex |
Индекс столбца в модели данных таблицы TableModel. Данные |
|
именно этого столбца отображает объект TableColumn |
preferredWidth, minWidth, |
Предпочтительный, минимальный и максимальный размеры |
maxWidth |
столбца таблицы (длина столбца в пикселах). Таблица всегда |
|
соблюдает минимальный и максимальный размеры столбца, так |
|
что эти свойства довольно «сильного» действия |
resizable |
Позволяет указать, можно ли будет динамически менять раз- |
|
меры столбца, или он всегда будет иметь предпочтительный |
|
размер |
cellRenderer, cellEditor, |
Объекты для отображения и редактирования ячеек данного |
headerRenderer |
столбца, а также объект, отображающий заголовок столбца. Мы |
|
обсудим их в разделе, посвященном внешнему виду ячеек та- |
|
блицы |
headerValue, identifier |
Первое свойство хранит значение, которое будет отображать- |
|
ся в заголовке столбца, а второе, используемое довольно |
|
редко, позволяет связать со столбцом некоторый уникальный |
|
идентификатор. Применяя этот идентификатор, вы впослед- |
|
ствии всегда сможете обнаружить данный столбец в модели |
|
TableColumnModel (с помощью метода getColumnIndex()), как бы |
|
сильно не изменилась структура последней |
Как правило, модель столбцов таблицы и объекты TableColumn чаще всего требуются для настройки размеров столбцов. Таблица JTable разрешает пользователю менять размеры столбцов прямо во время работы программы (если только вы не установили свойство столбца resizable равным false), поэтому в дополнение к трем размерам столбца иногда необходимо управлять тем, как таблица будет перераспределять
542 |
ГЛАВА 17 |
пространство при изменении размеров того или иного столбца. Режим перераспределения пространства таблицы управляется специальным свойством autoResizeMode. Класс JTable поддерживает несколько режимов перераспределения пространства таблицы, давайте посмотрим, как работает каждый из них, с помощью следующего примера:
//TableResizeModes.java
//Режимы перераспределения пространства таблицы
//при изменении размеров столбцов
import javax.swing.*; import java.awt.*;
public class TableResizeModes extends JFrame {
//названия столбцов таблицы private String[] columnNames = {
"Название", "Вкус", "Цвет"
};
//данные для таблицы
private String[][] data = {
{"Апельсин", "Кисло-сладкий", "Оранжевый"},
{"Лимон", "Кислый", "Желтый"}
}; // массив таблиц
private JTable[] tables = new JTable[5]; public TableResizeModes() {
super("TableResizeModes"); setDefaultCloseOperation(EXIT_ON_CLOSE);
//окно делим на пять ячеек setLayout(new GridLayout(5, 1));
//создаем массив таблиц
for (int i=0; i<tables.length; i++) { tables[i] =
new JTable(data, columnNames); add(new JScrollPane(tables[i]));
}
// меняем режимы распределения пространства tables[1].setAutoResizeMode(
JTable.AUTO_RESIZE_OFF); tables[2].setAutoResizeMode(
JTable.AUTO_RESIZE_NEXT_COLUMN); tables[3].setAutoResizeMode(
JTable.AUTO_RESIZE_LAST_COLUMN); tables[4].setAutoResizeMode(

Таблицы |
543 |
JTable.AUTO_RESIZE_ALL_COLUMNS);
//придаем окну оптимальный размер и
//выводим его на экран
pack();
setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new TableResizeModes(); } });
}
}
В примере создается пять таблиц, отображающих одни и те же данные, полученные из массивов data (данные ячеек) и columnNames (названия столбцов). Таблицы размещаются в окне с менеджером расположения GridLayout, который разбивает панель на пять ячеек одинакового размера, располагающихся вертикально. Единственное различие
всозданных таблицах заключается в том, что они задействуют различные режимы распределения пространства таблицы. Таблица JTable предоставляет нам пять таких режимов, пять таблиц мы и создали в примере. Обратите внимание, что для первой таблицы
вмассиве (tables[0]) режим не меняется — он остается используемым по умолчанию режимом AUTO_RESIZE_SUBSEQUENT_COLUMNS.
Запустив программу с примером, вы сможете своими руками «пощупать», чем характерен тот или иной режим перераспределения пространства, при этом вам наверняка пригодится краткое описание этих режимов, представленное в табл. 17.6.
Таблица 17.6. Режимы распределения пространства таблицы JTable
Режим |
Краткое описание |
AUTO_RESIZE_OFF |
В данном режиме пространство не перераспределяет- |
|
ся — размеры столбцов, если вы не меняете их вручную, |
|
таблицей не меняются. По умолчанию размер столбца ра- |
|
вен его предпочтительному размеру, если вы меняете раз- |
|
мер столбца, то меняется только он, остальные столбцы не |
|
затрагиваются. Если после изменения размеров столбцов |
|
места в таблице начинает не хватать, появляется горизон- |
|
тальная полоса прокрутки (в том случае если таблица была |
|
размещена в панели прокрутки JScrollPane, в противном |
|
случае крайние столбцы просто исчезают с экрана) |
AUTO_RESIZE_NEXT_COLUMN |
При изменении размеров столбца пространство пере- |
|
распределяется только между ним и следующим за ним |
|
столбцом — если вы делаете столбец больше, следующий |
|
столбец становится меньше, и наоборот, при уменьшении |
|
столбца следующий за ним сосед забирает освободившее- |
|
ся пространство |
AUTO_RESIZE_LAST_COLUMN |
Столбцы можно увеличивать или уменьшать только за счет |
|
последнего столбца таблицы, все остальные столбцы (не |
|
считая того, размер которого вы меняете) остаются неиз- |
|
менными |
544 |
ГЛАВА 17 |
Таблица 17.6 (продолжение) |
|
|
|
Режим |
Краткое описание |
|
|
AUTO_RESIZE_SUBSEQUENT_ |
Данный режим используется таблицей по умолчанию. При |
COLUMNS |
изменении размеров какого-либо столбца перераспреде- |
|
ление пространства происходит равномерно между всеми |
|
следующими за ним столбцами. Последний столбец нельзя |
|
отдельно уменьшить или увеличить, разве что меняя раз- |
|
меры предпоследнего столбца |
AUTO_RESIZE_ALL_COLUMNS |
Перераспределение пространства в этом режиме происхо- |
|
дит между всеми столбцами таблицы. Если вы, к примеру, |
|
увеличиваете размер некоторого столбца, то требуемое для |
|
этого пространство «отнимается» у всех остальных столб- |
|
цов в равном соотношении (правда, если столбец достигает |
|
минимального размера или изначально не допускает изме- |
|
нения своих размеров, он перестает участвовать в «жерт- |
|
вовании» пространства) |
Вкупе с регулированием минимального, предпочтительного и максимального размеров столбца, которое предоставляет нам класс TableColumn, различные режимы перераспределения пространства таблицы дают нам возможность сделать размеры таблицы и ее ячеек именно такими, какими вы хотите их видеть. Не забывайте и о простых свойствах rowHeight и intercellSpacing, рассмотренных нами в начале главы. Они позволяют парой строк кода дополнить только что изученные возможности по управлению размерами столбцов — первое свойство отвечает за высоту строк таблицы, а второе задает расстояние между соседними ячейками как по вертикали, так и по горизонтали. Любые размеры таблицы теперь у вас в руках.
Напоследок об еще одном свойстве таблицы JTable, которое имеет непосредственное отношение к модели столбцов TableColumnModel. Это свойство autoCreateColumnsFromModel
булева типа. Когда данное свойство равно true (а по умолчанию оно равно true), то происходит следующее: при присоединении модели данных TableModel таблица получает из этой модели информацию обо всех столбцах и на ее основе создает объекты TableColumn, которые затем передает в модель столбцов TableColumnModel. Если вы хотите получить больше контроля над тем, как, когда и какие столбцы будет выводить таблица на экран, сделайте это свойство равным false и добавляйте столбцы в модель вручную, когда настанет подходящий момент — в этом случае столбцы не будут создаваться автоматически. Правда, обращаться с данным свойством нужно аккуратнее и не забывать добавлять все нужные вам столбцы на экран, в противном случае от таблицы останется только рамка.
Модели выделения
Ячейки таблицы можно выделять любыми способами, которые вы только в состоянии себе представить: строками, столбцами, интервалами, единичными ячейками и многими другими. Нетрудно догадаться, что такую гибкость в выделении содержимого таблицы обеспечивают очередные модели, верно служащие классу JTable. На этот раз это модели, хранящие выделенные строки и столбцы таблицы. И для столбцов, и для строк таблицы имеется собственная модель выделения, но не опасайтесь сложностей — это прекрасно знакомая нам еще с главы 11 (в которой мы изучали списки Swing) модель выделения элементов списка ListSelectionModel. Многое, что мы узнали об этой модели из главы 11, мы здесь повторять не будем, так что если вы не читали про модель выделения списка или просто немного забыли ее, самое время ненадолго вернуться назад.
Итак, модели выделения две. Первая модель хранится непосредственно в таблице JTable и отвечает за выделение строк таблицы. Вторая модель (представленная все тем