Java Swing如何接收application/x-color格式的原生拖拽数据?
问题背景
要给color-palette-viewer添加颜色拖拽到其他应用的功能,文本编辑器用DataFlavor.stringFlavor就能实现,但GTK ColorButton、Qt颜色按钮这类控件不接受文本格式。反过来把这些控件的颜色拖到Java应用时,能识别到DataFlavor为application/x-color; class=java.io.InputStream,但获取数据时会抛出Unsupported data format: 16异常——查JDK源码发现sun.awt.X11.XSelection.getData里硬编码只支持格式8,JDK8和21版本都存在该问题,且这类拖拽仅提供application/x-color一种格式。
无需修改JDK的解决方案
1. 用JNA直接调用X11原生API绕过AWT限制
AWT的DnD桥接层做了格式限制,直接操作X11原生接口就能拿到数据,步骤如下:
- 引入JNA依赖(
net.java.dev.jna:jna和net.java.dev.jna:jna-platform) - 获取当前X11 Display对象
- 在Drop事件中,通过X11的
XGetSelectionOwner、XConvertSelection、XGetWindowProperty等函数直接读取application/x-color对应的数据 - 解析二进制数据:X11下
application/x-color通常是32位ARGB整数(字节序取决于系统),解析后即可得到颜色值
示例代码片段(JNA部分):
import com.sun.jna.Native; import com.sun.jna.platform.unix.X11; public class X11ColorDrop { public static int getDroppedColor() { X11.Display display = X11.INSTANCE.XOpenDisplay(null); X11.Window root = X11.INSTANCE.XDefaultRootWindow(display); X11.Atom colorAtom = X11.INSTANCE.XInternAtom(display, "application/x-color", false); X11.Atom selectionAtom = X11.INSTANCE.XInternAtom(display, "CLIPBOARD", false); X11.INSTANCE.XConvertSelection(display, selectionAtom, colorAtom, X11.None, root, X11.CurrentTime); X11.XEvent event = new X11.XEvent(); X11.INSTANCE.XNextEvent(display, event); X11.Atom actualType = new X11.Atom(); int[] actualFormat = new int[1]; long[] nItems = new long[1]; long[] bytesAfter = new long[1]; X11.XPointer data = new X11.XPointer(); X11.INSTANCE.XGetWindowProperty(display, root, colorAtom, 0, 4, false, X11.AnyPropertyType, actualType, actualFormat, nItems, bytesAfter, data); int color = data.getInt(0); X11.INSTANCE.XFree(data); X11.INSTANCE.XCloseDisplay(display); return color; } }
实际使用时需结合AWT的Drop事件时机,确保在正确的窗口和事件上下文调用函数,同时处理X11事件循环细节。
2. 给自定义Transferable添加application/x-color格式(发送端)
要让Java应用的颜色能拖到GTK/Qt控件,除了stringFlavor,还要提供符合X11标准的application/x-color格式数据:
- 自定义
Transferable类,添加new DataFlavor("application/x-color; class=java.io.InputStream") - 在
getTransferData方法中,返回包含ARGB二进制数据的InputStream——GTK通常期望4字节的ARGB整数(如0xFFRRGGBB格式)
示例自定义Transferable:
import java.awt.Color; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; public class ColorTransferable implements Transferable { private final Color color; private static final DataFlavor COLOR_FLAVOR = new DataFlavor("application/x-color; class=java.io.InputStream"); private static final DataFlavor[] FLAVORS = {COLOR_FLAVOR, DataFlavor.stringFlavor}; public ColorTransferable(Color color) { this.color = color; } @Override public DataFlavor[] getTransferDataFlavors() { return FLAVORS.clone(); } @Override public boolean isDataFlavorSupported(DataFlavor flavor) { return COLOR_FLAVOR.equals(flavor) || DataFlavor.stringFlavor.equals(flavor); } @Override public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { if (COLOR_FLAVOR.equals(flavor)) { int argb = color.getRGB(); byte[] data = new byte[]{ (byte) ((argb >> 24) & 0xFF), (byte) ((argb >> 16) & 0xFF), (byte) ((argb >> 8) & 0xFF), (byte) (argb & 0xFF) }; return new ByteArrayInputStream(data); } else if (DataFlavor.stringFlavor.equals(flavor)) { return String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()); } throw new UnsupportedFlavorException(flavor); } }
使用时在拖拽的DragGestureListener中返回该ColorTransferable即可。
3. 切换到SWT框架处理拖放
SWT直接对接X11原生接口,对DnD格式的支持更完整,包括application/x-color。如果项目兼容SWT,直接用SWT的DragSource和DropTarget就能轻松处理颜色拖拽,无需绕开AWT的限制。
总结
- 接收端:用JNA直接操作X11 API绕过AWT格式检查,或切换到SWT框架
- 发送端:给Transferable添加符合X11标准的
application/x-color数据流,让GTK/Qt控件可识别
内容的提问来源于stack exchange,提问作者Bobby

