Круговорот данных |
575 |
их правильно поняли и в других приложениях, будь они системными или другими Javaприложениями.
Задачей адаптации данных Java-приложения для обмена ими с системой занимается интерфейс Transferable из хорошо известного нам пакета java.awt.datatransfer. Именно реализацию этого интерфейса необходимо передать в метод setContents() класса Clipboard, чтобы разместить свои данные в системном буфере. По сути, интерфейс просто связывает данные Java-приложения, представленные некоторым объектом, с описанием типа данных DataFlavor, так что реализовать его самостоятельно совсем несложно1.
Таблица 18.2. Методы интерфейса Transferable
Метод |
Предназначение |
getTransferDataFlavors() |
Возвращает массив всех типов, поддерживаемых данными, которые |
|
описываются интерфейсом. Подразумевается, что самым первым эле- |
|
ментов массива идет самый удобный тип, последним — соответствен- |
|
но самый мало подходящий тип данных |
isDataFlavorSupported() |
Позволяет определить, поддерживается ли данными переданный в ка- |
|
честве параметра тип |
getTransferData() |
Основной метод, возвращает объект, который будет представлять дан- |
|
ные для указанного типа. К примеру, для типа-изображения необходимо |
|
будет вернуть изображение Image |
Видно, что для адаптации данных необходимо всего лишь определиться с тем, какие типы будут поддерживаться и для каждого их них возвращать соответствующий объект. Объекты, сопоставленные типам класса DataFlavor, мы обсуждали в предыдущем разделе, анализируя содержимое буфера обмена.
Как всегда, не обязательно для каждого случая с нуля писать свою реализацию интерфейса Transferable, есть и реализация по умолчанию, которая называется StringSelection. В конструктор этого класса можно передать строку String, и затем передать полученный объект в буфер обмена. Это прекрасный и быстрый способ вставить в буфер обмера строку с текстом, однако вы редко будете использовать этот класс напрямую, так как все текстовые компоненты Swing и без того обладают встроенной поддержкой операций копирования и вставки текста. Гораздо чаще встречается ситуация когда вам нужно хранить в буфере обмена не только текстовое представление данных, но и другое представление, к примеру, Java-объект или изображение. В этом случае реализовать интерфейс Transferable придется самостоятельно. Сделать это несложно, попробуем вставить
вбуфер обмена данные нескольких типов:
//PastingClipboard.java
//Вставка данных в системный буфер обмена import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.datatransfer.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener;
1 Мы недаром используем в этом разделе термин «адаптация», ведь интерфейс Transferable по сути и является представителем известного шаблона проектирования «адаптер»
576 |
ГЛАВА 18 |
import java.io.IOException; import java.util.Arrays;
public class PastingClipboard extends JFrame { private JTextArea textArea;
public PastingClipboard() { super("PastingClipboard");
//выход при закрытии окна setDefaultCloseOperation(EXIT_ON_CLOSE);
//добавим кнопку для вставки данных
JButton copyButton = new JButton("Копировать"); // текстовое поле для получения текста textArea = new JTextArea();
copyButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {
// буфер обмена
Clipboard clipboard = Toolkit.getDefaultToolkit(). getSystemClipboard();
// вставим в буфер наши адаптированные данные clipboard.setContents(new ComplexTransferable(), null);
}
});
add(new JScrollPane(textArea)); add(copyButton, "South");
// выведем окно на экран setSize(400, 300); setVisible(true);
}
// объект-адаптер для наших данных
class ComplexTransferable implements Transferable { // список поддерживаемых типов данных
private DataFlavor[] supportedTypes = new DataFlavor[]
{ DataFlavor.stringFlavor, DataFlavor.imageFlavor }; public DataFlavor[] getTransferDataFlavors() {
return supportedTypes;
}
public boolean isDataFlavorSupported(DataFlavor flavor) { return Arrays.asList(supportedTypes).contains(flavor);
}
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
Круговорот данных |
577 |
if ( flavor == DataFlavor.imageFlavor ) {
//если запрошено изображение, вернем красный квадрат
BufferedImage bi = new
BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); Graphics g = bi.getGraphics();
g.setColor(Color.RED); g.fillRect(0, 0, 100, 100); return bi;
}else if ( flavor == DataFlavor.stringFlavor ) {
//если запрошена строка, вернем выделенный текст return textArea.getSelectedText();
}
// исключение в случае неизвестного типа данных throw new UnsupportedFlavorException(flavor);
}
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new PastingClipboard(); } });
}
}
В примере мы создадим окно JFrame и разместим в его центре многострочное текстовое поле JTextArea, текст, который в нем набирается, потом можно будет скопировать в буфер обмена. На юге окна разместится кнопка, по нажатию которой в буфере обмена будут размещаться наши данные. При нажатии кнопки мы получаем системный буфер обмена уже знакомым нам методом класса Toolkit, и вызываем его метод setContents(), который размещает переданный ему объект Transferable в буфере. Вторым параметром этого метода можно передать слушатель событий, который будет оповещаться в случае утраты «владения» буфером обмена, то есть если другое приложение разместит в нем свои данные и перезапишет ваши. Эта возможность, хоть и может быть полезной, редко востребуется, и вместо слушателя мы передаем в метод пустую ссылку null.
Самое интересное скрыто в классе ComplexTransferable, который адаптирует несколько видов данных для размещения в системном буфере. Класс реализует интерфейс Transferable и все три его метода, которые мы чуть ранее обсудили. Их очень несложно реализовать . Мы поддерживаем два типа данных — строки и изображения, оба эти типа стандартны и описаны как константы класса DataFlavor. В методе getTransferData() нам остается вернуть строку или изображение, в зависимости от переданного типа данных. Строку мы берем из текстового поля, это выделенный в данный момент текст, а вот изображение строим в памяти динамически с помощью класса BufferedImage. Для этого требуется задать размер и тип изображения в конструкторе (тип изображения у нас стандартный RGB), а затем можно получить для изображения объект для рисования Graphics и нарисовать на нем все что угодно. Мы для простоты просто закрашиваем все красным цветом. После этого изображение передается для хранения в буфере.
578 |
ГЛАВА 18 |
Запустив приложение, вы сможете набрать в текстовом поле любой текст, выделить его и нажатием на кнопку вставить в буфер обмена не только текст, но и дополнительное изображение. Для проверки того, что все работает, останется найти приложение
споддержкой вставки изображений из буфера обмена, к примеру, любой графический редактора, и убедиться в том, что в буфере обмена находится красный квадрат. Одновременно с этим текстовый редактор сможет получить из буфера строку с нашим текстом.
Как правило, работа со сложными данными и буфером обмена в большом приложении сводится примерно к такому решению, что мы описали в примере. Для обмена
состальными приложениями системы вы размещаете в буфере строки, списки файлов или изображения, а для удобства своей работы заодно храните в буфере ссылку на объект приложения внутреннего типа, так что вам не нужно будет дополнительно обрабатывать его при вставке в другом месте своего приложения.
Буфер обмена и стандартные компоненты Swing
Несмотря на то, что мы разобрались с буфером обмена и подготовкой для него данных и можем все сделать своими руками, многое для нас уже сделано по умолчанию в стандартных компонентах Swing. Прежде всего, это относится ко всем текстовым компонентам, унаследованным от базового класса JTextComponent. В нем определены методы cut(), copy() и paste(), которые простым вызовом позволяют манипулировать текстом в буфере обмена, а также зарегистрированы стандартные клавиатурные сокращения (Control-C и так далее), которые предоставят пользователям привычные операции с помощью клавиатуры. К сожалению, контекстное меню для текстовых компонентов по умолчанию не предусмотрено, и его придется создавать самостоятельно, впрочем, сделать это просто — слушатели пунктов меню будут просто вызывать упомянутые чуть выше методы.
Дело не ограничивается текстовыми компонентами, компоненты посложнее, вроде списков JList, деревьев JTree или таблиц JTable также обладают некоторым уровнем поддержки буфера обмена. В них тоже зарегистрированы стандартные клавиатурные сокращения, нажав которые, пользователь может получить текстовое представление выбранных в данный момент данных (для таблицы это будет даже текстовое представление всех столбцов выбранной строки). Хорошо, что по умолчанию есть и это, но как правило, это всего лишь приятная мелочь. Если сложные компоненты призваны обмениваться данными друг с другом или другими приложениями, обычно приходится писать собственную реализацию этого процесса, но, как мы поняли из изучения обмена данных, сделать это несложно.
Операции перетаскивания
Операции визуального перетаскивания и вставки данных (drag and drop, DnD) выглядят для пользователей чрезвычайно заманчиво, потому что они очень естественны — даже пользуясь мышью, вы, по сути, перетаскиваете ее с места на место. Увидеть такой же процесс на экране приятно и очень понятно — вы берете нечто важное для вас, та-
Круговорот данных |
579 |
щите это в другое место, и оно там оказывается. Конечно, остаются вопросы эффективности подобного интерфейса — перетаскивание обычно удобно с экранными объектами большого размера, например схемами и изображениями, а вот с более мелкими элементами, вроде слов в тексте или очень точными размерами, удобнее пользоваться клавиатурой или меню. Однако для начинающих пользователей, в качестве более удобного интерфейса, перетаскивание довольно удобно.
Реализовать перетаскивание не совсем просто. Мало следить за всевозможными комбинациями движения мыши и клавиатуры, надо еще узнавать, готов ли компонент под курсором мыши принять данные, и понять, в каком месте компонента они окажутся. Поэтому, как правило, операционные системы предоставляют способы управления перетаскиванием более высокого уровня. В AWT перетаскиванию данных посвящен немаленький пакет java.awt.dnd, и пользоваться им возможно, но требуются усилия. К счастью, библиотека Swing предоставляет инструменты более высокого уровня, и чаще всего перетаскивание встроить в приложение довольно просто.
Важнейшую роль играет объект TransferHandler, присоединенный к любому компоненту Swing, унаследованному от класса JComponent. Объект этот хранится в свойстве transferHandler, и вы можете получить его или задать новый, собственный. Задачей этого объекта является предоставление данных в готовом виде из компонента для обмена данными (то есть в виде объекта Transferable, который нам уже знаком по разделу о буфере обмена) и обработка различных ситуаций при перетаскивании и вставке данных в компонент, который, как правило, пользуется моделью и не знает, как добавить в нее новые данные. Все эти спорные моменты улаживает объект TransferHandler, либо стандартными путями, либо с вашей помощью, когда вы переопределяете его методы.
Перетаскивание и вставка данных встроена не во все компоненты, а лишь в те, в которых это, по мнению создателей Swing, имеет смысл. Прежде всего, это текстовые компоненты, унаследованные от класса JTextComponent, там и вставка текста перетаскиванием не только поддерживается, но и включена по умолчанию, в чем вы легко можете убедиться, запустив пример с текстовыми компонентами Swing и перетаскивая в них текст. Не включено по умолчанию перетаскивание данных в списках JList, деревьях JTree и таблицах JTable, но оно там есть и настраивается очень легко. Достаточно установить свойство этих классов dragEnabled в true, как сразу же вы сможете перетаскивать данные с этих компонентов.
Насколько просто включить перетаскивание для упомянутых выше трех компонентов, настолько же требует усилий процесс подготовки данных для обмена данных с другими компонентами или даже приложениями и приема данных в своем компоненте и вставки их в нужное место. Все это обеспечивается методами класса TransferHandler, которые нам необходимо переопределять. К сожалению, данные в каждом случае индивидуальны, и способа отправить их в «путешествие» простым вызовом метода или двух нет. Впрочем, не все так сложно. Давайте составим таблицу с методами класса TransferHandler, которые нам необходимо определить, чтобы данные компонента были доступны не только ему.
Таблица 18.3. Методы класса TransferHandler, необходимые для описания данных и их перетаскивания
Метод Предназначение
createTransferable() В этом методе необходимо создать и вернуть прекрасно нам знакомый объект-адаптер Transferable, который описывает все поддерживаемые типы данных и возвращает их по запросу извне. К примеру, для списка имеет
смысл вернуть строку или объект, который выделен в списке в данный мо-
мент. Этот метод вызывается когда пользователь начинает «тащить» чтото с вашего компонента