Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Портянкин И. Swing

.pdf
Скачиваний:
210
Добавлен:
07.10.2020
Размер:
5 Мб
Скачать
☆

Таблицы

545

же интерфейсом ListSelectionModel) отвечает за выделение столбцов таблицы, но хранится она немного в другом месте: в модели столбцов таблицы TableColumnModel, которую мы недавно изучали. Как вы помните, модель выделения списка позволяет проводить выделение в одном из трех режимов: в режиме выделения одиночных элементов (SINGLE_ SELECTION), в режиме выделения непрерывными интервалами (SINGLE_INTERVAL_ SELECTION), а также в наиболее гибком режиме выделения произвольного набора элементов MULTIPLE_INTERVAL_SELECTION (в случае таблицы элементами для выделения являются строки или столбцы). Таким образом, для строк и для столбцов имеется по три режима выделения, так что всего таблица предоставляет нам девять комбинаций — впечатляющая гибкость, способная удовлетворить даже самым взыскательным нуждам.

Нетрудно догадаться, что по умолчанию в таблице и для строк, и для столбцов используется стандартная реализация интерфейса ListSelectionModel — класс DefaultListSelectionModel. Когда мы обсуждали выделение в списках, то пришли к выводу, что чаще всего писать собственную модель выделения нет особого смысла, стандартная реализация прекрасно подходит в подавляющем большинстве ситуаций. Она поддерживает списки слушателей ListSelectionListener, которые оповещаются об изменениях в выделенных строках и столбцах таблицы, и все три режима выделения. Только при реализации нетривиального поведения таблицы вам может понадобиться создание собственной модели выделения (как правило, путем наследования ее от класса DefaultListSelectionModel, как это делается, рассказано в главе 11).

Разберем теперь небольшой пример, в котором взглянем на некоторые способы управления выделением строк и столбцов в таблице JTable:

//TableSelection.java

//Режимы выделения ячеек таблицы import javax.swing.*;

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

public class TableSelection extends JFrame {

//названия столбцов таблицы private String[] columnNames = {

"Название", "Вкус", "Цвет"

};

//данные для таблицы

private String[][] data = {

{"Апельсин", "Кисло-сладкий", "Оранжевый"},

{"Арбуз", "Сладкий", "Темно-зеленый"},

{"Лимон", "Кислый", "Желтый"}

};

public TableSelection() { super("TableSelection"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//создаем пару таблиц с одинаковыми данными

//первая таблица — выделение по строкам

JTable table1 = new JTable(data, columnNames); table1.getSelectionModel().setSelectionMode(

ListSelectionModel.SINGLE_INTERVAL_SELECTION);

546

ГЛАВА 17

//вторая таблица — выделение по столбцам

JTable table2 = new JTable(data, columnNames); table2.setRowSelectionAllowed(false); TableColumnModel cm = table2.getColumnModel(); cm.setColumnSelectionAllowed(true); cm.getSelectionModel().setSelectionMode(

ListSelectionModel.SINGLE_SELECTION);

//добавляем таблицы и выводим окно на экран setLayout(new GridLayout(1, 2));

add(new JScrollPane(table1)); add(new JScrollPane(table2)); pack();

setVisible(true);

}

public static void main(String[] args) { SwingUtilities.invokeLater(

new Runnable() {

public void run() { new TableSelection(); } });

}

}

Здесь мы создаем две таблицы с одинаковыми данными, но с разными режимами выделения. Первая таблица будет разрешать выделение только по строкам (это поведение по умолчанию, столбцы таблицы не выделяются, пока вы явно не укажете, что вам это необходимо). Строки таблицы будут выделяться непрерывными интервалами, посмотрите, как выбирается этот режим: сначала мы получаем модель выделения строк методом getSelectionModel() класса JTable, а затем меняем режим работы полученной модели методом setSelectionMode(). Во второй таблице работает выделение по столбцам. Чтобы добиться этого, понадобилось несколько шагов: сначала мы отключили выделение по строкам с помощью свойства rowSelectionAllowed класса JTable, затем получили модель столбцов таблицы TableColumnModel, именно она отвечает за выделение столбцов, и с ее помощью настроили нужное нам поведение. Свойство columnSelectionAllowed позволяет включить выделение столбцов, а модель выделения столбцов, возвращаемая все тем же методом getSelectionModel(), определенным на этот раз в классе TableColumnModel, позволяет выбрать для столбцов любой из трех режимов выделения (в примере мы выбираем режим выделения одного столбца). Напоследок в окно добавляются обе таблицы. Запустив программу и выделяя строки первой таблицы и столбцы второй, вы увидите, как подействовали на таблицу сделанные нами изменения.

Изначально в таблице задействован режим выделения произвольной комбинации строк, а столбцы выделять нельзя, как это изменить, мы только что увидели. Ес-

Таблицы

547

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

Таблица 17.7. Основные методы и свойства для работы с выделением в таблице

Методы или свойства

Описание

selectionModel (класс JTable и модель

Данное свойство хранит модель выделения, в классе

TableColumnModel)

JTable — это модель выделения строк, в модели столб-

 

цов TableColumnModel — модель выделения столбцов.

 

По умолчанию используется стандартная реализация

 

DefaultListSelectionModel

rowSelectionAllowed (JTable), columnSelectionAllowed (модель TableColumnModel и класс JTable)

Первое свойство позволяет включить или отключить выделение строк (определено только в классе JTable), второе включает или отключает выделение столбцов (имеется и в модели выделения столбцов и в классе JTable). По

умолчанию выделение по столбцам отключено

selectionMode (свойство моделей

Данное свойство позволяет задать режим выделения,

выделения и таблицы JTable)

используемый моделью выделения строк или столбцов.

 

Свойство с тем же названием, определенное в классе

 

JTable, задает одинаковый режим выделения сразу для

 

обеих моделей

selectAll(), clearSelection()

Методы класса JTable, первый из них позволяет «одним

(класс JTable)

махом» выбрать все ячейки таблицы, второй дает возмож-

 

ность быстро снять все имеющееся на данный момент вы-

 

деление

cellSelectionAllowed (класс JTable)

Удобное свойство класса JTable, если вы приравняете его

 

значению true, то пользователь сможет выделять отдель-

 

ные ячейки таблицы (тот же эффект получается, когда

 

разрешено выделение и по строкам, и по столбцам)

rowSelectionInterval, columnSelection-

Весьма полезные свойства таблицы JTable, задают выде-

Interval, (методы add, set и remove,

ленные строки или столбцы (четыре метода для установ-

класс JTable)

ки новых значений). Методы, начинающиеся с префикса

 

«add», присоединяют к уже имеющемуся выделению но-

 

вые строки и столбцы, методы с префиксом «set» заме-

 

няют имеющееся выделение новым, методы с префиксом

 

«remove» удаляют из выделения указанные строки или

 

столбцы

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

548

ГЛАВА 17

и ее модели выделения позволяют узнавать, какие строки и столбцы выделяет пользователь. В табл. 17.8 собраны воедино основные методы, служащие этой цели.

Таблица 17.8. Методы для получения информации о выделенных ячейках

Методы

Описание

isSelectedIndex() (метод

Метод позволяет узнать, выделен ли элемент на некоторой позиции.

модели выделения)

Именно данным методом модели выделения пользуются все осталь-

 

ные, более удобные в работе, методы

isRowSelected(),

Методы класса JTable, позволяющие быстро узнать, выделена ли

isColumnSelected(),

строка (или столбец) с некоторым индексом. Третий метод дает воз-

isCellSelected()

можность определить, выделена ли ячейка, заданная строкой или

 

столбцом

getSelectedRows(),

Также методы класса JTable, позволяющие получить массивы индек-

getSelectedColumns()

сов выделенных на данный момент строк и столбцов

Модели выделения и изученные нами методы и свойства таблицы JTable позволяют полностью управлять работой механизмов выделения ячеек таблицы и получать исчерпывающую информацию о выделенных элементах таблицы. Не забывайте только, что модели выделения строк и столбцов действуют независимо друг от друга, как бы это не выглядело на экране. К примеру, если в вашей таблице действует режим выделения строк, и вы щелкаете на некоторой ячейке, выделяется вся строка, которой принадлежит выделенная ячейка, то есть все ячейки в данной строке выглядят как выделенные. Однако выделенными будут считаться только та строка и тот столбец, которым принадлежит ячейка, на которой вы щелкнули. Работу с выделением лучше всего проводить посредством моделей, и только изредка прибегать к методам класса JTable. Это сделает код более элегантным, упростит его поддержку и развитие, а модели вы при случае сможете разделить между видами, во многом сократив объем требуемой работы.

Сортировка и фильтрация

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

Создатели Swing подошли к проблеме сортировки данных со всей возможной серьезностью. В пакет javax.swing был добавлен абстрактный класс RowSorter, способный проводить сортировку строк в любой модели, коль скоро она способна предоставить свои данные в виде строк и дает узнать общее количество этих строк. В принципе, данный класс можно было бы встроить во все компоненты библиотеки Swing, предоставляющие свои данные в виде строк, хотя бы в хорошо известные нам списки JList, однако на данный момент поддержка класса RowSorter имеется только в таблицах. Как и всегда в Swing, библиотека любезно предоставляет некую реализацию по умолчанию класса RowSorter под названием DefaultRowSorter. Однако, несмотря на название класса, он все еще абстрактный, так как все еще не знает точный тип модели, данные которой надо сортировать. Ну а конкретный класс для сортировки таблиц называется TableRowSorter.

Вникать в тонкие взаимоотношения классов для сортировки совсем не обязательно, если ваша задача состоит в простой сортировке таблицы. Пары методов класса JTable и знания некоторых деталей классов для сортировки, как правило, достаточно для подавляющего большинства приложений. Посмотрим, как добавить стандартную сортировку в таблицу:

Таблицы

549

//SimpleSorting.java

//Сортировка таблицы по умолчанию import javax.swing.*;

import javax.swing.table.DefaultTableModel; import java.awt.*;

public class SimpleSorting extends JFrame { public SimpleSorting() {

super("SimpleSorting"); setDefaultCloseOperation(EXIT_ON_CLOSE);

// создаем таблицу на основе модели по умолчанию

SortModel sm = new SortModel(); sm.setColumnCount(4);

//добавляем сто строк случайных чисел for ( int i = 0; i < 100; i++ ) {

sm.addRow(new Integer[] { i, (int)(5*Math.random()), (int)(5*Math.random()), (int)(5*Math.random())} );

}

JTable table = new JTable();

//автоматическое включение сортировки table.setAutoCreateRowSorter(true);

//ограничение по количеству столбцов

((TableRowSorter)table.getRowSorter()).setMaxSortKeys(2); table.setModel(sm);

add(new JScrollPane(table));

//выводим окно на экран

setSize(400, 300); setVisible(true);

}

// модель для демонстрации сортировки и фильтрации static class SortModel extends DefaultTableModel {

// тип данных, хранимых в столбце

public Class getColumnClass(int column) { return Integer.class;

}

}

public static void main(String[] args) { SwingUtilities.invokeLater(

new Runnable() {

550

ГЛАВА 17

public void run() { new SimpleSorting(); } });

}

}

В примере мы создаем окно, в центре которого разместится таблица, отображающая специальную модель, которая как нельзя лучше подходит для сортировки. Модель, дабы не реализовывать хранение данных самостоятельно, унаследована от модели по умолчанию DefaultTableModel, и отличается лишь методом getColumnClass(), который говорит таблице, что в ячейках хранятся целые числа (по умолчанию все ячейки модели по умолчанию имеют тип Object). Правильный тип нам понадобится для более эффективной сортировки, так как именно по типу столбца выбирается способ его сортировки. Чуть позже мы обсудим это подробнее.

Модель заполняется ста рядами случайных чисел, причем случайные числа размещаются в трех столбцах, а первый столбец просто хранит порядковый номер строки таблицы. Далее нам остается лишь включить сортировку таблицы, доступную нам по умолчанию. Это делает метод setAutoCreateRowSorter(), передав в него значение true, мы просим таблицу автоматически создавать объект для сортировки RowSorter и присоединять его к таблице. Мы можем быть уверены в том, что это объект типа TableRowSorter, потому что именно этот класс таблица использует по умолчанию.

Класс TableRowSorter обладает несколькими интересными возможностями, и одну из них мы задействуем в примере. Изначально сортировка данных в модели таблицы ведется по всем столбцам (в сортировке это называется ключами). Это значит, что если значения в столбце равны, берется следующий столбец и по его значениям определяется, какая строка будет стоять выше, и так далее по всем столбцам, если необходимо. С помощью свойства maxSortKeys мы можем указать, какое количество столбцов будет участвовать в определении того, какая строка должна стоять выше. Иногда это важно, если таблица велика, и сортировка всех столбцов может сказаться на производительности. В нашем примере будут задействованы два столбца.

Сама сортировка по умолчанию включается через заголовок столбца, на котором пользователь должен щелкнуть мышкой, после чего там появится стрелка направления сортировки, и данные будут отсортированы. Конечно же, отсортировать данные можно и программно, получив объект RowSorter, и вызвав метод toggleSortOrder(), в качестве параметра передав ему номер столбца. Запустив пример и щелкая на заголовках столбцов, вы увидите, как они сортируются, и что сортировка ограничивается двумя столбцами, оставляя данные остальных столбцов в беспорядке.

Таблицы

551

Как мы видим, стандартная сортировка включается в мановение ока и работает безукоризненно, однако при любом сравнении данных прежде всего интересен вопрос, как именно эти данные сравнивают, тем более, что это происходит внутри объекта RowSorter. Как мы знаем, в Java, как правило, применяется отдельный интерфейс Comparable, в котором объект сравнивает себя с другими объектами, либо интерфейс Comparator, который уже сам сравнивает другие объекты, а не самого себя. Все это поддерживается классом RowSorter, и в следующей последовательности:

1.Вы можете установить для любого столбца таблицы объект Comparator, который будет заниматься сравнением значений из этого столбца, с помощью метода setComparator(). Это позволяет нам полностью контролировать процесс сортировки данных в столбце.

2.Если объект Comparator не установлен, анализируется тип столбца, который, как нам известно, задается классом (именно поэтому мы переопределили его в первом примере). Если класс столбца «умеет» сравнивать себя, то есть реализует интерфейс Comparable, именно он используется для сравнения значений.

3.Если первые два условия не соблюдены, сравнение выполняется на строках, полученных из ячеек. Вопрос лишь в том, как получить эти строки. Простейший спо-

соб — вызвать для всех ячеек метод toString(), но это может привести к излишним затратам памяти и потере производительности, если в объекте и без того есть строка, по которой нужно сравнивать, нет необходимости еще раз ее генерировать1. Поэтому класс TableRowSorter предоставляет возможность задать объект TableStringConverter, который преобразует объекты из ячейки в строки, используя известные ему данные о структуре данных. Ну а сами строки сортируются согласно текущим настройкам языка и культуры (locale) виртуальной машины JVM, с помощью класса Collator, что гарантирует нам безошибочную работу со всеми языками, какие бы их них не хранились в таблице.

Таким образом, если вы храните в таблице строки, то заботиться особенно не о чем, она прекрасно позаботиться о сортировке, учитывая при этом все особенности национальных языков. Если же в таблице хранятся некие объекты с данными, имеет смысл либо реализовать в них интерфейс Comparable, а если это невозможно или сложно, установить для столбца сортирующий объект Comparator, или, на крайний случай, преобразовывать данные в строки с помощью объекта TableStringConverter, так чтобы таблица сортировала уже строки.

Интересно, что сортирующий объект RowSorter, а точнее его реализация по умолчанию DefaultRowSorter позволяет нам фильтровать строки, то есть удалять их из таблицы и экрана, хотя в модели данных эти строки, конечно же, остаются. Сама таблица JTable при этом ничего не знает про фильтрацию и не имеет никаких посвященных ей управляющих методов. Управляет фильтрацией класс RowFilter из пакета javax.swing, в котором есть всего лишь один метод include(), который возвращает true, в том случае если строку данных следует показывать на экране. Чтобы упростить нам жизнь, в классе RowFilter есть набор заранее определенных фильтров для таблиц, весьма гибких и подходящих для многих ситуаций, например фильтры по числовым значениям или фильтры, проверяющее соответствие ячейки регулярному выражению. Эти предопределенные фильтры возвращаются статическими методами класса RowFilter.

Прежде чем фильтрацию строк таблицы на практике, необходимо вспомнить об одном очень важном вопросе. Мы недавно обсудили способы выделения строк таблицы и различ-

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

552

ГЛАВА 17

ные возможности получения выделенных в данный момент строк и столбцов. Что происходит с ними, если пользователь, или сама программа, сортирует данные и таким образом меняет оригинальный порядок данных из модели? Оказывается, когда мы узнаем о номерах выделенных в данный момент строк, эти номера соответствуют строкам на экране. Если на экране порядок строк другой, чем в модели, номера строк необходимо преобразовывать.

ВНИМАНИЕ

Если в таблице включена сортировка или фильтрация строк, вы не можете полагаться на то, что номера выделенных строк представляют собой строки из модели данных TableModel. Их необходимо преобразовывать методом таблицы JTable convertRowIndexToModel(), если вы хотите получить соответствующие этим номерам строки в модели данных.

Рассмотрим пример, в котором мы воспользуемся фильтрацией строк таблицы, и проверим, как преобразовывать номера выделенных строк и столбцов к их «оригинальным» номерам в модели данных:

//FilterAndSelection.java

//Настройка фильтрации таблицы и выделенные строки import javax.swing.*;

import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionEvent; import javax.swing.table.TableRowSorter; import java.awt.*;

public class FilterAndSelection extends JFrame { public FilterAndSelection() {

super("FilterAndSelection"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//создаем таблицу на основе модели по умолчанию

SimpleSorting.SortModel sm = new SimpleSorting.SortModel(); sm.setColumnCount(4);

//добавляем сто строк случайных чисел

for ( int i = 0; i < 100; i++ ) { sm.addRow(new Integer[] { i,

(int)(5*Math.random()),

(int)(5*Math.random()), (int)(5*Math.random())} );

}

final JTable table = new JTable(sm);

//автоматическое включение сортировки table.setAutoCreateRowSorter(true);

//присоединим фильтрующий объект

((TableRowSorter)table.getRowSorter()).

Таблицы

553

setRowFilter(new RowFilter() {

public boolean include(Entry entry) { // включаем только четные строки

return ((Integer)entry.getValue(0)) % 2 == 0;

}

});

add(new JScrollPane(table));

//поле для вывода номеров выбранных строк

final JTextArea out = new JTextArea(3, 10); add(new JScrollPane(out), "South");

//следим за выделением в таблице table.getSelectionModel().addListSelectionListener(

new ListSelectionListener() {

public void valueChanged(ListSelectionEvent e) { if ( table.getSelectedRow() != -1) {

out.append("Строка:" + table.getSelectedRow() + "\n");

out.append("Столбец:" + table.getSelectedColumn() +

"\n");

out.append("Строка модели:" + table.convertRowIndexToModel(

table.getSelectedRow()) + "\n");

}

}

}); // выводим окно на экран

setSize(400, 300); setVisible(true);

}

public static void main(String[] args) { SwingUtilities.invokeLater(

new Runnable() {

public void run() { new FilterAndSelection(); } });

}

}

В примере мы создадим таблицу на основе такой же модели, что и в предыдущем примере, и заполним ее случайными числами. Таблицу можно будет сортировать, а новинкой является фильтрующий объект RowFilter, который мы присоединяем к сортирующему объекту RowSorter, который мы получаем из таблицы (он там есть, так как мы попросили таблицу включить поддержку сортировки). В единственном методе include() необходимо определить, нужно ли показывать строку таблицы на экране, а объект Entry позволяет определить всю информацию о текущей строке, в том числе метод getValue() возвращает значение определенного столбца. Мы будем показывать только четные строки, определяя это по первому столбцу, в котором хранится номер строки.

554

ГЛАВА 17

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

Запустив пример, вы сразу же убедитесь в том, что нечетные строки на экране не появляются, это заслуга нашего фильтрующего объекта. Затем, выполняя сортировку по одному из столбцов, и выделив любую строку, вы сможете увидеть, что номера строк, которые возвращает нам модель выделения, и номера строк в реальных данных отличаются. Это обязательное правило при работе с таблицами со включенной сортировкой или фильтрацией — иначе получение правильных данных на выделенной позиции будет невозможно.

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

Можно упомянуть еще то, что объекты таблицы для сортировки и фильтрации типизированы, то есть позволяют указать при создании конкретные типы модели и вспомогательных данных, с которыми им придется работать. В наших примерах мы обошлись без этого, но если логика этих объектов у вас сложна, и они часто обращаются к модели данных, указать конкретный тип модели будет весьма кстати.

Внешний вид ячеек таблицы

Таблица JTable, как и любой другой сложный компонент Swing, такой как список JList или дерево JTree, дает возможность от начала и до конца управлять процессом отображения содержащихся в нем элементов. Каждая ячейка прорисовывается с помощью отдельного отображающего объекта, который должен реализовывать особый интерфейс TreeCellRenderer. Определив собственный отображающий объект или вмешавшись в работу стандартного объекта, вы получаете неограниченную власть над тем, как будут выглядеть ячейки вашей таблицы.