Перетаскивание пользовательских объектов из FX в Swing
Я работаю над приложением JavaFX, которое должно взаимодействовать с существующим приложением Swing с помощью перетаскивания. Обмен данными с помощью перетаскивания фактически работает, но мы хотим переработать эту часть функциональности, чтобы фактически обмениваться пользовательскими объектами Java вместо простых строк с объектами, сериализованными в JSON. Проблема в том, что пользовательский интерфейс Swing не получает перетаскиваемые данные, если вместо, например, используются пользовательские типы MIME. text/plain
, Ниже вы можете найти минимальный пример как для приложения перетаскивания (JavaFX), так и для приложения перетаскивания (Swing).
FxDrag
public class FxDrag extends Application {
private static final DataFormat format = new DataFormat("application/x-my-mime-type");
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
BorderPane root = new BorderPane();
root.setOnDragDetected(event -> {
Dragboard dragboard = root.startDragAndDrop(TransferMode.COPY);
ClipboardContent content = new ClipboardContent();
content.putString("Test");
// content.put(format, "Test");
dragboard.setContent(content);
event.consume();
});
stage.setScene(new Scene(root, 300, 300));
stage.setTitle("Drag");
stage.show();
}
}
SwingDrop
public class SwingDrop {
public static void main(String[] args) {
new SwingDrop().run();
}
private void run() {
JPanel panel = new JPanel();
panel.setTransferHandler(new TransferHandler() {
@Override
public boolean canImport(TransferSupport support) {
return true;
}
@Override
public boolean importData(TransferSupport support) {
Stream.of(support.getDataFlavors()).forEach(flavor -> {
System.out.println(flavor.getMimeType());
});
return super.importData(support);
}
});
JFrame frame = new JFrame();
frame.setTitle("Drop");
frame.add(panel);
frame.setSize(300, 300);
frame.setVisible(true);
}
}
Когда положить String
с помощью putString
к content
в приложении JavaFX приложение Swing получает перетаскивание и предоставляет следующие варианты:
application/x-java-serialized-object; class=java.lang.String
text/plain; class=java.io.Reader; charset=Unicode
text/plain; class=java.lang.String; charset=Unicode
text/plain; class=java.nio.CharBuffer; charset=Unicode
text/plain; class="[C"; charset=Unicode
text/plain; class=java.io.InputStream; charset=unicode
text/plain; class=java.nio.ByteBuffer; charset=UTF-16
text/plain; class="[B"; charset=UTF-16
text/plain; class=java.io.InputStream; charset=UTF-8
text/plain; class=java.nio.ByteBuffer; charset=UTF-8
text/plain; class="[B"; charset=UTF-8
text/plain; class=java.io.InputStream; charset=UTF-16BE
text/plain; class=java.nio.ByteBuffer; charset=UTF-16BE
text/plain; class="[B"; charset=UTF-16BE
text/plain; class=java.io.InputStream; charset=UTF-16LE
text/plain; class=java.nio.ByteBuffer; charset=UTF-16LE
text/plain; class="[B"; charset=UTF-16LE
text/plain; class=java.io.InputStream; charset=ISO-8859-1
text/plain; class=java.nio.ByteBuffer; charset=ISO-8859-1
text/plain; class="[B"; charset=ISO-8859-1
text/plain; class=java.io.InputStream; charset=windows-1252
text/plain; class=java.io.InputStream
text/plain; class=java.nio.ByteBuffer; charset=windows-1252
text/plain; class="[B"; charset=windows-1252
text/plain; class=java.io.InputStream; charset=US-ASCII
text/plain; class=java.nio.ByteBuffer; charset=US-ASCII
text/plain; class="[B"; charset=US-ASCII
Я даже могу отбрасывать разные данные из разных приложений, таких как браузеры и т. Д., А приложение Swing предоставляет соответствующие варианты данных в виде (текст, изображения и т. Д.).
Однако, если я использую свой собственный формат, никакие ароматы не будут перечислены вообще. Может ли Swing фильтровать данные, передаваемые через приложение перетаскивания?
2 ответа
Старый ответ не работал между отдельными приложениями. Новая попытка ниже.
Мне удалось заставить это работать между отдельными приложениями Swing и JavaFX в обоих направлениях. Я загрузил рабочий пример в репозиторий GitLab, если вы хотите его просмотреть, но здесь я рассмотрю некоторые основы.
Если вы посмотрите на хранилище, то заметите, что у меня есть подпроект Gradle model
который содержит класс модели com.example.dnd.model.Doctor
, Этот класс Serializable
и содержит три свойства: firstName
, lastName
, а также number
, Этот проект используется совместно приложениями JavaFX и Swing (т.е. они используют одну и ту же модель). В каждом приложении у меня есть таблица, которая отображает список Doctor
с этими свойствами: TableView
в JavaFX и JTable
в качелях.
Приложения позволяют перетаскивать одну или несколько строк в другое приложение и добавлять их в конец таблицы. Они делают это, отправив список соответствующих Doctor
s.
В примере требуется Java 10. GIF примера в действии.
JavaFX
Я обнаружил, что сторона JavaFX намного проще в реализации. На самом деле, единственное, что вам нужно выработать, это как настроить соответствующий DataFormat
, Тип MIME, который я использовал, был,
application/x-my-mime-type; class=com.example.dnd.model.Doctor
class=
параметр важен на стороне Swing; используется для десериализации. После некоторых проб и ошибок я обнаружил, что при попытке перетащить данные из Swing в JavaFX к данному типу MIME добавляется JAVA_DATAFLAVOR:
, делая это:
JAVA_DATAFLAVOR:application/x-my-mime-type; class=com.example.dnd.model.Doctor
Я должен был добавить это к DataFormat
используется в onDragDetected
Обработчик в противном случае Swing не распознает формат данных. Я не знаю, почему это так, и я не нашел документации об этом. При изменении версий Java и / или платформ я буду осторожен с этим, если это зависит от реализации (если вам не удается найти документацию).
В конце концов, мой DataFormat
было объявлено так:
DataFormat format = new DataForamt(
"JAVA_DATAFLAVOR:application/x-my-mime-type; class=com.example.dnd.model.Doctor",
"application/x-my-mime-type; class=com.example.dnd.model.Doctor"
);
Я добавил два идентификатора, один с JAVA_DATAFLAVOR
и один без, в попытке охватить оба случая (где это необходимо и нет). Я не знаю, нужно ли это, и помогает ли это вообще. Затем я сохранил это в некоторых static final
поле для глобального доступа.
Тогда вы просто реализуете onDragXXX
обработчики, как и следовало ожидать.
свинг
Swing сторона была немного более вовлечена в мое мнение; хотя это может быть только потому, что мне больше нравится JavaFX. Я хотел бы отметить, что уроки Oracle были очень полезны здесь. В Swing было три важных класса, связанных с DnD:
DataFlavor
- Свинг
DataFormat
, но сложнее
- Свинг
TransferHandler
- В основном
onDragXXX
обработчики как один класс
- В основном
Transferable
- Представляет передаваемые данные
1 - Есть другие участвующие классы, но это были три, которые я нашел наиболее важными в этом случае.
Чтобы это работало, мне пришлось создавать собственные реализации TransferHandler
а также Transferable
,
TransferHandler
import com.example.dnd.model.Doctor;
import java.awt.datatransfer.Transferable;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.TransferHandler;
public class DoctorTransferHandler extends TransferHandler {
@Override
public boolean canImport(TransferSupport support) {
return support.isDrop() && support.isDataFlavorSupported(DoctorTransferable.DOCTOR_FLAVOR);
}
@Override
public boolean importData(TransferSupport support) {
if (!canImport(support)) {
return false;
}
JTable table = (JTable) support.getComponent();
DoctorTableModel model = (DoctorTableModel) table.getModel();
try {
Transferable transferable = support.getTransferable();
ArrayList<Doctor> list =
(ArrayList<Doctor>) transferable.getTransferData(DoctorTransferable.DOCTOR_FLAVOR);
model.addAll(list);
return true;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}
@Override
public int getSourceActions(JComponent c) {
return COPY_OR_MOVE;
}
@Override
protected Transferable createTransferable(JComponent c) {
JTable table = (JTable) c;
DoctorTableModel model = (DoctorTableModel) table.getModel();
return new DoctorTransferable(model.getAll(table.getSelectedRows()));
}
@Override
protected void exportDone(JComponent source, Transferable data, int action) {
if (action == MOVE) {
JTable table = (JTable) source;
DoctorTableModel model = (DoctorTableModel) table.getModel();
model.removeAll(model.getAll(table.getSelectedRows()));
}
}
}
переводный
import com.example.dnd.model.Doctor;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
public class DoctorTransferable implements Transferable {
public static final DataFlavor DOCTOR_FLAVOR;
static {
try {
DOCTOR_FLAVOR = new DataFlavor("application/x-my-mime-type; class=java.util.ArrayList");
} catch (ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
private final ArrayList<Doctor> doctors;
public DoctorTransferable(Collection<? extends Doctor> doctors) {
this.doctors = new ArrayList<>(doctors);
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{DOCTOR_FLAVOR};
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return DOCTOR_FLAVOR.equals(flavor);
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (DOCTOR_FLAVOR.equals(flavor)) {
return doctors;
}
throw new UnsupportedFlavorException(flavor);
}
}
Если вы посмотрите на декларацию DataFlavor
, внутри Transferable
вы увидите, что я использую тот же тип MIME, что и в JavaFX минус JAVA_DATAFLAVOR:
немного.
Я считаю, что наиболее важной частью является создание собственного Transferable
который обрабатывает ваш пользовательский объект. это Transferable
будет создан в защищенном TransferHandler#createTranserfable
метод. Только когда я понял, что мне нужно это сделать, мне удалось заставить это работать. Это Transferable
который отвечает за сообщение DataFlavor
и как получить объекты.
Следующие две важные вещи, которые вы должны сделать, это переопределить canImport
а также importData
, Эти методы определяют, могут ли перетаскиваемые данные быть успешно отброшены и, если это так, как добавить их в компонент Swing. Мой пример довольно прост и добавляет данные в конец JTable
модель.
Для экспорта данных вы также должны переопределить exportDone
, Этот метод отвечает за выполнение любой очистки, если передача включала перемещение данных, а не только их копирование.
Я достиг этого решения через приличное количество проб и ошибок. В результате, в сочетании с тем фактом, что я хотел сделать это как можно проще, многие "стандартные" функции не реализованы. Например, данные всегда добавляются в конец таблицы, а не вставляются там, где их отбрасывают. На стороне JavaFX обработчики перетаскивания находятся на всем TableView
а не на каждом TableCell
(что было бы логичнее, я думаю).
Я надеюсь, что это работает для вас. Если нет, пожалуйста, дайте мне знать.
Просто для удобства добавлю комбинацию отличного решения @Slaw и минимального примера из моего вопроса. Чтобы лучше понять, взгляните на его ответ, так как он более подробный.
FxDrag
public class FxDrag extends Application {
public static final DataFormat FORMAT = new DataFormat(
"JAVA_DATAFLAVOR:application/x-my-mime-type; class=java.lang.String",
"application/x-my-mime-type; class=java.lang.String");
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
BorderPane root = new BorderPane();
root.setOnDragDetected(event -> {
Dragboard dragboard = root.startDragAndDrop(TransferMode.COPY);
ClipboardContent content = new ClipboardContent();
content.put(FORMAT, "Test123");
dragboard.setContent(content);
event.consume();
});
stage.setScene(new Scene(root, 300, 300));
stage.setTitle("Drag");
stage.show();
}
}
SwingDrop
public class SwingDrop {
public static final DataFlavor FLAVOR;
static {
try {
FLAVOR = new DataFlavor("application/x-my-mime-type; class=java.lang.String");
} catch (ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
public static void main(String[] args) {
new SwingDrop().run();
}
private void run() {
JPanel panel = new JPanel();
panel.setTransferHandler(new TransferHandler() {
@Override
public boolean canImport(TransferSupport support) {
return support.isDataFlavorSupported(FLAVOR);
}
@Override
public boolean importData(TransferSupport support) {
if (!canImport(support)) return false;
try {
String data = (String) support.getTransferable().getTransferData(FLAVOR);
System.out.println(data);
return true;
} catch (UnsupportedFlavorException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
});
JFrame frame = new JFrame("Drop");
frame.getContentPane().add(panel);
frame.setSize(300, 300);
frame.setVisible(true);
}
}
Операции DragAndDrop из приложений FX в приложение Swing возможны с этими примерами приложений. Невозможно перетащить в любое другое приложение, даже если переданные данные просто String
, Это работает так же, как и для улучшения юзабилити.