
Портянкин И. Swing
.pdfТекстовые компоненты |
515 |
Создать свой собственный рисующий объект для прорисовки фона за текстом очень просто — интерфейс HighlightPainter предусматривает всего лишь один метод paint(), в который вам передается объект для рисования Graphics и область, в который вы должны прорисовать фон. Используя механизм рисования фона для текстовых компонентов, вы можете создавать весьма удобные для пользователя режимы работы, подсказывая ему с помощью визуальных эффектов что-то, в чем он, скорее всего, в данный момент наиболее заинтересован, или разделяя на экране информацию различной структуры и предназначения.
Резюме
Текстовые компоненты Swing позволят организовать в своем приложении ввод и редактирование текста самыми разными способами: начиная от редактирования простого текста в одну строку и заканчивая составлением и отображением документов в известных форматах RTF и HTML. В данной главе мы разобрали базовые возможности текстовых компонентов Swing, на их основе вы сможете реализовать большую часть требуемых в приложениях операций. Если же вашей целью является мощный текстовый редактор с огромным спектром возможностей, то к вашим услугам пакет javax.swing.text: он содержит все, чтобы ваши фантазии можно было воплотить в жизнь.
С другой стороны, если речь заходит о мощных текстовых возможностях, вы можете начать с платформы NetBeans, основной целью которой является удобное редактирование текста, в том числе и структурированного. Скорее всего, если ваши цели и способности NetBeans совпадут, вы сэкономите на разработке очень много времени — реализация качественных текстовых компонентов требует усилий.
Глава 17. Таблицы
Пришло время познакомиться с одним из самых впечатляющих компонентов библиотеки Swing — таблицей JTable. Таблица JTable дает возможность с легкостью выводить двухмерную информацию, расположенную в виде строк и столбцов, достаточно легко настраивать и сортировать данные, выводить их в любом подходящем для вас виде, управлять заголовками таблицы и ее выделенными элементами и с небольшими усилиями делать еще многое другое. Бывают случаи, когда всю необходимую пользователю информацию вы можете разместить в нескольких таблицах, применяя остальные компоненты лишь в качестве вспомогательных, и с помощью тех же таблиц получать любую нужную информацию.
Впрочем, за все приходится платить. Это правило действует и в случае с таблицами. Чтобы полностью овладеть всей мощью класса JTable, придется изучить довольно много вспомогательных классов, узнать способы их взаимодействия, понять, как реализованы модели и почему они так реализованы, в каких ситуациях таблица ведет себя определенным образом, и многое другое. Вспомогательным классам таблицы JTable посвящен целый пакет (впечатляющего размера) javax.swing.table. Но пугаться не стоит, создать таблицу с некоторой информацией, полученной из любого источника, довольно просто и не требует особенного напряжения. Как всегда, Swing подстраивается под уровень пользователя: для получения простой таблицы потребуются простые действия, а все более сложные таблицы будут требовать от вас все больше и больше усердия. Начнем с самого простого.
Простые таблицы
Таблица JTable позволяет выводить двухмерные данные, записанные в виде строк и столбцов. Данные для таблицы поставляет специальная весьма гибкая модель, обязанности которой описаны в классе TableModel (стоит помнить, что практически все вспомогательные классы и интерфейсы для таблиц JTable находятся в пакете javax.swing.table). Она позволяет передать таблице все необходимые данные для верного вывода информации (количество строк и столбцов, названия столбцов, элемент, находящийся в определенном месте таблицы, тип данных, хранящийся в столбце, признак доступности для редактирования некоторого элемента), а также поддерживает слушателей, получающих уведомления об изменениях в данных модели. Всего этого хватает таблице JTable для отображения двухмерных данных любого типа.
Как и всегда в Swing, вам необязательно использовать для таблиц подготовленные и наполненные данными модели, вы можете передать в специальные конструкторы таблицы JTable массивы или векторы с информацией о ячейках таблицы и, при необходимости, о названиях ее столбцов. Довольно часто, особенно при создании простых программ или простых незатейливых таблиц, которые вряд ли будут в будущем расширяться или повторно использоваться, такой подход удобнее и быстрее. Учитывая при этом, что вы с легкостью можете получить значение любой ячейки таблицы методом getValueAt() и изменить значение любой ячейки методом setValueAt(), у вас и без моделей (хотя на самом деле они незримо работают за кулисами) есть возможность полностью управлять данными таблицы.
Таблица JTable, как и большинство сложных компонентов библиотеки Swing, реализует интерфейс Scrollable, так что вы смело можете добавлять ее в панель прокрутки JScrollPane, не заботясь о правильности прокрутки. При прокрутке полностью появля-
Таблицы |
517 |
ются следующий столбец или следующая строка таблицы, это удобно и интуитивно понятно пользователю. Интересно, что заголовок таблицы (специального вида надписи с названиями столбцов, реализованных классом JTableHeader) будет виден пользователю только при помещении таблицы в панель прокрутки, потому что таблица размещает компонент JTableHeader в виде заголовка панели прокрутки (как мы отмечали в главе 13, заголовок панели прокрутки хранится в ее свойстве rowHeaderView). Так что если вам нужно увидеть заголовок таблицы, придется включить таблицу в панель прокрутки или вручную разместить около верхней границы таблицы компонент JTableHeader. Чуть позже мы обсудим подробнее заголовок таблицы.
Ну а сейчас посмотрим, насколько просто можно создавать вполне работоспособные таблицы стандартного вида с помощью специальных конструкторов:
//SimpleTables.java
//Простые таблицы с помощью удобных конструкторов import javax.swing.*;
import java.util.*; import java.awt.*;
public class SimpleTables extends JFrame { // данные для таблиц
private Object[][] colors = new String[][] {
{"Красный", "Зеленый", "Синий" },
{"Желтый", "Оранжевый", "Белый" },
}; // названия заголовков столбцов
private Object[] colorsHeader = new String[] { "Цвет", "Еще цвет", "Тоже цвет"
};
public SimpleTables() { super("SimpleTables"); setDefaultCloseOperation(EXIT_ON_CLOSE); // несколько простых таблиц
JTable table1 = new JTable(colors, colorsHeader); JTable table2 = new JTable(5, 5);
//таблица на основе вектора, состоящего из векторов
Vector<Vector<String>> data =
new Vector<Vector<String>>(); Vector<String> row1 = new Vector<String>(); Vector<String> row2 = new Vector<String>();
//вектор с заголовками столбцов
Vector<String> columnNames = new Vector<String>(); // наполнение данными
for (int i=0; i<3; i++) { row1.add("Ячейка 1." + i); row2.add("Ячейка 2." + i);
518 |
ГЛАВА 17 |
columnNames.add("Столбец #" + i);
}
data.add(row1);
data.add(row2);
JTable table3 = new JTable(data, columnNames);
//добавляем таблицы в панель с тремя рядами setLayout(new GridLayout(3, 1));
add(new JScrollPane(table1)); add(new JScrollPane(table2)); add(table3);
//выводим окно на экран
setSize(350, 400); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new SimpleTables(); } });
}
}
В примере мы наследуем от окна JFrame, указываем, что при закрытии окна нужно будет закончить работу приложения, и размещаем в панели содержимого несколько таблиц JTable, созданных с помощью удобных конструкторов. Первая таблица (table1) создается конструктором, принимающим два массива с данными. Первый массив должен быть двухмерным и хранить строки таблицы с данными некоторого типа. Мы использовали в качестве данных самое простое из всего возможного — строки String. Остается только вспомнить, как записываются в Java двухмерные массивы, довольно редкие гости обычных программ. Вы видите, что данные для первой таблицы (две строки с названиями цветов) описаны в двухмерном массиве colors. Второй передаваемый в конструктор массив одномерный и хранит названия столбцов таблицы, с ним все проще. Вторая таблица (table2) создается с помощью, пожалуй, самого простого конструктора, которому требуется указать лишь количество строк и столбцов в таблице. Все ячейки будут пустыми, а в качестве названий столбцов использованы латинские буквы, как во всех электронных таблицах. Такой конструктор требуется редко, но он может быть полезен там, где с помощью таблиц нужно получать информацию от пользователя или имитировать вид электронной таблицы. Ну и, наконец, третий конструктор работает с вектором (динамическим массивом) Vector из пакета java.util. Ему требуется передать два вектора. Первый должен хранить в себе такие же векторы, в которых, в свою очередь, должны храниться данные строк таблицы. Каждый вектор отвечает за свою строку таблицы. Второй вектор хранит обычные объекты — названия столбцов таблицы. В примере мы наполняем векторы строк и названий столбцов в цикле. Затем векторы строк добавляются в вектор, который будет хранить все данные таблицы (data), после чего таблицу table3 можно создавать.
Созданные три таблицы добавляются в окно, в котором мы установили табличное расположение GridLayout из трех рядов и одного столбца. Как нам известно, табличное расположение выделяет своим ячейкам одинаковое пространство. Последовательное расположение FlowLayout и вообще любое расположение, предоставляющее компонен-

Таблицы |
519 |
там все затребованное ими «предпочтительное» пространство, нам не подойдут, потому что таблицы, помещенные в панель прокрутки JScrollPane, очень требовательны к пространству, даже если в них хранится совсем немного данных. Учитывайте этот факт при создании пользовательского интерфейса и подбирайте такой менеджер расположения, который не позволит таблицам в панели прокрутки «отнять» у контейнера слишком много места. С другой стороны, таблицы без панелей прокрутки занимают ровно столько места, сколько им требуется для отображения своих данных. Заметьте, что мы используем панель прокрутки только для первых двух таблиц, а третью добавляем в панель напрямую. После запуска программы с примером вы увидите, что в такой таблице не отображается заголовок. Для вывода заголовка нужно либо добавить таблицу в панель прокрутки (в заголовке которой и окажется заголовок таблицы), либо ввести его в контейнер вручную. Как работать с заголовками таблиц, мы узнаем немного позже.
На заключительном этапе наше окно выводится на экран. Запустив программу с примером, вы убедитесь в том, что несколькими строками кода мы создали вполне работоспособные таблицы, достаточные для многих простых приложений. Разбирая пример, вы также увидите, что выделение в таблицах JTable по умолчанию работает построчно, выделять можно произвольное количество строк, все элементы считаются редактируемыми (начать редактирование позволяет двойной щелчок мыши, таким образом он действует для всех внешних видов, поставляемых с библиотекой Swing), а столбцы таблицы можно перетаскивать и менять местами (правда, делать это можно только с первыми двумя таблицами, у которых есть заголовки). Все это поддается настройке. Эти вопросы мы будем обсуждать постепенно на протяжении всей главы. Перетаскивание столбцов таблицы, с одной стороны, может быть удобным (те столбцы, с которыми он чаще работает, пользователь сделает первыми), однако, с другой стороны, может запутать и усложнить работу даже с простой таблицей (пользователю придется выяснять, какие столбцы были перемещены, где они теперь расположены и в каких логических отношениях они находятся с другими столбцами). Перетаскивание столбцов смотрится эффектно, но, как правило, во избежание путаницы его следует отключать, вскоре мы узнаем, как это делается. С другой стороны, программисту-клиенту таблицы не придется учитывать перемещения столбцов: их номера в таблице и ее моделях не меняются, как бы изощренно пользователь не тасовал столбцы.
520 |
ГЛАВА 17 |
Простая настройка внешнего вида
Мы только что видели, как парой строк кода без привлечения особенных мощностей
ивнутренних ресурсов класса JTable и его «сотрудников» можно создать вполне достойные таблицы. Аналогичные действия можно проделать и при изменении внешнего вида таблицы, причем внешний вид можно поменять полностью, вплоть до переписывания UIпредставителя класса JTable, однако есть путь и попроще. С помощью нескольких полезных свойств можно буйно раскрасить таблицу, использовать различные цвета для текста
ивыделения, настроить сетку таблицы, задать разные расстояния между ячейками. Бывает, что этого хватает для получения той самой эффектной таблицы, которая вам нужна.
Рассмотрим несложный пример с парой таблиц, внешний вид которых мы изменим с помощью некоторых свойств класса JTable. Запустив программу с этим примером, вы воочию убедитесь, как именно влияют те или иные свойства на внешний вид таблиц, а затем мы обсудим их подробнее:
//SimpleTablesLook.java
//Небольшое изменение внешнего вида таблиц
import javax.swing.*; import java.awt.*;
public class SimpleTablesLook extends JFrame { // данные и заголовки для таблицы
private Object[][] data = new String[][] {
{"Мощная", "Синий", "Спортивный" },
{"Экономичная", "Красный", "Классика" }
};
private Object[] columns = new String[] { "Модель", "Цвет", "Дизайн"
};
public SimpleTablesLook() { super("SimpleTablesLook"); setDefaultCloseOperation(EXIT_ON_CLOSE);
//таблица с разными расстояниями между ячейками
JTable table1 = new JTable(data, columns);
//настройка расстояний и цветов table1.setRowHeight(40); table1.setIntercellSpacing(new Dimension(10, 10)); table1.setGridColor(Color.green); table1.setShowVerticalLines(false);
//таблица с разными цветами
JTable table2 = new JTable(data, columns); table2.setForeground(Color.red); table2.setSelectionForeground(Color.yellow); table2.setSelectionBackground(Color.blue); table2.setShowGrid(false);

Таблицы |
521 |
//добавляем таблицы в панель из двух ячеек setLayout(new GridLayout(1, 2, 5, 5)); add(new JScrollPane(table1));
add(new JScrollPane(table2));
//выводим окно на экран
setSize(600, 200); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new SimpleTablesLook(); } });
}
}
В примере мы создаем небольшое окно, в котором разместятся две таблицы. Данные для обеих таблиц одинаковы, они описаны в виде двух массивов: двухмерного data, в нем хранится информация о ячейках таблицы, и одномерного columns, в котором мы разместили названия столбцов таблицы. Первая таблица table1 демонстрирует некоторые свойства класса JTable, позволяющие управлять расстоянием между ячейками, высотой строк таблицы, а также цветом и стилем сетки таблицы. Вторая таблица table2 демонстрирует, как можно изменить цвета, используемые для вывода текста и выделенных элементов, и как отключить прорисовку сетки таблицы. Обе таблицы размещаются в панелях прокрутки и добавляются в окно с установленным табличным расположением, при котором панель разбивается на две ячейки одинакового размера с небольшим (в 5 пикселов) расстоянием между ними. После этого остается только вывести наше окно на экран.
Запустив программу с примером, вы сможете увидеть изменения во внешнем виде таблиц и оценить, в каких ситуациях подобное изменение внешнего вида может вам понадобится. Давайте опишем использованные в примере свойства немного подробнее, так чтобы они всегда были у вас «под рукой» (табл. 17.1).
Таблица 17.1. Свойства, меняющие внешний вид таблицы JTable
Свойства |
Краткое описание |
selectionBackground, Управляют цветами прорисовки фона выделенной ячейки (первое selectionForeground свойство) и текста выделенной ячейки (второе свойство). Вкупе со стандартными свойствами background и foreground, отвечающими за
фон и цвет любого компонента Swing, позволяют полно настроить
цветовую гамму таблицы
522 |
ГЛАВА 17 |
Таблица 17.1 (продолжение) |
|
|
|
Свойства |
Краткое описание |
|
|
rowHeight, intercellSpacing |
Первое свойство позволяет задать высоту сразу всех строк табли- |
|
цы или (с помощью перегруженного метода set) отдельно некоторой |
|
строки таблицы. Второе свойство управляет расстоянием между |
|
ячейками как по оси X, так и по оси Y (в примере вы можете видеть, |
|
что задается это расстояние в виде объекта Dimension) |
showVerticalLines, |
С помощью этих свойств вы можете указывать таблице, как и какие |
showHorizontalLines, |
линии сетки следует рисовать. Первые два свойства по отдельности |
showGrid |
управляют прорисовкой вертикальных и горизонтальных линий сетки |
|
таблицы, а третье свойство позволяет «одним махом» показать или |
|
скрыть всю сетку таблицы |
gridColor |
Позволяет задать цвет сетки таблицы JTable (вы могли видеть, как ис- |
|
пользовать это свойство, в рассмотренном нами примере) |
С помощью перечисленных свойств вы сможете быстро и без особых усилий настроить некоторые аспекты внешнего вида таблицы JTable, иногда именно это нужно интерфейсу программы. Далее, читая эту главу, мы узнаем множество других немного более сложных, но и более тонких способов настройки внешнего вида таблицы, а точнее, ее ячеек.
Модели таблицы JTable
Для правильной работы таблица JTable нуждается сразу в трех моделях, поставляющих ей данные и при изменении этих данных сохраняющих изменения. Первая и самая важная модель, о которой мы уже упоминали в начале главы, хранит непосредственно данные ячеек таблицы, а также дополнительную служебную информацию об этих ячейках. Обязанности этой модели описываются интерфейсом TableModel, который довольно прост, и реализовать его самому не представляет особого труда. Вторая модель управляет столбцами таблицы и описана в интерфейсе TableColumnModel. Столбцы очень важны для любой таблицы: вспомним, что, как правило, строки таблицы представляют собой различные наборы однотипных данных, а столбцы описывают тип хранимых в строках данных и порядок их следования. Именно столбцы определяют, какой набор данных отображает таблица, данные в таблице сортируются по столбцам, так что отдельная модель для столбцов не является не роскошью. Модель столбцов таблицы TableColumnModel позволяет добавлять, перемещать, находить столбцы, узнавать об изменениях в их данных или расположении, с ее помощью также можно изменить расстояние между столбцами
инаходящимися в них ячейками, изменить модель выделения столбцов. Наконец, третья
ипоследняя модель таблицы нам хорошо знакома еще с главы 11, в которой мы знакомились со списками библиотеки Swing — это модель выделения списка ListSelectionModel.
Она управляет выделением строк таблицы и не затрагивает столбцы, в модели которых TableColumnModel есть собственная модель выделения (в качестве последней используется все та же модель выделения списка ListSelectionModel). Так что формально можно говорить о четырех моделях таблицы JTable, просто одна из этих моделей вызывается неявно, через другую модель. Помимо моделей таблица JTable предоставляет еще несколько удобных механизмов выделения строк, столбцов и ячеек; вскоре мы о них узнаем.
Нет ничего удивительного в том, что у вас имеется широкий выбор альтернатив при работе с моделями, в этом заключается сама суть Swing. Вы можете использовать для каждой из них соответствующий абстрактный класс, начинающийся со слова Abstract, в котором тщательно реализованы поддержка слушателей и рассылка событий. Наследуя от такого класса, вам остается только наполнить модель данными, применяя удобные способы и инструменты, и в нужные моменты оповещать присоединенных слушателей
Таблицы |
523 |
об изменениях в данных. Для каждой из моделей таблицы JTable имеется стандартная реализация (класс, название которого начинается со слова Default), хорошо подходящая в тех случаях, когда вам требуется модель, но нет причин или желания писать собственную . Впрочем, никто не отбирает у вас право полностью, «с нуля», написать свою модель и реализовать ее нужным образом. Перейдем теперь непосредственно к работе с моделями и изучим возможности каждой из них.
Модель данных TableModel
Модель TableModel позволяет полно описать каждую ячейку таблицы JTable. Определенные в ней методы дают возможность таблице получить значение произвольной ячейки и изменить его (модель данных таблицы считается изменяемой, для этого не нужно проводить дополнительных действий или реализовывать новых интерфейсов), узнать о том, можно ли пользователю редактировать ячейки, получить исчерпывающую информацию о столбцах (их количестве, названиях и типе) и строках. Нам, со своей стороны, нужно определить эти методы и с их помощью передать таблице все подготовленные данные.
Прежде чем начать разработку собственной модели, можно рассмотреть вариант использования стандартной модели DefaultTableModel. Если вы вспомните первый пример данной главы, где мы создавали простые таблицы с помощью удобных конструкторов, то там таблица JTable неявно создавала и наполняла переданными в конструктор данными именно стандартную модель DefaultTableModel. Модель DefaultTableModel хранит переданные ей данные в двух векторах Vector, примерно так, как мы это делали в первом примере для одного из конструкторов таблицы: в одном из векторов хранятся такие же вектора,
вкоторых в свою очередь хранятся данные строк, а во втором векторе хранятся названия столбцов таблицы. Если вам безразличны структура и способ получения отображаемых в таблице данных, лучше ограничиться стандартной моделью. С другой стороны, у вас остается не так много рычагов управления данными таблицы: все ячейки окажутся редактируемыми, у вас не будет способа тонко настроить тип данных для определенного столбца, то есть таблица будет иметь довольно безликий вид. К недостаткам стандартной модели можно отнести также некоторый архаизм: класс DefaultTableModel появился еще
всамом первом выпуске Swing для JDK 1.1 и использует для хранения данных класс Vector,
устаревший и уступающий по многим показателям новым контейнерам данных (таким как список ArrayList). Так что во многих ситуациях лучше написать собственную модель.
Впрочем, применение стандартной модели все равно остается самым быстрым способом создать таблицу и наполнить ее данными (и вы можете разделять ее между видами и отдельно от пользовательского интерфейса обрабатывать хранящиеся в ней данные). Рассмотрим небольшой пример, который продемонстрирует некоторые возможности стандартной модели:
//UsingDefaultTableModel.java
//Использование стандартной модели при создании таблицы import javax.swing.*;
import javax.swing.table.*; import java.awt.event.*; import java.awt.*;
public class UsingDefaultTableModel extends JFrame { // наша модель
private DefaultTableModel dtm; public UsingDefaultTableModel() {
524 |
ГЛАВА 17 |
super("UsingDefaultTableModel"); setDefaultCloseOperation(EXIT_ON_CLOSE);
//создаем стандартную модель dtm = new DefaultTableModel();
//задаем названия стоблцов dtm.setColumnIdentifiers(
new String[] {"Номер", "Товар", "Цена"});
//наполняем модель данными
dtm.addRow(new String[] {"№1", "Блокнот", "5.5"}); dtm.addRow(new String[] {"№2", "Телефон", "175"}); dtm.addRow(new String[] {"№3", "Карандаш", "1.2"});
//передаем модель в таблицу
JTable table = new JTable(dtm);
//данные могут меняться динамически
JButton add = new JButton("Добавить"); add.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { // добавляем новые данные
dtm.addRow(
new String[] {"?", "Новинка!", "Супер Цена!"});
}
});
JButton remove = new JButton("Удалить"); remove.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { // удаляем последнюю строку (отсчет с нуля) dtm.removeRow(dtm.getRowCount() — 1);
}
});
//добавляем кнопки и таблицу add(new JScrollPane(table)); JPanel buttons = new JPanel(); buttons.add(add); buttons.add(remove); add(buttons, "South");
//выводим окно на экран setSize(300, 300); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {