Java 在Linux上拖放错误期间,如何实现autoscroll的变通方法?
我在滚动窗格中有一个包含许多元素的列表,并且我已经实现了对列表的拖放。当我从列表中选择一个项目并将其拖动到列表的底部时,只要我保持鼠标靠近边缘,列表就会自动向下滚动。这在Windows上正常工作,但在Linux上,列表滚动一个元素,然后停止 下面是一个简短的程序,它揭示了这个错误:Java 在Linux上拖放错误期间,如何实现autoscroll的变通方法?,java,swing,drag-and-drop,jlist,autoscroll,Java,Swing,Drag And Drop,Jlist,Autoscroll,我在滚动窗格中有一个包含许多元素的列表,并且我已经实现了对列表的拖放。当我从列表中选择一个项目并将其拖动到列表的底部时,只要我保持鼠标靠近边缘,列表就会自动向下滚动。这在Windows上正常工作,但在Linux上,列表滚动一个元素,然后停止 下面是一个简短的程序,它揭示了这个错误: import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.datatran
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import javax.swing.DropMode;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.WindowConstants;
public class JListAutoscroll {
protected static Container createUI() {
JList<String> jlist = new JList<>(generateData(100));
setDragAndDrop(jlist);
JScrollPane scrollPane = new JScrollPane(jlist);
JPanel panel = new JPanel(new BorderLayout());
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
private static void setDragAndDrop(JList<String> jlist) {
jlist.setDragEnabled(true);
jlist.setDropMode(DropMode.INSERT);
jlist.setTransferHandler(new ListTransferHandler());
}
private static String[] generateData(int nRows) {
String rows[] = new String[nRows];
for (int i = 0; i < rows.length; i++) {
rows[i] = "element " + i;
}
return rows;
}
private static class ListTransferHandler extends TransferHandler {
@Override
public int getSourceActions(JComponent component) {
return COPY_OR_MOVE;
}
@Override
protected Transferable createTransferable(JComponent component) {
return new ListItemTransferable((JList)component);
}
@Override
public boolean canImport(TransferHandler.TransferSupport support) {
return true;
}
@Override
public boolean importData(TransferHandler.TransferSupport support) {
return true;
}
}
private static class ListItemTransferable implements Transferable {
private String item;
public ListItemTransferable(JList<String> jlist) {
item = jlist.getSelectedValue();
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] { DataFlavor.stringFlavor };
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.equals(DataFlavor.stringFlavor);
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if(!isDataFlavorSupported(flavor)) {
throw new UnsupportedFlavorException(flavor);
}
return item;
}
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("JList Autoscroll");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setContentPane(createUI());
frame.setPreferredSize(new Dimension(400, 600));
frame.pack();
frame.setVisible(true);
}
});
}
}
导入java.awt.BorderLayout;
导入java.awt.Container;
导入java.awt.Dimension;
导入java.awt.datatransfer.DataFlavor;
导入java.awt.datatransfer.transfer;
导入java.awt.datatransfer.UnsupportedFlavorException;
导入java.io.IOException;
导入javax.swing.DropMode;
导入javax.swing.JComponent;
导入javax.swing.JFrame;
导入javax.swing.JList;
导入javax.swing.JPanel;
导入javax.swing.JScrollPane;
导入javax.swing.SwingUtilities;
导入javax.swing.TransferHandler;
导入javax.swing.WindowConstants;
公共类JLIStatoScroll{
受保护的静态容器createUI(){
JList JList=新JList(generateData(100));
setDragAndDrop(jlist);
JScrollPane scrollPane=新的JScrollPane(jlist);
JPanel panel=newjpanel(newborderlayout());
panel.add(滚动窗格,BorderLayout.CENTER);
返回面板;
}
私有静态void setDragAndDrop(JList JList){
jlist.setDragEnabled(真);
jlist.setDropMode(DropMode.INSERT);
setTransferHandler(新的ListTransferHandler());
}
私有静态字符串[]生成数据(int nRows){
字符串行[]=新字符串[nRows];
for(int i=0;i
我实现了一个简单的TransferHandler,它在拖放时不做任何操作,但足以在拖动到列表边缘时显示问题
这似乎是JDK中的一个已知错误,最好在中进行描述。我见过一些建议的解决办法,比如,
但我不清楚如何实施它们。在我看来,我必须创建一个DropTarget子类,我使用的组件应该实现Autoscroll
接口。但是JList
没有实现它!另外,如果我在列表上设置了DropTarget,而不是TransferHandler,我是否会丢失TransferHandler实现的所有默认拖放行为
那么,如何修改我的程序来解决这个错误呢?如中所述,有两个类可以处理拖放操作:
,java.awt.dnd.DropTarget的成员类,负责支持实现DropTargetAutoScroller
接口的组件李>Autoscroll
,是DropHandler
的一个成员类,它在实现javax.swing.TransferHandler
接口的组件上自动执行d&d自动滚动可滚动
JList
,它实现了可滚动的,而不是自动滚动的。但是,如果您在源代码中查找DropTarget
和TransferHandler
,您会注意到autoscroll代码基本相同,并且在这两种情况下都是错误的。该解决方案与DropTarget非常相似,只添加了几行代码。基本上,解决方案是将鼠标光标的位置从组件坐标系转换为屏幕坐标系。这样,在检查鼠标是否移动时,将使用绝对坐标。因此,我们可以从TransferHandler
复制代码,并添加以下几行
太好了。。。但是我们把这个代码放在哪里,如何调用它呢
如果我们查看setTransferHandler()
我们会发现它实际上设置了一个DropTarget
,这是一个名为SwingDr的包私有静态类
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.TooManyListenersException;
import javax.swing.JComponent;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class AutoscrollWorkaround implements DropTargetListener, ActionListener {
private JComponent component;
private Point lastPosition;
private Rectangle outer;
private Rectangle inner;
private Timer timer;
private int hysteresis = 10;
private static final int AUTOSCROLL_INSET = 10;
public AutoscrollWorkaround(JComponent component) {
if (!(component instanceof Scrollable)) {
throw new IllegalArgumentException("Component must be Scrollable for autoscroll to work!");
}
this.component = component;
outer = new Rectangle();
inner = new Rectangle();
Toolkit t = Toolkit.getDefaultToolkit();
Integer prop;
prop = (Integer)t.getDesktopProperty("DnD.Autoscroll.interval");
timer = new Timer(prop == null ? 100 : prop.intValue(), this);
prop = (Integer)t.getDesktopProperty("DnD.Autoscroll.initialDelay");
timer.setInitialDelay(prop == null ? 100 : prop.intValue());
prop = (Integer)t.getDesktopProperty("DnD.Autoscroll.cursorHysteresis");
if (prop != null) {
hysteresis = prop.intValue();
}
}
@Override
public void dragEnter(DropTargetDragEvent e) {
lastPosition = e.getLocation();
SwingUtilities.convertPointToScreen(lastPosition, component);
updateRegion();
}
@Override
public void dragOver(DropTargetDragEvent e) {
Point p = e.getLocation();
SwingUtilities.convertPointToScreen(p, component);
if (Math.abs(p.x - lastPosition.x) > hysteresis
|| Math.abs(p.y - lastPosition.y) > hysteresis) {
// no autoscroll
if (timer.isRunning()) timer.stop();
} else {
if (!timer.isRunning()) timer.start();
}
lastPosition = p;
}
@Override
public void dragExit(DropTargetEvent dte) {
cleanup();
}
@Override
public void drop(DropTargetDropEvent dtde) {
cleanup();
}
@Override
public void dropActionChanged(DropTargetDragEvent e) {
}
private void updateRegion() {
// compute the outer
Rectangle visible = component.getVisibleRect();
outer.setBounds(visible.x, visible.y, visible.width, visible.height);
// compute the insets
Insets i = new Insets(0, 0, 0, 0);
if (component instanceof Scrollable) {
int minSize = 2 * AUTOSCROLL_INSET;
if (visible.width >= minSize) {
i.left = i.right = AUTOSCROLL_INSET;
}
if (visible.height >= minSize) {
i.top = i.bottom = AUTOSCROLL_INSET;
}
}
// set the inner from the insets
inner.setBounds(visible.x + i.left,
visible.y + i.top,
visible.width - (i.left + i.right),
visible.height - (i.top + i.bottom));
}
@Override
public void actionPerformed(ActionEvent e) {
updateRegion();
Point componentPosition = new Point(lastPosition);
SwingUtilities.convertPointFromScreen(componentPosition, component);
if (outer.contains(componentPosition) && !inner.contains(componentPosition)) {
autoscroll(componentPosition);
}
}
private void autoscroll(Point position) {
Scrollable s = (Scrollable) component;
if (position.y < inner.y) {
// scroll upward
int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, -1);
Rectangle r = new Rectangle(inner.x, outer.y - dy, inner.width, dy);
component.scrollRectToVisible(r);
} else if (position.y > (inner.y + inner.height)) {
// scroll downard
int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, 1);
Rectangle r = new Rectangle(inner.x, outer.y + outer.height, inner.width, dy);
component.scrollRectToVisible(r);
}
if (position.x < inner.x) {
// scroll left
int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, -1);
Rectangle r = new Rectangle(outer.x - dx, inner.y, dx, inner.height);
component.scrollRectToVisible(r);
} else if (position.x > (inner.x + inner.width)) {
// scroll right
int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, 1);
Rectangle r = new Rectangle(outer.x + outer.width, inner.y, dx, inner.height);
component.scrollRectToVisible(r);
}
}
private void cleanup() {
timer.stop();
}
}
public static void applyTo(JComponent component) {
if (component.getTransferHandler() == null) {
throw new IllegalStateException("A TransferHandler must be set before calling this method!");
}
try {
component.getDropTarget().addDropTargetListener(new AutoscrollWorkaround(component));
} catch (TooManyListenersException e) {
throw new IllegalStateException("Something went wrong! DropTarget should have been " +
"SwingDropTarget which accepts multiple listeners", e);
}
}
private static void setDragAndDrop(JList<String> jlist) {
jlist.setDragEnabled(true);
jlist.setDropMode(DropMode.INSERT);
jlist.setTransferHandler(new ListTransferHandler());
AutoscrollWorkaround.applyTo(jlist); // <--- just this line added
}