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

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

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);

}

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