此问题是对Custom object drag-and-drop from FX to Swing的后续问题。
我正在为使用JavaFX的某些图形用户界面的Swing应用程序开发插件。我们添加了拖放功能以改善用户体验。首先,我们为Stage
使用了一个外部JavaFX窗口(Scene
),现在我们想通过JFXPanel
将其直接嵌入到Swing应用程序中。
现在,奇怪的是,无论是将完全相同的Scene
加载到Stage
还是JFXPanel
中,对于拖放操作似乎都有很大的不同
当尝试将具有自定义MIME类型的某些自定义Java对象(序列化形式)从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从FX应用程序中拖动时试图构造DataFlavor
且无法解析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]
),但删除的数据始终为{{ 1}}。从另一个问题中可以看出,使用第二种方法将两个分离的窗口一起使用,我什至无法收到null
,但现在它可以在某种程度上达到这一极限。
答案 0 :(得分:2)
对于转移的工作方式可能存在一些误解。
尝试直接以字符串形式检索传输数据可能适用于“文本/纯文本”或其他标准文本类型,并且如您所注意到的,对于自定义未注册类型的特殊情况,有些古怪之处。但是我认为进行自定义变通办法的努力是没有道理的。
由于您完全控制了自定义mime类型的内容结构以及同一应用程序中数据生产者和使用者的两端,因此我建议不要处理依赖于内部工具包实现的前缀或类映射。可能更好的方法是只定义您的MIME类型,而不必使用不相关的元数据和不正确的前缀(应该是这样)。
定义“ application / x-my-mime ”类型并正确解码数据就足够了。
根据您的示例更正的以下内容,应将数据细化到Java 8中的Swing框架中。
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;
}
...
请注意,出于说明目的,数据检索非常简单,对于实际应用而言,可能希望添加更严格的流读取,处理错误等。
第一个示例传输一个序列化的对象(这通常是一件好事,很简单,因为您可以传输任何可序列化的东西,但是很难传输/接受(例如,第三者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似乎更灵活,更易于处理。