[英]Custom object drag-and-drop from embedded FX (JFXPanel) to Swing
這個問題是Custom對象從FX拖放到Swing的后續問題 。
我正在為一個Swing應用程序創建一個插件,該應用程序將JavaFX用於某些圖形用戶界面。 我們添加了拖放功能以改善用戶體驗。 首先,我們為Scene
使用外部JavaFX窗口( Stage
),現在我們想通過JFXPanel
將它直接嵌入到Swing應用程序中。
現在,奇怪的是,無論是在Stage
還是在JFXPanel
加載完全相同的Scene
,拖放似乎都會產生很大的不同。
嘗試將一些自定義Java對象(以序列化形式)與JavaFX應用程序中的自定義MIME類型拖到Swing應用程序中時,我遇到了一些問題。 但是,我在上面提到的問題中解決了我的問題。 現在,使用嵌入式JavaFX應用程序,我遇到了一些新問題,所以我想問一下是否有人有類似的問題或者知道這個場景的解決方案。
我寫了一個MVCE,它是一個簡單的Java應用程序,一邊是支持拖拽的JFXPanel
,另一邊是支持drop的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;
}
}
根據另一個問題的答案,在DataFormat
使用前綴JAVA_DATAFLAVOR:
是Swing正確處理MIME類型所必需的。 但是,在JFXPanel
使用這樣的DataFormat
(在示例中禁用)時,似乎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]
),但刪除的數據始終為null
。 正如在另一個問題中看到的,使用第二種方法和兩個獨立的窗口,我甚至無法接收DataFlavor
,但現在它以某種方式工作到這個有限的點。
可能存在對轉移如何工作的一些誤解。
嘗試直接以字符串形式檢索傳輸數據可能適用於“text / plain”或其他標准文本類型,並且正如您所注意到的,對於自定義未注冊類型的特定情況有一些怪癖。 但我不認為自定義解決方法的努力是合理的。
由於您完全控制自定義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似乎更靈活,更直接地處理整體。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.