繁体   English   中英

自定义对象从FX拖放到Swing

[英]Custom object drag-and-drop from FX to Swing

我正在研究一个JavaFX应用程序,它应该通过拖放与现有的Swing应用程序进行交互。 通过拖放进行的数据交换实际上是可行的,但是我们希望重新设计这部分功能来实际交换自定义Java对象,而不是使用序列化为JSON的对象的简单字符串。 问题是,如果使用自定义MIME类型而不是例如text/plain ,则Swing UI不会接收拖动的数据。 下面您可以找到拖动应用程序(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);
    }
}

通过putStringString放入JavaFX应用程序中的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应用程序在drop(文本,图像等)中提供相应的数据风格。

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

旧答案在单独的应用程序之间无效。 下面的新尝试


我设法在两个方向上在单独的Swing和JavaFX应用程序之间进行此操作。 我将一个工作示例上传到GitLab存储库,如果你想查看它,但我将在这里介绍一些基础知识。

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

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

该示例需要Java 10. GIF示例


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

我不得不将其添加到onDragDetected处理程序中使用的DataFormat ,否则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 3 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 (我认为这会更有意义)。

我希望这适合你。 如果没有,请告诉我。

为方便起见,我将添加@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 ,也无法拖动到任何其他应用程序。 这有助于提高可用性。

暂无
暂无

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

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