Cette question est une question de suivi à glisser-déposer d'objets personnalisés de FX Swing .
Je travaille sur un plug - in pour une application Swing qui utilise JavaFX pour certaines interfaces utilisateur graphiques. Nous avons ajouté la fonctionnalité glisser-déposer pour améliorer l'expérience utilisateur. Tout d' abord, nous utilisions une fenêtre externe JavaFX ( Stage
) pour notre Scene
, maintenant nous voulons l'intégrer directement dans l'application Swing par un JFXPanel
.
Maintenant, la chose étrange est qu'il semble faire une grande différence pour le glisser-déposer si exactement le même Scene
est chargé dans un Stage
ou dans un JFXPanel
.
Je l'ai déjà rencontré quelques problèmes lorsque vous essayez de faire glisser un objet Java personnalisé (sous forme sérialisée) avec un type MIME personnalisé à partir d'une application JavaFX dans une application Swing. Cependant, mes problèmes ont été résolus dans la question je l'ai mentionné ci-dessus. Maintenant, en utilisant l'application JavaFX intégrée, je rencontre de nouveaux problèmes, donc je voulais demander si quelqu'un a eu des problèmes similaires ou connaît une solution pour ce scénario.
J'ai écrit un MVCE, il est une simple application Java avec un soutien glisser JFXPanel
sur un côté et une goutte d'appui JPanel
de l'autre côté:
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;
}
}
Selon la réponse à l'autre question, en utilisant le préfixe JAVA_DATAFLAVOR:
en DataFormat
est nécessaire pour Swing gérer correctement le type MIME. Cependant, lors de l' utilisation d' un tel DataFormat
dans un JFXPanel
(désactivé dans l'exemple), il semble que Java tente de construire un DataFlavor
lors du déplacement de l'application FX et ne parvient pas à analyser le type MIME avec le préfixe:
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)
En utilisant uniquement le type MIME pur, sans le préfixe, les travaux de glisser-déposer et je peux même recevoir le bon DataFlavor
( java.awt.datatransfer.DataFlavor[mimetype=application/x-my-mime-type;representationclass=java.lang.String]
), mais les données sont toujours abandonnées null
. Comme on le voit dans l'autre question, en utilisant cette seconde approche avec deux fenêtres séparées, je ne peux même pas recevoir DataFlavor
, mais maintenant il fonctionne en quelque sorte à ce point limité.
Il y a probablement un peu de mal comprendre la façon dont le transfert fonctionne.
Essayer de récupérer les données de transfert directement en tant que chaîne peut travailler pour « text / plain » ou d'autres types de texte standard et, comme vous le notez, avec quelques bizarreries pour des cas particuliers de type non enregistré personnalisé. Mais je ne pense pas que l'effort de solutions de contournement personnalisés est justifiée.
Puisque vous contrôlez entièrement la structure du contenu pour le type MIME personnalisé et les deux extrémités du producteur de données et le consommateur dans la même application, je vous suggère de ne pas traiter avec la boîte à outils internes préfixes dépendant de l'implémentation ou les correspondances de classe. Probablement mieux est juste de définir votre type MIME sans métadonnées et sans rapport avec préfixes malformés (comme il est censé être).
Définition d' une « application / x-my-mime type » et le décodage correctement les données devrait être suffisant.
L'échantillon 1 (données sérialisées)
Le ci-dessous, corrigé de votre échantillon, devrait laisser tomber l'amende de données au cadre Swing en 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;
}
}
Production: transferred: Test (class java.lang.String)
L'extrait est essentiel ici:
...
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;
}
...
Remarque, la récupération de données est simpliste à des fins d'illustration, pour un vrai d'application serait probablement à ajouter une lecture de flux plus rigoureux, les erreurs de manipulation, etc.
Exemple 2 (mime personnalisé avec le texte)
Les premiers transferts d'échantillons d'un objet sérialisé (ce qui est généralement une bonne chose et simple, comme vous pouvez transférer serialisable quoi que ce soit, mais il est difficile de transférer / accepter, par exemple, JSON 3ème partie). Dans le cas improbable où vous souhaitez produire un texte réel ou tout autre contenu arbitraire pour le MIME personnalisé au lieu d'un objet sérialisé, le soufflet doit faire le travail:
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;
}
}
Production: transferred text: Test
L'essentiel est ici:
...
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;
}
...
Encore une fois, être une illustration, le flux, erreur, etc. manipulation ici est simpliste.
Une chose à noter est qu'il ya aussi une « application / x-java-objet sérialisé » prédéfini ( DataFlavor.javaSerializedObjectMimeType
) pour la désérialisation plus générique et plus facile. Mais MIME de coutume à long terme semble plus souple et plus facile à gérer l' ensemble.