[英]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);
}
}
通过putString
将String
放入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
,包含三个属性: firstName
, lastName
和number
。 该项目在JavaFX和Swing应用程序之间共享(即它们使用相同的模型)。 在每个应用程序中,我都有一个表格,通过这些属性显示Doctor
s的列表:JavaFX中的TableView
和Swing中的JTable
。
应用程序允许您将一行或多行拖动到另一个应用程序,并将它们附加到表的末尾。 他们通过发送相应Doctor
的清单来做到这一点。
该示例需要Java 10. GIF示例 。
我发现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重要的类:
DataFlavor
DataFormat
,但更复杂 TransferHandler
onDragXXX
处理程序作为一个类 Transferable
1 - 还有其他课程,但这些是我在这种情况下最重要的三个课程。
为了实现这一点,我必须创建TransferHandler
和Transferable
自定义实现。
的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
以及如何检索对象。
您需要做的下两件重要事情是覆盖canImport
和importData
。 这些方法处理是否可以成功删除拖过的数据,如果是,则如何将其添加到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.