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

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

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. Определив собственный отображающий объект или вмешавшись в работу стандартного объекта, вы получаете неограниченную власть над тем, как будут выглядеть ячейки вашей таблицы.

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