简体   繁体   English

自定义对象从嵌入式FX(JFXPanel)拖放到Swing

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

This question is a follow-up question to Custom object drag-and-drop from FX to Swing . 这个问题是Custom对象从FX拖放到Swing后续问题

I'm working on a plugin for a Swing application that uses JavaFX for some graphical user interfaces. 我正在为一个Swing应用程序创建一个插件,该应用程序将JavaFX用于某些图形用户界面。 We added drag-and-drop functionality to improve the user experience. 我们添加了拖放功能以改善用户体验。 First, we were using an external JavaFX window ( Stage ) for our Scene , now we want to embed it directly into the Swing application via a JFXPanel . 首先,我们为Scene使用外部JavaFX窗口( Stage ),现在我们想通过JFXPanel将它直接嵌入到Swing应用程序中。

Now, the strange thing is, that it seems to make a big difference for drag-and-drop whether the exactly same Scene is loaded in a Stage or in a JFXPanel . 现在,奇怪的是,无论是在Stage还是在JFXPanel加载完全相同的Scene ,拖放似乎都会产生很大的不同。

I already encountered some problems when trying to drag some custom Java object (in serialized form) with a custom MIME type from a JavaFX application into a Swing application. 尝试将一些自定义Java对象(以序列化形式)与JavaFX应用程序中的自定义MIME类型拖到Swing应用程序中时,我遇到了一些问题。 However, my problems were solved in the question I mentioned above. 但是,我在上面提到的问题中解决了我的问题。 Now, using the embedded JavaFX application, I encounter some new problems, so I wanted to ask if someone had similar problems or knows a solution for this scenario. 现在,使用嵌入式JavaFX应用程序,我遇到了一些新问题,所以我想问一下是否有人有类似的问题或者知道这个场景的解决方案。

I've written a MVCE, it's a simple Java application with a drag-supporting JFXPanel on the one side and a drop-supporting JPanel on the other side: 我写了一个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;
    }
}

According to the answer in the other question, using the prefix JAVA_DATAFLAVOR: in the DataFormat is necessary for Swing to handle the MIME type correctly. 根据另一个问题的答案,在DataFormat使用前缀JAVA_DATAFLAVOR:是Swing正确处理MIME类型所必需的。 However, when using such a DataFormat inside a JFXPanel (disabled in the example), it seems like Java tries to construct a DataFlavor when dragging from the FX application and fails to parse the MIME type with the prefix: 但是,在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)

Using only the pure MIME type, without the prefix, the drag-and-drop operation works and I can even receive the correct DataFlavor ( java.awt.datatransfer.DataFlavor[mimetype=application/x-my-mime-type;representationclass=java.lang.String] ), but the dropped data is always null . 只使用纯MIME类型,没有前缀,拖放操作工作,我甚至可以收到正确的DataFlavorjava.awt.datatransfer.DataFlavor[mimetype=application/x-my-mime-type;representationclass=java.lang.String] ),但删除的数据始终为null As seen in the other question, using this second approach with two separated windows, I can't even receive the DataFlavor , but now it works somehow to this limited point. 正如在另一个问题中看到的,使用第二种方法和两个独立的窗口,我甚至无法接收DataFlavor ,但现在它以某种方式工作到这个有限的点。

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. 尝试直接以字符串形式检索传输数据可能适用于“text / plain”或其他标准文本类型,并且正如您所注意到的,对于自定义未注册类型的特定情况有一些怪癖。 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. 由于您完全控制自定义mime类型的内容结构以及同一应用程序中数据生成器和使用者的两端,因此我建议不要处理内部工具包实现相关的前缀或类映射。 Probably better is just to define your MIME type without unrelated metadata and malformed prefixes (as it is supposed to be). 可能更好的只是定义你的MIME类型而没有不相关的元数据和格式错误的前缀(应该是这样)。

Defining an "application/ x-my-mime " type and correctly decoding the data should be enough. 定义“application / x-my-mime ”类型并正确解码数据就足够了。


Sample 1 (serialized data) 样本1(序列化数据)

The below, corrected from your sample, should drop the data fine to the Swing frame in Java 8. 从您的示例中纠正的以下内容应该将数据精确地丢弃到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;
  }

}

Output: transferred: Test (class java.lang.String) 输出: 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) 示例2(带文本的自定义mime)

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). 第一个示例传输一个序列化的对象(这通常是一件好事和简单的事情,因为你可以传输任何可序列化的东西,但是很难转移/接受,比如说,第三方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: 在不太可能的情况下,当您希望为自定义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;
  }

}

Output: transferred text: Test 输出: 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. 需要注意的一点是,还有一个预定义的“application / x-java-serialized-object”( DataFlavor.javaSerializedObjectMimeType ),用于更通用和更简单的反序列化。 But long-term custom MIME seems more flexible and more straightforward to handle overall. 但长期自定义MIME似乎更灵活,更直接地处理整体。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM