自定义对象从FX拖放到Swing

时间:2018-08-01 11:22:03

标签: java swing javafx drag-and-drop

我正在开发JavaFX应用程序,该应用程序应通过拖放与现有的Swing应用程序进行交互。通过拖放进行的数据交换实际上是有效的,但是我们希望对该功能的一部分进行重新设计,以实际交换自定义Java对象,而不是将简单的String与序列化为JSON的对象交换。问题是,如果使用自定义MIME类型而不是例如,则Swing UI不会接收拖动的数据。 text/plain。在下面,您可以找到拖动应用程序(JavaFX)和放置应用程序(Swing)的最小示例。

FxDrag

public class FxDrag extends Application {

    private static final DataFormat format = new DataFormat("application/x-my-mime-type");

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        BorderPane root = new BorderPane();
        root.setOnDragDetected(event -> {
            Dragboard dragboard = root.startDragAndDrop(TransferMode.COPY);
            ClipboardContent content = new ClipboardContent();
            content.putString("Test");
            // content.put(format, "Test");
            dragboard.setContent(content);
            event.consume();
        });
        stage.setScene(new Scene(root, 300, 300));
        stage.setTitle("Drag");
        stage.show();
    }
}

SwingDrop

public class SwingDrop {

    public static void main(String[] args) {
        new SwingDrop().run();
    }

    private void run() {
        JPanel panel = new JPanel();
        panel.setTransferHandler(new TransferHandler() {

            @Override
            public boolean canImport(TransferSupport support) {
                return true;
            }

            @Override
            public boolean importData(TransferSupport support) {
                Stream.of(support.getDataFlavors()).forEach(flavor -> {
                    System.out.println(flavor.getMimeType());
                });
                return super.importData(support);
            }

        });
        JFrame frame = new JFrame();
        frame.setTitle("Drop");
        frame.add(panel);
        frame.setSize(300, 300);
        frame.setVisible(true);
    }
}

在JavaFX应用程序中通过StringputString放置到content时,Swing应用程序会收到拖动并提供以下样式:

application/x-java-serialized-object; class=java.lang.String
text/plain; class=java.io.Reader; charset=Unicode
text/plain; class=java.lang.String; charset=Unicode
text/plain; class=java.nio.CharBuffer; charset=Unicode
text/plain; class="[C"; charset=Unicode
text/plain; class=java.io.InputStream; charset=unicode
text/plain; class=java.nio.ByteBuffer; charset=UTF-16
text/plain; class="[B"; charset=UTF-16
text/plain; class=java.io.InputStream; charset=UTF-8
text/plain; class=java.nio.ByteBuffer; charset=UTF-8
text/plain; class="[B"; charset=UTF-8
text/plain; class=java.io.InputStream; charset=UTF-16BE
text/plain; class=java.nio.ByteBuffer; charset=UTF-16BE
text/plain; class="[B"; charset=UTF-16BE
text/plain; class=java.io.InputStream; charset=UTF-16LE
text/plain; class=java.nio.ByteBuffer; charset=UTF-16LE
text/plain; class="[B"; charset=UTF-16LE
text/plain; class=java.io.InputStream; charset=ISO-8859-1
text/plain; class=java.nio.ByteBuffer; charset=ISO-8859-1
text/plain; class="[B"; charset=ISO-8859-1
text/plain; class=java.io.InputStream; charset=windows-1252
text/plain; class=java.io.InputStream
text/plain; class=java.nio.ByteBuffer; charset=windows-1252
text/plain; class="[B"; charset=windows-1252
text/plain; class=java.io.InputStream; charset=US-ASCII
text/plain; class=java.nio.ByteBuffer; charset=US-ASCII
text/plain; class="[B"; charset=US-ASCII

我什至可以从各种应用程序(如浏览器等)中删除不同的数据,而Swing应用程序则在拖放中提供了各自的数据风格(文本,图像等)。

但是,如果我使用自定义格式,则根本不会列出任何口味。 Swing是否过滤通过拖放应用程序传输的数据类型?

2 个答案:

答案 0 :(得分:3)

旧答案在单独的应用程序之间不起作用。下面的新尝试


我设法使这两个方向在单独的Swing和JavaFX应用程序之间起作用。如果您想查看示例,我已将其工作示例上传到GitLab repository,但在这里我将介绍一些基础知识。

如果查看存储库,您会发现我有一个名为model的Gradle子项目,其中包含模型类com.example.dnd.model.Doctor。此类为Serializable,包含三个属性:firstNamelastNamenumber。该项目在JavaFX和Swing应用程序之间共享(即它们使用相同的模型)。在每个应用程序中,我都有一个表,其中显示以下属性的Doctor列表:JavaFX中的TableView和Swing中的JTable

应用程序允许您将一个或多个行拖到另一个应用程序,并将它们附加到表的末尾。他们通过发送适当的Doctor列表来做到这一点。

该示例需要Java 10。GIF of example in action


JavaFX

我发现JavaFX方面更容易实现。确实,您需要解决的唯一事情就是如何配置适当的DataFormat。我使用的MIME类型是

application/x-my-mime-type; class=com.example.dnd.model.Doctor

class=参数在Swing侧很重要;它用于反序列化。经过反复试验,我发现当您尝试将数据从Swing拖到JavaFX时,给定的MIME类型以JAVA_DATAFLAVOR:开头,使其成为:

JAVA_DATAFLAVOR:application/x-my-mime-type; class=com.example.dnd.model.Doctor

我必须将其添加到DataFormat处理程序中使用的onDragDetected中,否则Swing无法识别数据格式。我不知道为什么会这样,也没有找到有关此文档。在更改Java版本和/或平台时,如果这是与实现有关的行为(除非您设法找到文档),我将对此小心谨慎。

最后,我的DataFormat被这样声明:

DataFormat format = new DataForamt(
    "JAVA_DATAFLAVOR:application/x-my-mime-type; class=com.example.dnd.model.Doctor",
    "application/x-my-mime-type; class=com.example.dnd.model.Doctor"
);

我添加了两个标识符,其中一个带有JAVA_DATAFLAVOR,另一个没有,试图涵盖两种情况(需要和不需要)。我不知道这是否有必要,也无济于事。然后,我将其存储在static final字段中以进行全局访问。

然后,您只需实现预期的onDragXXX处理程序即可。


摇摆

我认为,Swing方面要多一些;尽管那可能只是因为我对JavaFX更加满意。我想提到Oracle Tutorials在这里非常有用。 Swing中与DnD相关的三个 1 重要类:

1-还涉及其他类,但这是我发现在这种情况下最重要的三个类。

要使此工作正常进行,我必须创建TransferHandlerTransferable的自定义实现。

TransferHandler

import com.example.dnd.model.Doctor;
import java.awt.datatransfer.Transferable;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.TransferHandler;

public class DoctorTransferHandler extends TransferHandler {

  @Override
  public boolean canImport(TransferSupport support) {
    return support.isDrop() && support.isDataFlavorSupported(DoctorTransferable.DOCTOR_FLAVOR);
  }

  @Override
  public boolean importData(TransferSupport support) {
    if (!canImport(support)) {
      return false;
    }
    JTable table = (JTable) support.getComponent();
    DoctorTableModel model = (DoctorTableModel) table.getModel();
    try {
      Transferable transferable = support.getTransferable();
      ArrayList<Doctor> list =
          (ArrayList<Doctor>) transferable.getTransferData(DoctorTransferable.DOCTOR_FLAVOR);
      model.addAll(list);
      return true;
    } catch (Exception ex) {
      ex.printStackTrace();
      return false;
    }
  }

  @Override
  public int getSourceActions(JComponent c) {
    return COPY_OR_MOVE;
  }

  @Override
  protected Transferable createTransferable(JComponent c) {
    JTable table = (JTable) c;
    DoctorTableModel model = (DoctorTableModel) table.getModel();
    return new DoctorTransferable(model.getAll(table.getSelectedRows()));
  }

  @Override
  protected void exportDone(JComponent source, Transferable data, int action) {
    if (action == MOVE) {
      JTable table = (JTable) source;
      DoctorTableModel model = (DoctorTableModel) table.getModel();
      model.removeAll(model.getAll(table.getSelectedRows()));
    }
  }

}

可转让

import com.example.dnd.model.Doctor;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;

public class DoctorTransferable implements Transferable {

  public static final DataFlavor DOCTOR_FLAVOR;

  static {
    try {
      DOCTOR_FLAVOR = new DataFlavor("application/x-my-mime-type; class=java.util.ArrayList");
    } catch (ClassNotFoundException ex) {
      throw new RuntimeException(ex);
    }
  }

  private final ArrayList<Doctor> doctors;

  public DoctorTransferable(Collection<? extends Doctor> doctors) {
    this.doctors = new ArrayList<>(doctors);
  }

  @Override
  public DataFlavor[] getTransferDataFlavors() {
    return new DataFlavor[]{DOCTOR_FLAVOR};
  }

  @Override
  public boolean isDataFlavorSupported(DataFlavor flavor) {
    return DOCTOR_FLAVOR.equals(flavor);
  }

  @Override
  public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
    if (DOCTOR_FLAVOR.equals(flavor)) {
      return doctors;
    }
    throw new UnsupportedFlavorException(flavor);
  }

}

如果您在DataFlavor内查看Transferable的声明,您会发现我使用的是与JavaFX相同的MIME类型,减去了JAVA_DATAFLAVOR:位。

我认为最重要的部分是创建自己的Transferable来处理您的自定义对象。该Transferable将以受保护的TransferHandler#createTranserfable方法创建。直到我意识到自己需要这样做时,我才设法使它起作用。由Transferable负责报告DataFlavor以及如何检索对象。

接下来要做的两件事是覆盖canImportimportData。这些方法处理是否可以成功删除拖放的数据,以及如何将其添加到Swing组件。我的示例非常简单,并将数据添加到JTable模型的末尾。

对于导出数据,您还应该覆盖exportDone。如果传输涉及移动数据,而不仅仅是复制数据,则此方法负责执行任何清理操作。


我通过大量的尝试和错误达到了此解决方案。结果,结合我想尽可能简化这一事实,没有实现许多“标准”行为。例如,数据总是附加到表的底部,而不是插入到表的底部。在JavaFX方面,拖动处理程序位于整个TableView上,而不是位于每个TableCell上(我认为这更有意义)。

我希望这对您有用。如果没有,请告诉我。

答案 1 :(得分:0)

为方便起见,我将结合@Slaw的出色解决方案和问题中的最小示例。为了获得更好的见解,请看一下他的答案,因为它的方法更加详细。


FxDrag

public class FxDrag extends Application {

    public static final DataFormat FORMAT = new DataFormat(
        "JAVA_DATAFLAVOR:application/x-my-mime-type; class=java.lang.String",
        "application/x-my-mime-type; class=java.lang.String");

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        BorderPane root = new BorderPane();
        root.setOnDragDetected(event -> {
            Dragboard dragboard = root.startDragAndDrop(TransferMode.COPY);
            ClipboardContent content = new ClipboardContent();
            content.put(FORMAT, "Test123");
            dragboard.setContent(content);
            event.consume();
        });

        stage.setScene(new Scene(root, 300, 300));
        stage.setTitle("Drag");
        stage.show();
    }

}

SwingDrop

public class SwingDrop {

    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 SwingDrop().run();
    }

    private void run() {
        JPanel panel = new JPanel();
        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;
            }

        });
        JFrame frame = new JFrame("Drop");
        frame.getContentPane().add(panel);
        frame.setSize(300, 300);
        frame.setVisible(true);
    }

}
这些示例应用程序可以从FX应用程序到Swing应用程序进行

DragAndDrop操作。即使传输的数据只是纯String,也无法将其拖到任何其他应用程序。这样做的目的只是为了提高可用性。