我正在开发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应用程序中通过String
将putString
放置到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是否过滤通过拖放应用程序传输的数据类型?
答案 0 :(得分:3)
旧答案在单独的应用程序之间不起作用。下面的新尝试。
我设法使这两个方向在单独的Swing和JavaFX应用程序之间起作用。如果您想查看示例,我已将其工作示例上传到GitLab repository,但在这里我将介绍一些基础知识。
如果查看存储库,您会发现我有一个名为model
的Gradle子项目,其中包含模型类com.example.dnd.model.Doctor
。此类为Serializable
,包含三个属性:firstName
,lastName
和number
。该项目在JavaFX和Swing应用程序之间共享(即它们使用相同的模型)。在每个应用程序中,我都有一个表,其中显示以下属性的Doctor
列表:JavaFX中的TableView
和Swing中的JTable
。
应用程序允许您将一个或多个行拖到另一个应用程序,并将它们附加到表的末尾。他们通过发送适当的Doctor
列表来做到这一点。
该示例需要Java 10。GIF of example in action。
我发现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 重要类:
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
上(我认为这更有意义)。
我希望这对您有用。如果没有,请告诉我。
答案 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
,也无法将其拖到任何其他应用程序。这样做的目的只是为了提高可用性。