Custom object drag-and-drop from embedded FX (JFXPanel) to Swing

笑着哭i 提交于 2019-12-06 01:35:59

Probably there is a bit of misunderstanding of how the transfer works.

Trying to retrieve the transfer data directly as a string may work for "text/plain" or other standard text types and, as you note, with some quirks for particular cases of custom unregistered type. But I don't think the effort for custom workarounds is justified.

Since you control entirely the content structure for the custom mime type and both ends of the data producer and the consumer in the same application, I suggest not to deal with internal toolkit implementation-dependent prefixes or class mappings. Probably better is just to define your MIME type without unrelated metadata and malformed prefixes (as it is supposed to be).

Defining an "application/x-my-mime" type and correctly decoding the data should be enough.


Sample 1 (serialized data)

The below, corrected from your sample, should drop the data fine to the Swing frame in 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;
  }

}

Output: transferred: Test (class java.lang.String)

The essential excerpt here is:

...

  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;
  }

...  

Note, the data retrieval is simplistic for illustration purposes, for a real application one probably would like to add a more rigorous stream reading, handling errors, etc.


Sample 2 (custom mime with text)

The first sample transfers a serialized object (which is usually a good and simple thing, as you can transfer anything serializable, but makes it hard to transfer/accept, say, 3rd party JSON). In the unlikely case when you wish to produce real text or other arbitrary content for the custom MIME instead of a serialized object, the below should do the job:

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;
  }

}

Output: transferred text: Test

The essential part here is:

...

  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;
  }  

...

Once again, being an illustration, the stream, error, etc. handling here is simplistic.


One thing to note is that there is also a predefined "application/x-java-serialized-object" (DataFlavor.javaSerializedObjectMimeType) for the more generic and easier deserialization. But long-term custom MIME seems more flexible and more straightforward to handle overall.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!