Портянкин И. Swing
.pdf
Таблицы |
565 |
super("CustomTableEditor"); setDefaultCloseOperation(EXIT_ON_CLOSE);
// создаем таблицу на основе стандартной модели
DefaultTableModel model =
new DefaultTableModel(data, columns) { // необходимо указать тип столбца
public Class getColumnClass(int column) { return data[0][column].getClass();
}
};
JTable table = new JTable(model); table.setRowHeight(20);
//указываем редактор для дат table.setDefaultEditor(
Date.class, new DateCellEditor());
//выводим окно на экран
getContentPane().add(new JScrollPane(table)); setSize(400, 300);
setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new CustomTableEditor(); } });
}
}
Здесь мы создаем таблицу JTable на основе стандартной модели, эта таблица затем разместится в панели прокрутки, которая, в свою очередь, разместится в центре панели содержимого окна. Данные для таблицы, как это уже не раз бывало, находятся в двух массивах: одномерный массив columns содержит названия столбцов, а двухмерный data — непосредственно данные ячеек. Обратите внимание, что второй столбец содержит даты, представленные объектами Date. Оба массива мы передаем в специальный конструктор стандартной модели DefaultTableModel и здесь же переопределяем метод модели getColumnClass(). Это необходимо для того, чтобы указать, что второй столбец нашей таблицы хранит объекты типа Date. Если мы этого не сделаем, то наш новый редактор не будет задействован для их редактирования. Переопределять метод isCellEditable() нам не потребовалось: стандартная модель по умолчанию считает, что редактировать можно все ячейки.
566 |
ГЛАВА 17 |
Далее мы регистрируем в таблице созданный нами новый редактор по умолчанию в качестве редактора для столбцов с данными типа Date. Видно, что для этого предназначен метод setDefaultEditor(). В дополнение ко всему высота ячеек таблицы немного увеличивается — это позволит счетчику JSpinner чувствовать себя в них более комфортно, поэтому им будет удобнее пользоваться. Запустив программу с примером, вы увидите, что первый столбец таблицы редактируется посредством обычного текстового поля, а вот для редактирования второго применяется наш новый редактор на основе счетчика JSpinner. Без всякого сомнения, это во много раз повышает удобство работы с датами в таблицах. Аналогичным образом вы сможете написать редакторы и для остальных нестандартных типов данных, редактировать которые средствами стандартных редакторов по тем или иным причинам неудобно.
Заголовок таблицы JTableHeader
Заголовок таблицы, реализованный в классе JTableHeader из пакета javax.swing.table, отображает названия столбцов, а также позволяет перетаскивать столбцы, меняя их местами, и менять их размеры. Мы уже знаем, что таблица JTable автоматически добавляет заголовок таблицы в качестве верхнего заголовка панели прокрутки, если вы добавляете таблицу в панель прокрутки. Если таблица будет размещена в любом другом контейнере, места для заголовка таблицы не найдется, поэтому подыскивать его придется самостоятельно.
Заголовок таблицы представляет собой самый обыкновенный компонент со своим UI-представителем, отвечающим за его внешний вид. Данные для работы заголовок таблицы черпает из модели столбцов таблицы TableColumnModel, которую необходимо передавать в конструктор класса JTableHeader или присоединять к заголовку позже. Фактически, заголовок таблицы выступает как дополнительный вид модели столбцов (наряду с таблицей JTable) — он позволяет перетаскивать столбцы и менять их размеры, меняя данные в модели. Никаких данных в самом заголовке не хранится — все находится в модели TableColumnModel (рассматривая модели столбцов TableColumnModel и объектов TableColumn мы уже выяснили, что именно в них хранится исчерпывающая информация о столбцах, в том числе и об их размерах и порядке следования на экране).
Со стандартным заголовком таблицы можно сделать немногое, но все предоставляемые нам возможности очень полезны и весьма часто востребованы. Для начала, вы можете включить или отключить перетаскивание столбцов и смену их размеров. Далее, заголовок таблицы обладает собственным отображающим объектом, который и отвечает за то, как выглядит название столбца и его оформление. По умолчанию отображающий объект для заголовка таблицы предоставляет UI-представитель класса JTableHeader, но это легко изменить, придав тем самым заголовку вашей таблицы уникальный узнаваемый вид (тем более, что мы хорошо знакомы с процессом написания отображающих объектов). Рассмотрим небольшой пример, в котором присоединим к заголовку таблицы собственный отображающий объект, а заодно настроим несколько дополнительных свойств заголовка:
//UsingTableHeader.java
//Настройка заголовка таблицы JTableHeader import javax.swing.*;
import javax.swing.table.*; import javax.swing.border.*; import java.awt.*;
public class UsingTableHeader extends JFrame { // данные для таблицы
Таблицы |
567 |
private String[][] data = {
{"Июнь", "+18 С" },
{"Июль", "+22 С" },
{"Август", "+19 С" }
}; // названия столбцов
private String[] columnNames = { "Месяц", "Средняя температура"
};
public UsingTableHeader() { super("UsingTableHeader"); setDefaultCloseOperation(EXIT_ON_CLOSE); // создаем таблицу
JTable table = new JTable(data, columnNames);
//настраиваем заголовок таблицы
JTableHeader header = table.getTableHeader(); header.setReorderingAllowed(false); header.setResizingAllowed(false);
//присоединяем отображающий объект header.setDefaultRenderer(new HeaderRenderer());
//добавляем таблицы в панель прокрутки
add(new JScrollPane(table)); setSize(400, 300); setVisible(true);
}
// специальный отображающий объект для заголовка class HeaderRenderer
extends DefaultTableCellRenderer {
// метод возвращает компонент для прорисовки public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
//получаем настроенную надпись от базового класса
JLabel label = (JLabel)super. getTableCellRendererComponent(table,
value, isSelected, hasFocus, row, column);
//настраиваем особую рамку и цвет фона label.setBackground(Color.gray); label.setBorder(border);
return label;
568 |
ГЛАВА 17 |
|
} |
|
private Border border = BorderFactory. |
|
createMatteBorder(16, 16, 16, 16, |
|
new ImageIcon("bullet.png")); |
|
} |
|
public static void main(String[] args) { |
|
SwingUtilities.invokeLater( |
|
new Runnable() { |
|
public void run() { new UsingTableHeader(); } }); |
} |
} |
|
В примере мы создаем небольшое окно, в центре которого разместится панель прокрутки с расположенной в ней таблицей JTable. Таблицу мы создаем на основе двух массивов: двумерный массив data хранит данные ячеек, а одномерный columnNames — названия столбцов. После создания таблицы мы получаем ее заголовок (для этого предназначен метод getTableHeader()) и принимаемся за его настройку. Для начала меняются два наиболее полезных свойства заголовка: свойство reorderingAllowed контролирует, будет ли доступна возможность перетаскивания столбцов, а свойство resizingAllowed позволяет включить (или, как в нашем примере, отключить) возможность смены размеров столбцов. Напоследок мы устанавливаем для заголовка свой отображающий объект (присоединить его позволяет метод setDefaultRenderer()). Собственно отображающий объект реализован во внутреннем классе HeaderRenderer и не представляет собой ничего хитрого: он унаследован от стандартного отображающего объекта DefaultTableCellRenderer, на который и перекладывается большая часть работы. К полученной в результате работы стандартного объекта надписи (а вы помните, что стандартный отображающий объект представляет собой именно надпись JLabel) прибавляется необычная рамка, составленная на основе небольшого значка.
После запуска программы с примером вы оцените настройку заголовка таблицы: помимо его необычного и красочного внешнего вида вы не сможете поменять столбцы местами или изменить их размеры. Только не забудьте, что стандартный заголовок таблицы применяется для операций сортировки и прорисовывает направление сортировки. Если в таблице включена сортировка и вам необходимо использовать нестандартный заголовок, придется позаботиться о прорисовке значка сортировки самостоятельно, узнавая об изменениях в сортировке с помощью слушателя объекта RowSorter.
Очень часто заголовок таблицы JTableHeader применяется для добавления к таблице операций для конкретного столбца. Для этого к заголовку таблицы присоединяется слушатель событий от мыши, следящий за щелчками на заголовках столбцов. После щелчка
Таблицы |
569 |
легко определить, на каком именно столбце он произошел (вызвав метод columnAtPoint() из класса JTableHeader), получить данные этого столбца (в этом поможет модель данных TableModel), как-либо применить их и дать сигнал таблице о том, что данные изменились и вид пора обновить, если это необходимо.
Резюме
Разнообразная двухмерная информация, особенно полученная из баз данных, их отображений на объекты средствами, подобными iBatis и Hibernate, или из компонентов Enterprise JavaBeans, — частый гость в приложениях. Благодаря таблице JTable, ее без преувеличения фантастической гибкости и безграничным возможностям настройки, вывод этой информации не станет для вас проблемой. Как мы убедились, читая материал этой главы, с таблицей JTable можно делать практически все. Чем больше знакомишься с компонентом JTable, тем более поражаешься глубинам его возможностей и убеждаешься в том, что таблица JTable иллюстрирует саму суть Swing, позволяя с элегантной простотой и мощью выводить на экран любые данные.
Круговорот данных |
571 |
лен классом с очевидным названием Clipboard, класс этот находится в пакете java.awt. datatransfer, который, как легко судить по названию, хранит все инструменты для работы с обменом данных.
Напрямую буфер обмена Clipboard обычно не создается (хотя это возможно), да в этом и нет смысла, как правило, нас интересует системный буфер обмена данными. Чтобы получить к нему доступ, используется фабрика классов AWT Toolkit, метод которой getSystemClipboard() возвращает системный буфер. С его помощью можно получать данные из буфера или передавать их туда. Однако данные у каждого приложения свои, и необходимо предоставить способ их распознавания. Для этого служат типы данных для обмена.
Типы данных для обмена
Современные системы позволяют хранить в буфере обмена все что угодно, будь то простой текст или изображения огромных размеров. Прежде чем что-то получить из буфера, особенно если это что-то получено из другого приложения, нам необходимо понять, как именно представлены данные и каким образом их можно обработать. Существует много разных способов описать данные, но есть и стандарт под названием MIME (MultiPurpose Internet Mail Extenstions), возникший после того, как к сообщениям электронной почты стало возможным присоединять данные разных типов. Для того чтобы как-то определить, что за данные присоединены к почте и как с ними обращаться, была введена классификация типов MIME. Грубо говоря, тип описывается как совокупность базового типа (это может быть текст, видео или что-то еще) и его конкретного типа. В итоге описание типа выглядит как строка вида «text/html». Именно такое описание типов используют классы для обмена данными в Java.
Нам не придется углубляться в детали спецификации MIME, потому что основную часть работы выполняет вспомогательный класс DataFlavor из все того же пакета java.awt.datatransfer. Он сопоставляет описание типа MIME и Java-класс, который может представить этот тип. Данный класс позволяет создавать и свои уникальные типы данных, которые вы можете описать в формате MIME, но, как правило, хватает и стандартных типов. Они описаны как поля-константы класса DataFlavor и представляют собой конкретные объекты этого класса:
Таблица 18.1. Основные типы данных для обмена
Стандартный тип |
Предназначение |
|
|
DataFlavor.stringFlavor |
Основа из основ, тип данных для строки языка Java String, кото- |
|
рая, как известно, представляет собой набор символов Unicode. |
|
Как правило, большая часть данных при обмене может быть |
|
представлена в виде строки |
DataFlavor. javaJVMLocal- |
Тип данных для любого объекта в вашем Java-приложении, но |
ObjectMimeType |
именно в вашем приложении, между разными виртуальными |
|
машинами объекты этого типа не передать. Этот тип данных |
|
хорош для обмена данными между компонентами внутри Java- |
|
приложения, однако данные такого типа не сможет понять ни |
|
одно системное или любое другое приложение, так что в допол- |
|
нение к ним можно представить, к примеру, строковое представ- |
|
ление объекта |
DataFlavor.imageFlavor |
Описывает всевозможные изображения, данные при этом пред- |
|
ставлены как класс изображений AWT Image. |
572 |
ГЛАВА 18 |
|
Таблица 18.1 (продолжение) |
||
|
|
|
Стандартный тип |
Предназначение |
|
|
|
|
DataFlavor.javaFileListFlavor |
Представляет собой список файлов, выбранных в файловой си- |
|
|
стеме. Такие данные, к примеру, могут быть переданы в буфер об- |
|
мена различными файловыми приложениями, когда пользователь
копирует файл в буфер или «вырезает» его из текущей директо-
рии. Данные представлены как список List, в котором находятся
объекты, представляющие файлы (или директории) File.
В абсолютном большинстве случаев вполне достаточно стандартных типов данных, более того, из других приложений вы можете получить только описанные данные описанных выше типов. В своем же приложении вы вольны заполнять буфер локальными объектами Java и работать с ними как обычно, мы уже видели, что для этого есть свой тип данных. Никто не запрещает данным иметь и два, и даже больше типов. Пример этого мы вскоре рассмотрим.
Теперьмы,наконец,можемначатьработусбуферомобмена.Длятогочтобывыяснить, что же хранится в буфере, класс Clipboard предоставляет метод getAvailableDataFlavors(), который возвращает массив всех типов для данных, находящихся в буфере в текущий момент. Обнаружив в типах то, что мы умеем обрабатывать или показывать, мы можем получить данные. Посмотрим, как это происходит:
//ClipboardContents.java
//Доступ к содержимому буфера обмена import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.datatransfer.Clipboard;
import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.io.IOException;
import javax.swing.*;
public class ClipboardContents extends JFrame { public ClipboardContents() {
super("ClipboardContents");
//выход при закрытии окна setDefaultCloseOperation(EXIT_ON_CLOSE);
//добавим кнопку получения содержимого
JButton getButton = new JButton("Получить");
//текстовое поле для вывода данных
final JTextArea textArea = new JTextArea(); getButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { try {
// буфер обмена
Круговорот данных |
573 |
Clipboard clipboard = Toolkit.getDefaultToolkit(). getSystemClipboard();
// выведем все типы данных for (DataFlavor next:
clipboard.getAvailableDataFlavors()) { textArea.append(next.toString() + "\n"); // попытка получить сами данные textArea.append(clipboard.
getData(next).toString() + "\n");
}
}catch (UnsupportedFlavorException ex) { ex.printStackTrace();
}catch (IOException ex) { ex.printStackTrace();
}
}
});
add(new JScrollPane(textArea)); add(getButton, "South");
// выведем окно на экран setSize(400, 300); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new ClipboardContents(); } });
}
}
Как и всегда, пример максимально прост, так что мы сможем сконцентрироваться на том, что происходит с данными внутри буфера обмена. Мы создаем окно, в центре которого располагается текстовая область в панели прокрутки, так что она сможет показать нам текст любого размера. Внизу окна мы добавили кнопку, при нажатии которой происходит анализ содержимого системного буфера обмена.
При нажатии кнопки мы получаем ссылку на системный буфер обмена, а также все доступные на текущий момент типы данных, которые находятся в буфере. Чтобы получить их, вызываем уже известный нам метод getAvailableDataFlavors(). Для каждого поддерживаемого данными типа мы выводим строковое представление типа на экран, а также с помощью метода getData() пытаемся получить объект, которые представляет эти данные в Java-приложении.Мы не рассчитываем на конкретный тип данных, поэтому не можем преобразовать полученный объект во что-то более ценное, и просто выводим в текстовую область его строковое представление. Обратите внимание, что нам приходится учитывать исключения, которые могут возникнуть при невозможности получить данные запрошенного типа (в нашем примере это невозможно, так как мы
574 |
ГЛАВА 18 |
получаем данные типа, который точно поддерживается ими) или ошибке при считывании этих данных (IOException). Запустив приложение, скопируйте что-нибудь в буфер обмена системы, например файлы или просто текст, и нажмите кнопку, чтобы изучить содержимое и типы объектов, которыми оно представлено.
На указанном снимке экрана приложения видно, как сначала в буфер попал список файлов, описанный как прекрасно всем нам знакомый список элементов List. Cписок выводится на экран в виде распечатки всех своих элементов, объектов File с их полными путями в файловой системе.
Далее ситуация становится еще более интересной. В буфер был скопирован текст из внешнего редактора («привет из блокнота!»), казалось бы, он будет представлен только как объект String, но не тут-то было. Если для нас в Java-приложении действительно хватает строки String, то для системных приложений этого мало, и более того, они могут воспринимать строки по-разному. Здесь система обмена данными выполняет за нас всю нудную работу — она представляет строку в виде множества комбинаций, в различных кодировках, в виде набора байтов, в виде потока данных, и много другого, вы все сможете увидеть, запустив приложение. Ну а в Java-приложении остается лишь получить строку String, в универсальном формате Unicode, и оценить свои преимущества — в таких ситуациях универсальность Java окупает себя как никогда.
Итак, задача получения данных, хоть и влечет за собой анализ типа этих данных и преобразование к соответствующему объекту, который представляет данные, все же несложна, и встроить в приложение получение из системного буфера строк, изображений или файлов можно всего несколькими строками кода. Однако это лишь одна сторона медали, приложение должно также позволять пользователям копировать свои данные и переносить их в другие приложения. Сделать это просто, требуется один лишь метод setContents() класса Clipboard, но вот сами данные при этом придется подготовить для выхода «на люди». Посмотрим, как это делается.
Адаптация данных
Платформа Java создана работать одинаково на всех операционных системах и аппаратных комбинациях, однако при обмене данными с приложениями «родной» системы встает вопрос понимания друг друга. В предыдущем разделе мы узнали, как нам понять, что же лежит в системном буфере обмена и как эти данные перевести в понятные нам объекты Java. С другой стороны, очевидно, что наше Java-приложение имеет свои форматы данных, даже простых строк, и их необходимо представить так, чтобы
