自定义对象从嵌入式FX(JFXPanel)拖放到Swing [英] Custom object drag-and-drop from embedded FX (JFXPanel) to Swing

查看:171
本文介绍了自定义对象从嵌入式FX(JFXPanel)拖放到Swing的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题是对后续问题 to-swing>自定义对象从FX拖放到Swing

This question is a follow-up question to Custom object drag-and-drop from FX to Swing.

我正在为使用JavaFX的Swing应用程序创建一个插件对于一些图形用户界面。我们添加了拖放功能以改善用户体验。首先,我们在 Scene 中使用外部JavaFX窗口( Stage ),现在我们想直接嵌入它Swing应用程序通过 JFXPanel

I'm working on a plugin for a Swing application that uses JavaFX for some graphical user interfaces. 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.

现在,奇怪的是,它似乎有很大的不同用于拖放是否在 Stage 中加载完全相同的 Scene > JFXPanel

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.

尝试使用自定义MIME类型拖动某些自定义Java对象(以序列化形式)时,我遇到了一些问题将JavaFX应用程序转换为Swing应用程序。但是,我在上面提到的问题中解决了我的问题。现在,使用嵌入式JavaFX应用程序,我遇到了一些新问题,所以我想询问是否有人遇到类似问题或者知道这种情况的解决方案。

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. 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.

我是写了一个MVCE,它是一个简单的Java应用程序,一边是拖动支持 JFXPanel ,另一边是支持drop的 JPanel 另一方面:

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:

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_DATAFLAV:是Swing正确处理MIME类型所必需的。但是,当在 JFXPanel 中使用这样的 DataFormat 时(在示例中禁用),似乎Java试图构建从FX应用程序拖动时, DataFlavor 无法使用前缀解析MIME类型:

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. 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:

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] ),但丢弃的数据总是如此。正如在另一个问题中看到的,使用第二种方法使用两个独立的窗口,我甚至无法接收 DataFlavor ,但现在它以某种方式工作到这个限制点。

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. 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.

推荐答案

可能存在对转移如何运作的一些误解。

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

尝试直接以字符串形式检索传输数据可能适用于text / plain或其他标准文本类型,正如您所注意到的那样,针对特定情况的一些怪癖自定义未注册类型。但我不认为定制解决方法的努力是合理的。

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.

因为你完全控制了自定义mime类型的内容结构和数据生成器的两端以及在同一个应用程序中的消费者,我建议不要处理内部工具包依赖于实现的前缀或类映射。可能更好的方法是定义你的MIME类型而没有不相关的元数据和格式错误的前缀(因为它应该是)。

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).

定义application / x-my -mime 输入并正确解码数据就足够了。

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

以下内容(从您的示例中更正)应该将数据精确地丢弃到Java 8中的Swing框架。

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

}

输出: transfer:Test(类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;
  }

...  

注意,数据检索为了说明的目的,这是一个简单的过程,对于一个真正的应用程序,人们可能希望添加更严格的流读取,处理错误等。

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.

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

}

输出:转移文本:测试

这里的基本部分是:

...

  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.

有一点需要注意的是,还有一个预定义的application / x-java-serialized-object( DataFlavor.javaSerializedObjectMimeType ),用于更通用和更简单的反序列化。但是长期自定义MIME似乎更灵活,更易于整体处理。

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.

这篇关于自定义对象从嵌入式FX(JFXPanel)拖放到Swing的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆