Перетаскивание пользовательского объекта из встроенного FX (JFXPanel) в Swing
Этот вопрос является дополнительным вопросом для перетаскивания пользовательского объекта из FX в Swing.
Я работаю над плагином для приложения Swing, которое использует JavaFX для некоторых графических пользовательских интерфейсов. Мы добавили функцию перетаскивания, чтобы улучшить взаимодействие с пользователем. Во-первых, мы использовали внешнее окно JavaFX (Stage
) за наших Scene
Теперь мы хотим встроить его непосредственно в приложение Swing через JFXPanel
,
Теперь, странно то, что, кажется, имеет большое значение для перетаскивания точно так же Scene
загружен в Stage
или в JFXPanel
,
Я уже сталкивался с некоторыми проблемами при попытке перетащить некоторый пользовательский объект Java (в сериализованной форме) с пользовательским типом MIME из приложения JavaFX в приложение Swing. Тем не менее, мои проблемы были решены в вопросе, который я упомянул выше. Теперь, используя встроенное приложение JavaFX, я столкнулся с некоторыми новыми проблемами, поэтому я хотел спросить, есть ли у кого-то похожие проблемы или знает решение для этого сценария.
Я написал MVCE, это простое Java-приложение с поддержкой перетаскивания JFXPanel
с одной стороны и опора JPanel
на другой стороне:
public class MyApp {
public static final DataFormat FORMAT = new DataFormat(
// this works fine in a separate window
//"JAVA_DATAFLAVOR:application/x-my-mime-type; class=java.lang.String",
"application/x-my-mime-type; class=java.lang.String");
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 MyApp().run();
}
private void run() {
JFrame frame = new JFrame();
frame.setLayout(new GridLayout(1, 2));
frame.add(buildFX());
frame.add(buildSwing());
frame.setSize(300, 300);
frame.setVisible(true);
}
private JFXPanel buildFX() {
BorderPane parent = new BorderPane();
parent.setOnDragDetected(event -> {
Dragboard dragboard = parent.startDragAndDrop(TransferMode.COPY);
ClipboardContent content = new ClipboardContent();
content.put(FORMAT, "Test");
dragboard.setContent(content);
event.consume();
});
JFXPanel panel = new JFXPanel();
panel.setScene(new Scene(parent));
return panel;
}
@SuppressWarnings("serial")
private JPanel buildSwing() {
JPanel panel = new JPanel();
panel.setBackground(Color.ORANGE);
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;
}
});
return panel;
}
}
Согласно ответу в другом вопросе, используя префикс JAVA_DATAFLAVOR:
в DataFormat
Swing необходим для правильной обработки MIME-типа. Однако при использовании такого DataFormat
внутри JFXPanel
(отключено в примере), похоже, что Java пытается создать DataFlavor
при перетаскивании из приложения FX и не в состоянии анализировать тип MIME с префиксом:
Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: failed to parse:JAVA_DATAFLAVOR:application/x-my-mime-type; class=java.lang.String
at java.awt.datatransfer.DataFlavor.<init>(Unknown Source)
at javafx.embed.swing.SwingDnD$DnDTransferable.getTransferDataFlavors(SwingDnD.java:394)
at sun.awt.datatransfer.DataTransferer.getFormatsForTransferable(Unknown Source)
at sun.awt.dnd.SunDragSourceContextPeer.startDrag(Unknown Source)
at java.awt.dnd.DragSource.startDrag(Unknown Source)
at java.awt.dnd.DragSource.startDrag(Unknown Source)
at java.awt.dnd.DragGestureEvent.startDrag(Unknown Source)
at javafx.embed.swing.SwingDnD.startDrag(SwingDnD.java:280)
at javafx.embed.swing.SwingDnD.lambda$null$66(SwingDnD.java:247)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access$500(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
Используя только чистый MIME-тип без префикса, операция перетаскивания работает, и я даже могу получить правильный DataFlavor
(java.awt.datatransfer.DataFlavor[mimetype=application/x-my-mime-type;representationclass=java.lang.String]
), но пропущенные данные всегда null
, Как видно из другого вопроса, используя этот второй подход с двумя раздельными окнами, я даже не могу получить DataFlavor
, но теперь это работает как-то до этой ограниченной точки.
1 ответ
Вероятно, есть небольшое недопонимание того, как работает передача.
Попытка извлечения данных переноса непосредственно в виде строки может работать для "текста / обычного" или других стандартных типов текста и, как вы заметили, с некоторыми причудами для конкретных случаев нестандартного незарегистрированного типа. Но я не думаю, что усилия для пользовательских обходных путей оправданы.
Поскольку вы полностью управляете структурой содержимого для пользовательского типа MIME, а также с обеих сторон производителя данных и потребителя в одном приложении, я предлагаю не иметь дело с внутренними префиксами, зависящими от реализации инструментария, или отображениями классов. Вероятно, лучше просто определить ваш тип MIME без несвязанных метаданных и некорректных префиксов (как это и должно быть).
Определения типа "application /x-my-mime" и правильного декодирования данных должно быть достаточно.
Образец 1 (сериализованные данные)
Ниже, исправленный из вашего примера, должен сбрасывать данные в кадре Swing в Java 8.
package jfxtest;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.datatransfer.DataFlavor;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.Collections;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.DataFormat;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.TransferHandler.TransferSupport;
public class MyApp {
final static String MY_MIME_TYPE = "application/x-my-mime";
public static final DataFormat FORMAT = new DataFormat(MY_MIME_TYPE);
public static final DataFlavor FLAVOR = new DataFlavor(MY_MIME_TYPE, "My Mime Type");
private void startDrag(Node node) {
node.startDragAndDrop(TransferMode.COPY).setContent(
Collections.singletonMap(FORMAT, "Test"));
}
private boolean processData(TransferSupport support) {
try (InputStream in = (InputStream) support.getTransferable().getTransferData(FLAVOR)) {
Object transferred = new ObjectInputStream(in).readObject();
System.out.println("transferred: " + transferred + " (" + transferred.getClass() + ")");
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public static void main(String[] args) {
new MyApp().run();
}
private void run() {
JFrame frame = new JFrame();
frame.setLayout(new GridLayout(1, 2));
frame.add(buildSwing());
SwingUtilities.invokeLater(() -> {
frame.add(buildFX());
});
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private JFXPanel buildFX() {
BorderPane parent = new BorderPane();
parent.setOnDragDetected(event -> {
startDrag(parent);
event.consume();
});
JFXPanel panel = new JFXPanel();
panel.setScene(new Scene(parent));
return panel;
}
private JPanel buildSwing() {
JPanel panel = new JPanel();
panel.setBackground(Color.ORANGE);
panel.setTransferHandler(new TransferHandler() {
private static final long serialVersionUID = 1L;
@Override
public boolean canImport(TransferSupport support) {
return support.isDataFlavorSupported(FLAVOR);
}
@Override
public boolean importData(TransferSupport support) {
if (canImport(support)) {
return processData(support);
}
return false;
}
});
return panel;
}
}
Выход: transferred: Test (class java.lang.String)
Существенная выдержка здесь:
...
final static String MY_MIME_TYPE = "application/x-my-mime";
public static final DataFormat FORMAT = new DataFormat(MY_MIME_TYPE);
public static final DataFlavor FLAVOR = new DataFlavor(MY_MIME_TYPE, "My Mime Type");
private void startDrag(Node node) {
node.startDragAndDrop(TransferMode.COPY).setContent(
Collections.singletonMap(FORMAT, "Test"));
}
private boolean processData(TransferSupport support) {
try (InputStream in = (InputStream) support.getTransferable().getTransferData(FLAVOR)) {
Object transferred = new ObjectInputStream(in).readObject();
System.out.println("transferred: " + transferred + " (" + transferred.getClass() + ")");
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
...
Обратите внимание, что получение данных упрощено в целях иллюстрации, для реального приложения, возможно, потребуется добавить более строгое чтение потока, обработку ошибок и т. Д.
Образец 2 (пользовательская пантомима с текстом)
В первом примере передается сериализованный объект (что, как правило, хорошо и просто, поскольку вы можете передавать все, что угодно сериализуемо, но затрудняет передачу / прием, скажем, стороннего JSON). В маловероятном случае, когда вы хотите создать реальный текст или другой произвольный контент для пользовательского MIME вместо сериализованного объекта, ниже следует выполнить эту работу:
package jfxtest;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.datatransfer.DataFlavor;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.DataFormat;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.TransferHandler.TransferSupport;
public class MyApp {
final static String MY_MIME_TYPE = "application/x-my-mime";
public static final DataFormat FORMAT = new DataFormat(MY_MIME_TYPE);
public static final DataFlavor FLAVOR = new DataFlavor(MY_MIME_TYPE, "My Mime Type");
private void startDrag(Node node) {
node.startDragAndDrop(TransferMode.COPY).setContent(
// put a ByteBuffer to transfer the content unaffected
Collections.singletonMap(FORMAT, StandardCharsets.UTF_8.encode("Test")));
}
private boolean processData(TransferSupport support) {
try (InputStream in = (InputStream) support.getTransferable().getTransferData(FLAVOR)) {
byte[] textBytes = new byte[in.available()];
in.read(textBytes);
String transferred = new String(textBytes, StandardCharsets.UTF_8);
System.out.println("transferred text: " + transferred);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public static void main(String[] args) {
new MyApp().run();
}
private void run() {
JFrame frame = new JFrame();
frame.setLayout(new GridLayout(1, 2));
frame.add(buildSwing());
SwingUtilities.invokeLater(() -> {
frame.add(buildFX());
});
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private JFXPanel buildFX() {
BorderPane parent = new BorderPane();
parent.setOnDragDetected(event -> {
startDrag(parent);
event.consume();
});
JFXPanel panel = new JFXPanel();
panel.setScene(new Scene(parent));
return panel;
}
private JPanel buildSwing() {
JPanel panel = new JPanel();
panel.setBackground(Color.ORANGE);
panel.setTransferHandler(new TransferHandler() {
private static final long serialVersionUID = 1L;
@Override
public boolean canImport(TransferSupport support) {
return support.isDataFlavorSupported(FLAVOR);
}
@Override
public boolean importData(TransferSupport support) {
if (canImport(support)) {
return processData(support);
}
return false;
}
});
return panel;
}
}
Выход: transferred text: Test
Важная часть здесь:
...
private void startDrag(Node node) {
node.startDragAndDrop(TransferMode.COPY).setContent(
// put a ByteBuffer to transfer the content unaffected
Collections.singletonMap(FORMAT, StandardCharsets.UTF_8.encode("Test")));
}
private boolean processData(TransferSupport support) {
try (InputStream in = (InputStream) support.getTransferable().getTransferData(FLAVOR)) {
byte[] textBytes = new byte[in.available()];
in.read(textBytes);
String transferred = new String(textBytes, StandardCharsets.UTF_8);
System.out.println("transferred text: " + transferred);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
...
Еще раз, в качестве иллюстрации, обработка потоков, ошибок и т. Д. Здесь упрощена.
Следует отметить, что существует также предопределенный "application / x-java-serialized-object" (DataFlavor.javaSerializedObjectMimeType
) для более общей и простой десериализации. Но долгосрочный пользовательский MIME кажется более гибким и простым в обращении.