如
bug description
,有两个类可以处理拖放:
-
DropTargetAutoScroller
,的成员类
java.awt.dnd.DropTarget
,负责支持实施
Autoscroll
界面
-
DropHandler
,的成员类
javax.swing.TransferHandler
,它自动化了d&d在实现
Scrollable
界面
因此,确实,解决方法不适合
JList
,实现
可滚动
而不是
自动滚动
。但是,如果您在源代码中查找
DropTarget
和
TransferHandler
,您会注意到自动滚动代码基本相同,并且在两种情况下都是错误的。解决方法也与
删除目标(DropTarget)
代码,只添加了几行。基本上,解决方案是将鼠标光标的位置从组件坐标系转换为屏幕坐标系。这样,当检查鼠标是否移动时,使用绝对坐标。所以我们可以从
传输处理程序
而是添加这几行。
太好了。。。但是我们把这个代码放在哪里,如何调用它?
如果我们往里看
setTransferHandler()
我们看到它实际上设置了
删除目标(DropTarget)
,这是一个
包专用静态
类已调用
SwingDropTarget
来自
传输处理程序
班它将拖放事件委派给
专用静态
DropTargetListener
打电话
删除处理程序
。该类执行拖放过程中发生的所有魔术,当然,它还使用来自
传输处理程序
这意味着我们不能自己设定
删除目标(DropTarget)
而不会丢失已在中实现的所有功能
传输处理程序
。我们可以重写
传输处理程序
(大约1800行),我们添加了几行来修复这个bug,但这不是很现实。
更简单的解决方案是编写
DropTargetListener
,其中我们只需从
删除处理程序
(它也实现了这个接口),并添加了我们的行。这是一个类:
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();
}
}
(您会注意到,基本上只有SwingUtilities.convertXYZ()调用是
传输处理程序
代码)
接下来,我们可以将此侦听器添加到
删除目标(DropTarget)
在设置
传输处理程序
。(注意
删除目标(DropTarget)
只接受一个侦听器,如果添加了另一个,则将抛出异常。
SwingDropTarget(摆动下降目标)
使用
删除处理程序
,但幸运的是,它还增加了对其他听众的支持)
所以,让我们将这个静态工厂方法添加到
AutoscrollWorkaround
类,它为我们做到了这一点:
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);
}
}
这提供了一种简单且非常方便的方法,只需调用这一个方法,即可将解决方法应用于任何遭受此错误的组件。只需确保在
setTransferHandler()
在组件上。因此,我们只需在原始程序中添加一行:
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
}
自动滚动现在在Windows和Linux上都可以正常工作。(虽然在Linux上,在自动滚动工作之前,放置位置的行不会重新绘制,但哦。)
此解决方法也适用于
JTable
(我测试过),
JTree
以及可能实现的任何组件
可滚动
.