Материал: Портянкин И. Swing

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

530

ГЛАВА 17

Таблица 17.2 (продолжение)

 

 

 

Тип данных

Описание

 

 

Boolean.class

В столбце такого типа хранятся данные о логических переменных

 

(boolean), «обернутых» в стандартный класс Boolean. Данные такого

 

столбца таблица отображает, помещая в ячейку экземпляр флажка

 

JCheckBox, который может быть и редактируемым (если ячейка с та-

 

ким типом данных редактируема, состояния флажка можно изменить,

 

используя мышь или клавиатуру)

Date.class

Удобный тип столбца, особенно полезный в таблицах, отображающих

 

результаты запросов к базам данных, где даты встречаются очень ча-

 

сто. Столбец с таким типом должен хранить данные в виде дат Date

 

из пакета java.util. Таблица будет отображать даты согласно локаль-

 

ным настройкам операционной системы (в разных странах стандарты

 

отображения дат различны), используя для форматирования объект

 

DateFormat. Правда, в Swing нет компонентов или стандартных диа-

 

логовых окон для ввода дат, так что их редактирование проводится

 

так же, как редактирование обычных строк, что неудобно и запуты-

 

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

 

тировать даты, проще написать собственный редактор ячеек с таким

 

типом. Как это сделать, мы вскоре увидим

Icon.class

Очень полезный тип столбца, указывающий, что в нем хранятся знач-

 

ки Icon, к которым в том числе относятся и значки на основе изобра-

 

жений ImageIcon. Как правило, ячейки со значками не редактируются,

так что особого редактора для значков в таблице нет, а если он вам

понадобится, придется написать его самостоятельно

По умолчанию считается, что данные в столбце могут иметь любой тип, таким данным соответствует тип Object.class. Данные этого типа отображаются как обычные строки: для любого объекта вызывается метод toString(), полученная строка и выводится таблицей в соответствующей ячейке. При редактировании столбцов с таким типом данных на вводимые пользователем данные не накладывается никаких ограничений.

Используя метод getColumnClass(), мы с легкостью можем указать таблице, что в наших столбцах хранятся нестандартные данные, так что она сможет соответствующим образом вывести их на экран. Для второго столбца нашей таблицы мы будем использовать тип Boolean.class (так что логические переменные отобразятся флажками), а для третьего — Icon.class, после чего таблица будет выводить в соответствующих ячейках не текст, а значки. Для первого столбца нашей таблицы можно было бы указать тип Integer. class, но мы для простоты ограничились простыми строками String, автоматически преобразуя любые объекты и примитивные типы в строки.

Таблицы

531

Запустив программу с примером, вы сможете оценить, как описанная в примере простая модель поставляет данные для таблицы JTable. Заметьте, что программа работает быстро, хотя таблица отображает огромное количество записей. Собственная модель,

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

Далее мы увидим, что внешний вид столбцов и способ редактирования хранящихся

вних ячеек может быть настроен гибко. Это можно сделать с помощью типа столбца, который вы сообщаете методом модели getColumnClass(). При этом вы не ограничены стан-

дартными типами данных для столбцов и можете регистрировать новые типы, указывая таблице JTable, как их нужно отображать и редактировать. Можно также действовать «в обход» модели, указав, как следует отображать и редактировать данные для некоторого столбца, представленного объектом TableColumn. Пока же нам важно то, как модель позволяет гибко описать все ячейки таблицы независимо от того, где и как эти ячейки будут отображаться.

Модель таблицы для работы с базами данных

Большая часть современных приложений так или иначе взаимодействует с базами данных, либо напрямую сотрудничая с системами управления базами данных (СУБД), либо посредством вспомогательных компонентов, выполняющих дополнительные действия, таких как EJB-компоненты (аббревиатура EJB означает Enterprise JavaBeans). Идея разделения данных и места, где эти данные (или с их часть) обрабатываются, давно признана удачной и активно эксплуатируется, позволяя эффективно разделять, обновлять и защищать данные (в меньшем масштабе эта идея прекрасно прижилась в библиотеке Swing, в которой разделены модели, представляющие собой данные, и виды, эти данные отображающие).

Не составляет большого труда заметить, что в Swing лучше всего для отображения результатов запросов к базам данных подходит именно таблица JTable. Она с легкостью отобразит любое количество столбцов и строк, позволит задать различные типы данных для столбцов, соответствующим образом редактировать и отображать их (а в современных базах данных типы хранимой информации задаются именно для столбцов), при необходимости внести в данные изменения и передать модифицированные результаты обратно в хранилище. Самое приятное, что благодаря интерфейсу TableModel мы можем один раз создать модель, получающую информацию из базы данных, а затем использовать ее для вывода в таблицах JTable результатов запросов к разнообразным базам данных.

Работать с базами данных мы будем с помощью интерфейса JDBC, в котором результат запроса к базе данных возвращается в виде объекта ResultSet. С помощью этого объекта несложно получить данные любого типа, считывая их по строкам и столбцам. Для каждого объекта ResultSet существует дополнительная информация в виде объекта ResultSetMetaData, позволяющая выяснить количество, типы и названия столбцов, полученных в результате запроса. Мы используем эту информацию для настройки нашей модели. Итак, приступим:

//com/porty/swing/DatabaseTableModel.java

//Модель данных таблицы, работающая

//с запросами к базам данных

package com.porty.swing;

import javax.swing.table.*; import java.sql.*;

532 ГЛАВА 17

import java.util.*;

public class DatabaseTableModel extends AbstractTableModel {

// здесь мы будем хранить названия столбцов

private ArrayList<String> columnNames = new ArrayList<String>();

// список типов столбцов

private ArrayList<Class> columnTypes = new ArrayList<Class>(); // хранилище для полученных данных из базы данных

private ArrayList<ArrayList<Object>> data

=new ArrayList<ArrayList<Object>>();

//признак редактирования таблицы private boolean editable;

//конструктор позволяет задать возможность редактирования public DatabaseTableModel(boolean editable) {

this.editable = editable;

}

//количество строк

public int getRowCount() { return data.size();

}

//количество столбцов public int getColumnCount() {

return columnNames.size();

}

//тип данных столбца

public Class getColumnClass(int column) { return columnTypes.get(column);

}

// название столбца

public String getColumnName(int column) { return columnNames.get(column);

}

// данные в ячейке

public Object getValueAt(int row, int column) { return (data.get(row)).get(column);

}

// возможность редактирования

public boolean isCellEditable(int row, int column) { return editable;

}

Таблицы

533

//замена значения ячейки public void setValueAt(

Object value, int row, int column){ (data.get(row)).set(column, value);

}

//получение данных из объекта ResultSet public void setDataSource(

ResultSet rs) throws Exception {

//удаляем прежние данные data.clear(); columnNames.clear(); columnTypes.clear();

//получаем вспомогательную информацию о столбцах

ResultSetMetaData rsmd = rs.getMetaData(); int columnCount = rsmd.getColumnCount(); for ( int i=0; i<columnCount; i++) {

//название столбца columnNames.add(rsmd.getColumnName(i+1));

//тип столбца

Class type = Class.forName(rsmd.getColumnClassName(i+1));

columnTypes.add(type);

}

// получаем данные while ( rs.next() ) {

// здесь будем хранить ячейки одной строки

ArrayList<Object> row = new ArrayList<Object>(); for ( int i=0; i<columnCount; i++) {

row.add(rs.getObject(i+1));

}

data.add(row);

}

// сообщаем об изменениях в структуре данных

fireTableStructureChanged();

}

}

Мы назвали нашу модель DatabaseTableModel и разместили ее в пакете com.porty.swing, так что при необходимости вы сможете включать ее в программы и без особых трудностей наглядно выводить результаты запросов к базам данных. Основной метод нашей модели — setDataSource(). Он принимает результат запроса к базе данных ResultSet, удаляет хранящиеся в модели данные, полученные после обработки предыдущих запросов (если таковые были), и заново описывает структуру столбцов таблицы на основе типов

534

ГЛАВА 17

данных полученного запроса. Все данные, включая названия и типы данных (объекты Class) столбцов, хранятся в списках ArrayList. При получении в метод setDataSource() нового результата запроса к базе данных мы удаляем из списков всю прежнюю информацию и приступаем к обработке объекта ResultSet.

Как уже было упомянуто, с каждым объектом ResultSet ассоциирован другой объект ResultSetMetaData, содержащий вспомогательную информацию о результатах запроса. Он позволит нам гибко, не вдаваясь в подробности того, к какой базе данных мы подключены, узнавать все необходимое для построения подходящей модели. Прежде всего мы получаем количество столбцов в полученном результате запроса (с помощью метода getColumnCount()), и для каждого столбца выясняем его название (это название столбца в базе данных, чаще всего оно не слишком удобно для прочтения человеком, но в реальном приложении вы сможете легко сменить идентификаторы столбцов на что-либо более подходящее) и тип (тип, как всегда в Java, аналогичен классу, так что мы получаем название класса и создаем его экземпляр статическим методом forName()). Обратите внимание на то, что при получении информации из объекта ResultSetMetaData (а затем и из объекта ResultSet) отсчет столбцов ведется с единицы, а не с нуля, как в остальных случаях. Так уж устроен интерфейс JDBC, это правило стоит помнить, иначе при попытке получения нулевого столбца запроса вы вместо данных «словите» исключение.

Далее можно приступать непосредственно к работе с данными. Для каждой строки с данными создается свой отдельный список ArrayList, в него будут поочередно добавляться значения данных всех столбцов текущей строки запроса. Данные можно просто получать методом getObject(), пригодным на все случаи жизни, класс ResultSet обещает вернуть в нем подходящий типу столбца объект. После заполнения данными очередной строки она добавляется в список всех данных data.

Как только мы выясняем «всю подноготную» новых столбцов таблицы и наполняем списки строками таблицы, вызывается метод fireTableStructureChanged(), который оповестит слушателей о том, что структура столбцов таблицы поменялась. Такое оповещение приведет к отображению на экране (в том случае если модель присоединена к виду, то есть к таблице JTable), и пользователь увидит, какие столбцы присутствуют в новом наборе данных. При смене структуры столбцов все данные также будут считаны заново и отображены на экране.

Остальные методы нам прекрасно знакомы и служат для правильной работы модели. Большая часть из них получает информацию (название столбца, количество строк и столбцов, тип столбца) из списков ArrayList, которые заполняются данными в уже рассмотренном нами методе setDataSource(). Конструктор нашей модели позволяет указать, будет ли она разрешать редактирование. Новые данные записываются в списке в методе модели setValueAt(), который вызывает таблица при окончании редактирования.

Атеперь проверим работу нашей новой модели. Приведем простой пример:

//DatabaseTable.java

//Таблица, работающая с базой данных

//посредством специальной модели

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

import com.porty.swing.*;

public class DatabaseTable { // параметры подключения private static String

Источник: https://studfile.net/preview/15935977/