580 |
ГЛАВА 18 |
Таблица 18.3 (продолжение)
Метод |
Предназначение |
|
|
getSourceActions () |
Здесь нужно вернуть те действия, которые поддерживает компонент при |
|
перетаскивании. По умолчанию никаких действий не поддерживается. Дей- |
|
ствия определены как константы в класса TransferHandler, чаще всего это |
|
COPY (копирование перетаскиванием), MOVE (перемещение данных), или |
|
оба этих действия. Если метод не переопределить, включение перетаски- |
|
вания ни к чему не приведет |
canImport () |
Если компонент сможет принимать данные, этот метод необходимо пе- |
|
реопределить и возвращать true, когда данные подходящие. Этот метод |
|
вызывается когда пользователь пытается что-то «бросить» на компонент. |
|
Как правило, вы анализируете тип данных и решаете, подходит ли он вам. |
|
Доступна также текущая позиция курсора. |
importData() |
Если предыдущий метод «одобрил» данные, они передаются в этот ме- |
|
тод, если пользователь решил вставить их в ваш компонент. Здесь данные |
|
необходимо каким-то образом обработать, вставить в модель или куда-то |
|
еще, и желательно показать пользователю на экране, что данные добавле- |
|
ны. Доступна также текущая позиция курсора, так что данные можно встав- |
|
лять очень точно. |
Мы уже выполняли основные задачи для обмена данных, когда работали с буфером обмена, новым является лишь использование класса TransferHandler. А теперь перейдем к делу и начнем с простых списков JList, между которыми можно будет перетаскивать строки, из которых они состоят, и вставлять любые другие строки, перетаскивая их из других приложений.
//ListDrag.java
//Перетаскивание и вставка данных и TransferHandler import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable;
public class ListDrag extends JFrame { // начальные данные для списков private String[] listData =
new String[] { "Раз", "Два", "Три" }; public ListDrag() {
super("ListDrag");
//выход при закрытии окна setDefaultCloseOperation(EXIT_ON_CLOSE);
//создаем модели
DefaultListModel model1 = new DefaultListModel(), model2 = new DefaultListModel();
for (String element: listData) {
Круговорот данных |
581 |
model1.addElement(element);
model2.addElement(element);
}
// создаем списки
JList list1 = new JList(model1); list1.setTransferHandler(new ListTransferHandler(list1)); list1.setDragEnabled(true);
JList list2 = new JList(model2); list2.setTransferHandler(new ListTransferHandler(list2)); list2.setDragEnabled(true);
//добавим списки на экран setLayout(new GridLayout(1, 2)); add(new JScrollPane(list1)); add(new JScrollPane(list2));
//выведем окно на экран setSize(400, 300); setVisible(true);
}
// объект-адаптер для списков и их данных
class ListTransferHandler extends TransferHandler { private JList list;
public ListTransferHandler(JList list) { this.list = list;
}
@Override
public int getSourceActions(JComponent c) {
// данные могут копироваться или перемещаться return TransferHandler.COPY_OR_MOVE;
}
@Override
public boolean canImport(TransferSupport support) { // поддерживается импорт только строковых данных return support.isDataFlavorSupported(
DataFlavor.stringFlavor);
}
@Override
protected Transferable createTransferable(JComponent c) { // стандартный способ адаптировать строку для обмена return new StringSelection(
list.getSelectedValue().toString());
}
582 |
ГЛАВА 18 |
|
@Override |
|
public boolean importData(TransferSupport support) { |
|
if ( support.isDataFlavorSupported(DataFlavor.stringFlavor) ) { |
|
try { |
|
// добавляем в модель новую строку на выбранную позицию |
|
((DefaultListModel)list.getModel()). |
|
add(list.locationToIndex( |
|
support.getDropLocation().getDropPoint()), |
|
// получение строки из Transferable |
|
support.getTransferable(). |
getTransferData(DataFlavor.stringFlavor));
return true;
} catch (Exception e) { e.printStackTrace();
}
}
return false;
}
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new ListDrag(); } });
}
}
Итак, приступим. В примере создаются два списка JList, которые будут добавлены
вокно, разделенное на две одинаковые части с помощью табличного расположения. Для того чтобы мы смогли что-то добавлять в списки, нам необходима модель, которая допускает добавление элементов, здесь как нельзя лучше пригодится стандартная модель списков DefaultListModel. Мы создаем по модели для каждого списка и добавляем
вних для начала по три простых строки из массива данных. Затем модели передаются
вконструкторы списков.
Создание списков и моделей очень просто и нам прекрасно известно, теперь нужно заставить их уметь отдавать и принимать данные при перетаскивании. Само перетаскивание включается для списков свойством dragEnabled. Этого вполне достаточно. Далее вся работа переносится на объект TransferHandler, который будет отдавать данные при перетаскивании и принимать их при вставке2. В примере мы написали универсальный объект ListTransferHandler, годный для любого списка, работающего со стандартной моделью. Он требует список, данные которого требуется «адаптировать», в качестве параметра своего конструктора. Вспоминая таблицу 18.3 с описанием основных методов, требуемых для обмена данными, реализация нашего класса становится довольно очевидной. Сначала в методе getSourceActions() необходимо указать, какие действия поддерживает
2 Как нетрудно видеть, объект TransferHandler представляет собой пример классического ша- блона «стратегия», когда сторонний объект определяет, как следует обмениваться данными и их описывать, и объект этот легко поменять.
Круговорот данных |
583 |
компонент при перетаскивании. У нас это копирование и перемещение данных, хотя, строго говоря, мы лишь копируем данные из списка, а перемещение добавлено только для удобства (копирование требует нажатия клавиши Control при перетаскивании).
Метод createTransferable() создает данные для обмена в виде объекта Transferable. Нам этот процесс уже хорошо знаком, так как именно в таком виде мы вставляли данные в буфер обмена. Так как в списках у нас данные в виде строк, мы вполне можем ограничиться стандартным адаптером для строк StringSelection, ну а как реализовать более сложные случаи, мы уже знаем.
Самое сложное — это вставка данных в список. Прежде всего мы указываем в методе canImport(), что будем поддерживать лишь строковые данные. Вся необходимая информация о данных, которые пользователь пытается «бросить» на компонент, в том числе и их тип DataFlavor, доступна через параметр метода TransferSupport. Ну а метод importData() уже непосредственно отвечает за вставку данных в компонент. На всякий случай мы проверяем, представляют ли собой данные строку, получаем модель списка, и, подразумевая, что это стандартная модель списка DefaultListModel, добавляем в него новый элемент. Посмотрите, как вычисляется позиция для вставки — метод getDropPoint() позволяет узнать точку, в которой пользователь «бросил» данные, а метод списка locationToIndex() преобразует точку в своих координатах в позицию списка.
Запустив пример, вы сможете вдоволь насладиться «бросанием» текста из списка
всписок, из списка в самого себя (да, формально это не отличается от перетаскивания из других компонентов), или же вообще из других приложений. Самое замечательное
вподдержке перетаскивания то, что мы, по сути, им не занимаемся, а сконцентрированы на задаче преобразования данных. Кстати, написанный нами класс ListTransferHandler
весьма полезен для передачи строковых данных списка и может многократно использоваться и в других ваших программах, если ваш список основан на стандартной модели.
Аналогичным образом работает перетаскивание для деревьев JTree и таблиц JTable — достаточно включить свойство dragEnabled, и описать, как будет организован круговорот данных из компонента и в него. У этих трех компонентов (включая списки), есть и еще одно свойство, которое позволяет указать, каким образом будет прорисовываться место «бросания» данных. По умолчанию в нашем примере используется режим выделения данных особым цветом (элементы списка под курсором выделяются), что не всегда верно, в этом случае скорее стоило показать, что вставка данных будет происходить перед указанным элементом или после него. Изменить графическое представление вставки позволяет свойство dropMode, в которое надо передать одно из значений классаперечисления DropMode. По умолчанию оно равно USE_SELECTION, а в случае с нашим списком скорее стоило использовать INSERT, что лучше отображает вставку на показанную позицию. Остальные возможности класса DropMode вы можете узнать в интерактивной документации JDK.
584 |
ГЛАВА 18 |
А как же остальные компоненты Swing? Можно ли перетаскивать данные с панелей или кнопок? Можно, однако в этом случае придется самостоятельно определять момент начала перетаскивания. Все так же необходимо задать для компонента объект преобразования данных TransferHandler, а затем, в подходящий момент, вызвать его метод exportAsDrag(), который начнет системный процесс перетаскивания данных. Как правило, вы просто присоединяете к компоненту слушатель событий от мыши, следите за перетаскиванием мыши на нем, и при событии перетаскивания вызываете данный метод. Здесь как нельзя лучше пригодится дополнительный конструктор класса TransferHandler, который принимает название свойства компонента для обмена. Это свойство должно иметь методы get и set (если подразумевается вставка данных). К сожалению, такой объект TransferHandler не в состоянии обмениваться данными с остальной операционной системой, даже если свойство представляет собой строку, потому что данные представлены как внутренний объект JVM, однако для простых визуальных операций это может пригодиться. Рассмотрим на простом примере:
//LabelManualDrag.java
//Перетаскивание и вставка данных "вручную" import javax.swing.*;
import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseEvent;
public class LabelManualDrag extends JFrame { public LabelManualDrag() {
super("ListDrag");
//выход при закрытии окна setDefaultCloseOperation(EXIT_ON_CLOSE);
final JLabel label = new JLabel("Просто текст!");
//стандартный способ обмена свойством "text" label.setTransferHandler(new TransferHandler("text"));
//момент начала перетаскивания label.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) { TransferHandler handler = label.getTransferHandler();
//вызов системного перетаскивания
handler.exportAsDrag(label, e, TransferHandler.COPY);
}
});
//добавим текстовое поле и надпись на экран add(new JScrollPane(new JTextArea())); add(label, "South");
//выведем окно на экран
setSize(400, 300); setVisible(true);
}